目的:了解深圳單簡出租房屋的價格分布
成果(簡單可視化):






由于請求限制目前,目前樣本只有1200個左右。有待。



主要使用的庫:BeautifulSoup,requests,Pool
IDE:Pycharm
瀏覽器:Opera Next
數(shù)據(jù)庫:Mongodb
分析工具:Power BI,Excel
一,編寫爬蟲
(1)抓取各個地區(qū)的主頁面

我們要把每個地區(qū)的主網(wǎng)頁抓下來,導(dǎo)入需要的庫
from bs4 import BeautifulSoup
import requests
先點一個地區(qū)看一下網(wǎng)址是什么樣子的
我們得到了:sz.58.com/futian/hezu/
再看一下地區(qū)所在的標(biāo)簽

在dd下的a標(biāo)簽,url后部分是href部分
可以知道各個地區(qū)的url是:http://sz.58.com +'href'即http://sz.58.com + /futian/hezu/
定義一下主頁部分:
host_url ='http://sz.58.com'
接下來可以定義抓取的函數(shù)了
def get_index_url(url):
wb_data =requests.get(url)
soup = BeautifulSoup(wb_data.text,'lxml')
links = soup.select('body > div.mainbox > div.main > div.search_bd > dl.secitem.secitem_fist > dd > a')#用copy中的copy selector
for linkin links:
page_url = host_url + link.get('href')
print(page_url)
我們把所有a中的href取出來和主頁http://sz.58.com加在一起再打印出來,就得到了各個地區(qū)的合租主頁了,其實這幾個可以手動復(fù)制粘貼出來,但是Life is short,U need Python.這也是我們Python的一個原因。
打印出來把他直接復(fù)制出來定義成hezu_channel
hezu_channel ='''
http://sz.58.com/luohu/hezu/
http://sz.58.com/futian/hezu/
http://sz.58.com/nanshan/hezu/
http://sz.58.com/yantian/hezu/
http://sz.58.com/baoan/hezu/
http://sz.58.com/longgang/hezu/
http://sz.58.com/buji/hezu/
http://sz.58.com/pingshanxinqu/hezu/
http://sz.58.com/guangmingxinqu/hezu/
http://sz.58.com/szlhxq/hezu/
http://sz.58.com/dapengxq/hezu/
http://sz.58.com/shenzhenzhoubian/hezu/
'''
注意這里有一個'所有'下的鏈接,把它手動刪除
(2)抓取各個地區(qū)的網(wǎng)頁上的數(shù)據(jù)
由于我們想要的標(biāo)題、價格、地址、面積信息都在頁面上展示了就不用再下到詳細(xì)頁面去抓取了

現(xiàn)在導(dǎo)入鏈接mongodb的庫pymongo,還有讓爬蟲休息一下避免被封ip的time庫和random(讓時間隨機(jī)一下)
import pymongo
import random
import time
先在mongodb創(chuàng)建庫
client = pymongo.MongoClient('localhost',27017)
work = client['work']
hezu2 = work['hezu2']
寫一個函數(shù)能抓取一個頁面的信息,變量為前面得到的channel_list和頁碼(待會對頁碼做個循環(huán)就可以遍歷所有頁了)
我們先看一下網(wǎng)頁結(jié)構(gòu):http://sz.58.com/futian/hezu/
點擊第二頁:http://sz.58.com/futian/hezu/pn2/
我們試試把pn2給成pn1,結(jié)果是跳到第一頁,可以知道對channel_list中的url加上pn{}就可以翻頁了
于是頁面構(gòu)成變?yōu)椋篶hannel_list+pn{}
寫一個函數(shù)包含著兩個變量
def get_url_all_infom(channel,pages):
list_view ='{}pn{}'.format(channel,str(pages))#要看的頁url
wb_data = requests.get(list_view)#請求
soup = BeautifulSoup(wb_data.text,'lxml')#解析
由于有些地區(qū)的頁碼沒有70頁(如坪山) ,我們要判斷如果沒有就跳過,唯一標(biāo)識就是頁碼欄了
看一下源代碼的位置

這里有一個strong,我們看看是不是唯一的,搜索發(fā)現(xiàn)的確是唯一的,而對pn9的頁面是沒有的,我們可以把‘strong’作為唯一標(biāo)識
如果在soup發(fā)現(xiàn)了這個標(biāo)識就抓取,沒有的話就打印ERROR
if soup.find('strong'):
try:
except:
print('ERROR')
接下來是抓取元素
一樣的,我們找到所對應(yīng)的標(biāo)簽,這里要調(diào)整的是我們要抓取標(biāo)簽下的所有子標(biāo)簽
titles = soup.select('li > div > h2 > a')
areas = soup.select('body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > p.add')
zones = soup.select('body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > p.room')
links = soup.select('li > div > h2 > a')
prices = soup.select('body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.listliright > div.money > b')
把這些東西裝進(jìn)一個字典
for title, area, price, zone, linkin zip(titles, areas, prices, zones, links):
title = title.text,#直接把文本提取出來
area = area.text + list_view.split('/')[-3],#同理,但是這里的位置沒有區(qū)名,我們從訪問的url提取,用split根據(jù)/把url拆開再取倒數(shù)第三個(http://sz.58.com/**pingshanxinqu**/hezu/pn2/)
price = price.text,#這里可以用int轉(zhuǎn)成數(shù)值,但是可能出現(xiàn)價格為‘面議’的情況,所以直接這樣處理
zone = zone.text.split(' ')[-1],#面積標(biāo)簽下還有是否是主臥次臥,我們只要面積
link = link.get('href')#可以把這個租房的地址也取出來
要通過一定的方法把要的東西篩選出來,具體看注釋。
然后對字典里的逐一插入數(shù)據(jù)庫
hezu2.insert_one({'title':title,'area':area,'price':price,'zone':zone,'link':link})
睡上一些時間
i=int(random.choice([20,22,23]))
time.sleep(i)
完整代碼:
def get_url_all_infom(channel,pages):
list_view ='{}pn{}'.format(channel,str(pages))
wb_data = requests.get(list_view)
soup = BeautifulSoup(wb_data.text,'lxml')
if soup.find('strong'):
try:
titles = soup.select('li > div > h2 > a')
areas = soup.select('body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > p.add')
zones = soup.select('body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.des > p.room')
links = soup.select('li > div > h2 > a')
prices = soup.select('body > div.mainbox > div.main > div.content > div.listBox > ul > li > div.listliright > div.money > b')
for title, area, price, zone, linkin zip(titles, areas, prices, zones, links):
title = title.text,
area = area.text + list_view.split('/')[-3],
price = price.text,
zone = zone.text.split(' ')[-1],
link = link.get('href')
hezu2.insert_one({'title':title,'area':area,'price':price,'zone':zone,'link':link})
i=int(random.choice([20,22,23]))
time.sleep(i)
except:
print('ERROR')
我們使用多進(jìn)程處理,新建一個main.py調(diào)動所有要用的函數(shù)
#-*- coding: utf-8 -*-
from multiprocessingimport Pool
from parsingimport get_url_all_infom#抓取信息并插入數(shù)據(jù)庫的函數(shù)
from shenzhen_extractimport hezu_channel_list#各個地區(qū)的主頁列表
def get_all_from(channel):#提取各地區(qū)里的1到70頁的鏈接
for iin range(1,71):
get_url_all_infom(channel,i)#用這個函數(shù)提取地區(qū)所有鏈接
if __name__ =='__main__':
pool = Pool()
# pool = Pool(processes=4)
pool.map(get_all_from,hezu_channel_list.split())#map函數(shù)可以用第一個參數(shù)中的函數(shù)一一處理第二個參數(shù)的元素(用split把他們拆分一一放進(jìn)get_all_from)
我們再編寫一個監(jiān)控腳本,當(dāng)數(shù)據(jù)庫中的數(shù)據(jù)不發(fā)生變化時發(fā)出警告(可能是58同城監(jiān)視到了要求輸入驗證碼,可以手動輸入一下)
import time
from parsingimport hezu2
import winsound#是一個發(fā)出聲音的函數(shù)
print('ok~')
t=0
while True:
print(hezu2.find().count())
i = int(hezu2.find().count())
time.sleep(5)
print(hezu2.find().count())
time.sleep(5)
if i == int(hezu2.find().count()):
winsound.Beep(1000,5000)#1000位頻率,5000為時間
(3)終端運行爬蟲
先運行main.py,到所在文件夾調(diào)用命令python main.py
再運行counts.py
(4)改進(jìn)與問題
多進(jìn)程只對多核處理器有用
BeautifulSoup解析速度過慢,可改用lxml庫
異步非阻塞方式提高請求效率
異步網(wǎng)絡(luò)和多個ip請求