1. 程式人生 > >python2.7爬蟲例項詳細介紹之爬取大眾點評的資料

python2.7爬蟲例項詳細介紹之爬取大眾點評的資料

一.

Python作為一種語法簡潔、面向物件的解釋性語言,其便捷性、容易上手性受到眾多程式設計師的青睞,基於python的包也越來越多,使得python能夠幫助我們實現越來越多的功能。本文主要介紹如何利用python進行網站資料的抓取工作。我看到過利用c++和java進行爬蟲的程式碼,c++的程式碼很複雜,而且可讀性、可理解性較低,不易上手,一般是那些高手用來寫著玩加深對c++的理解的,這條路目前對我們不通。Java的可讀性還可以,就是程式碼冗餘比較多,同樣的一個爬蟲,java的程式碼量可能是python的兩倍,感覺也沒有python容易上手。因此,建議大家以後如果對爬蟲有興趣的話直接使用python就好。

二.本文首先爬取大眾點評-北京的火鍋這個條目下的部分資料。下面,我主要針對如何爬取資料進行講解,針對資料進行分析的部分就略過。會根據我自己的理解和經驗對程式碼進行詳細的分析,比較適合初學者,高手請出門右轉。由於是針對初學者,所以我最大程度地將程式碼進行精簡,當然也因此刪去了一些功能。

三、注意!

不要盲目的直接把程式碼複製直接執行,最好先看完本文,然後再執行。因為我是在ubuntu14.04下執行的我的程式碼,因此在獲取資料時的編碼格式不一樣,輸出資訊到視窗時的編碼也會有所不同,在linux下預設編碼是utf-8,而在windows下預設編碼是gbk,所以,如果系統不同,直接執行程式碼,可能會輸出亂碼,這不代表我的程式碼有問題。需要注意的問題,在本文中我基本上都給了講解,如果還有問題的話,歡迎留言探討。

本人的瀏覽器為forefox,不同瀏覽器的檢視元素的方法和介面也會有所不同,我在下文進行介紹時,只能以我的瀏覽器為準,不同的瀏覽器可以自己找一下相應的東西,一般不會差太多。

四.閒話少敘,直接上程式碼。

# -*- coding:utf-8 -*-
import re
from bs4 import BeautifulSoup
import json
import threading
from requests import Session
class dazp_bj:
	def __init__(self,category):
		self.baseUrl='http://www.dianping.com'
		self.bgurl=category[0]
		self.typename=category[1]
		self.page=1
		self.pagenum=10 #設定最大頁面數目,大眾點評每個條目下最多有50頁,可以根據自己需求進行設定
		self.headers={
			"Host":"www.dianping.com",
			"User-Agent":"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/48.0",
			#此User-Agent是我本人的引數,在使用時記得修改為自己的引數,如何獲取下四.4部分有講解
			"Referer":"http://www.dianping.com/beijing",
		}
	def start(self):
		self.s=Session()	#定義一個Session()物件
		print self.bgurl,self.typename
		print "please wait for 15"
		dazp_bj.__parseHtml(self,self.bgurl) #呼叫__parseHtml函式
		print 'getdata down'
	def __parseHtml(self,preurl):
		_json=dict()	#定義一個字典用以儲存數
		html=self.s.post(preurl,headers=self.headers).text	#傳送請求,獲取html
		soup=BeautifulSoup(html,'lxml') #進行解析
              	name=['商家名稱','評論數量','人均消費','地址','評分','連結']
		for li in soup.find('div',class_="shop-wrap").find('div',id="shop-all-list").ul.find_all('li'):
			info=li.find('div',class_='txt')
			_json[name[0]]=info.find('div',class_='tit').a.h4.get_text().encode('utf-8')					
			_json[name[1]]=int(info.find('div',class_='comment').find('a',class_="review-num").b.get_text().encode('utf-8'))
			_json[name[2]]=int(re.sub('¥','',info.find('div',class_='comment').find('a',class_="mean-price").b.get_text().encode('utf-8')))
			_json[name[3]]=info.find('div',class_='tag-addr').find('span',class_='tag').get_text().encode('utf-8')+info.find('div',class_='tag-addr').find('span',class_='addr').get_text().encode('utf-8')
			_json[name[4]]=float(info.find('span',class_='comment-list').find_all('b')[0].get_text())+float(info.find('span',class_='comment-list').find_all('b')[1].get_text())+float(info.find('span',class_='comment-list').find_all('b')[2].get_text())
			_json[name[5]]=self.baseUrl+info.find('div',class_='tit').a['href']				
			with open(self.typename+'.json','a') as outfile:
				json.dump(_json,outfile,ensure_ascii=False)
			with open(self.typename+'.json','a') as outfile:
				outfile.write(',\n')	
		self.page+=1
		if self.page<=self.pagenum:
			self.nexturl=self.baseUrl+soup.find('div',class_='page').find('a',class_='next')['href']  #獲得下一頁的連結
			dazp_bj.__parseHtml(self,self.nexturl)		
if __name__=='__main__':
	cat=[(r'http://www.dianping.com/search/category/2/10/g110',u'火鍋 ')]
	obj=list()
	obj.append(dazp_bj(cat[0]))
	[threading.Thread(target=foo.start(),args=()).start for foo in obj]#多執行緒執行obj列表中的任務
五.上面是所有程式碼。接下來則對程式碼進行精細的講解。

1.# -*- coding:utf-8 -*- #這句程式碼是在檔案的開頭設定好編碼的格式,這樣就能夠在檔案裡寫中文,否則python2.7會預設使用ASCII編碼。個人建議最好在所有的.py檔案的開頭都寫上這句程式碼,防止不必要的錯誤。有時候這樣設定後在爬取某些網站時仍然會出現問題,比如說會報這樣的錯誤:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128) 。這種情況下可以加如下幾行程式碼:

import sys
reload(sys)
sys.setdefaultencoding("utf-8" )
暫時不知道為什麼會這樣,但是這樣就可以解決問題了。據說python2.7在這方面特別容易出錯,在python3中則會好很多,可能以後會轉到python3吧,差別應該不大,新手可以直接學3.

2.現在講一下引用的包。re是正則表示式,對網頁進行解析時很有用,非常強大,當你掌握了正則表示式的使用後,你就可以在各個網頁中來回馳騁了;json是一種資料格式,我利用json檔案在儲存爬取到的字典資料,感覺json儲存字典資料很強大,呼叫讀取都很便捷。之前看到一個txt檔案儲存的字典資料,在讀資料的時候很麻煩,沒有json便捷;threading 是多執行緒,可以提高爬取效率,本文中因為只抓了一個連結,因此沒有用到它的功能,在cat中多加入幾個連結,並append到obj中就可以使用這個功能;bs4是python的一個很強大的對網頁進行解析的包,如果沒有這個包,如果要解析資料都是要使用正則表示式的,那個場面一定很慘烈。python也提供了另一種方式方便查詢網頁中的標籤:xpath,只用過一次,配合firefox的firebug很好用,在定位到感興趣的標籤後直接右鍵即可獲得該標籤的xpath路徑,省得我們還要一個一個標籤去找,但是當我們爬取的網站比較大、複雜的時候,這種方式可能就不是那麼適用了,因為不同條目的同一種類的資料可能在不同的位置,或者有些資料沒有值的情況下這個標籤就不存在,這個時候只能使用beautifulsoup來根據id、class、name等值準確定位到標籤;requests是向伺服器傳送請求的包,有post和get兩張方法,可以傳入很多引數,如headers,cookies,data,params等,headers是你瀏覽器的請求頭資料,傳入這個引數可以將你的爬取訪問偽裝成瀏覽器的正常訪問,對一些網站有效,有些網站則需要傳入cookies,data和params是在其他情況下需要傳入的引數,初學者一般用不到,在此就不贅述,需要的時候百度基本上可以找到方法。requests下的Session()方法和request有點相似,也包括get和post方法,傳的引數也基本上差不多,但是會話物件requests.Session能夠跨請求地保持某些引數,比如cookies,即在同一個Session例項發出的所有請求都保持同一個cookies,而requests模組每次會自動處理cookies,這樣就很方便地處理登入時的cookies問題。但是本文中貌似不需要使用session,只是習慣性的使用這個,大家可以修改後試一下:將後文的self.s替換為requests .

3.首先定義了一個類,類下面有很多方法。__init__是對類的引數進行初始化,類內的全域性變數都可以在此設定,在其他地方設定也可,不過要在變數名前加上global。start(),類中的方法和函式都在此進行呼叫,start可以說是類的入口。__parseHtml是定義的爬取資料的主函式。

4.下面介紹如何針對每個資料條目定製解析語句,以及如何獲得headers引數。


開啟頁面之後右鍵然後選擇檢視元素(有些瀏覽器是檢查元素,我使用的是firefox,注意不是檢視原始碼)然後點網路(Network),然後點左上角“所有”旁邊的刪除符號,然後點選重新載入(或者重新整理頁面),可以看到下圖所示的情況:


點選最上面的那一條,得到下圖,請求頭資訊、cookie、引數等資訊都在這裡。上面程式碼中的headers內的User-Agent是我的瀏覽器資訊,在使用時需要根據自己的電腦進行修改。請求頭中有些資料是不需要的,因此我只選擇了幾條。


解析每個條目資料基本上也是右鍵檢視元素就可以搞定。

右鍵時直接在那個條目的位置右鍵,即可直接定位到該標籤的位置,不需要F12後一層一層找浪費時間。

舉個例子:假如我們要查詢“井各老灶火鍋(望京新世界店)”這幾個字,我們可以將滑鼠放在這些字上方,然後點選右鍵,選擇檢視元素。如下圖所示,可以看到該標籤以及該標籤所屬的每一層的標籤屬性。如果要利用BeautifulSoup定位到這個標籤,可以從最上面的標籤一級一級找下來,但是這樣不僅耗費時間增加程式碼量,而且毫無必要,一般來說只需要找到包含它的標籤中的具有唯一標誌性的標籤即可。對於這個標籤,我們首先找到每一個店鋪所在的標籤,即:

soup.find('div',class_="shop-wrap").find('div',id="shop-all-list").ul.find_all('li')

從上面這句程式碼可以看出,儘管li的上面後很多種各種各樣的標籤,但是我在定位的時候可以跳過這些對我無用的標籤。各個條目的資料都可以按照這種方法來定位並獲取。


5.執行。在windows下執行時,開啟cmd視窗:然後輸入python並空格,然後將需要執行的檔案拖到視窗中回車即可執行。

最後得到的資料是長這樣子的:

{"商家名稱": "珍滋味港式粥火鍋(工體店)", "評分": 27.0, "地址": "火鍋工人體育場東路丙2號中國紅街3號樓2層裡", "連結": "http://www.dianping.com/shop/6232395", "人均消費": 174, "評論數量": 2307}
{"商家名稱": "井格老灶火鍋(望京新世界店)", "評分": 26.200000000000003, "地址": "火鍋望京廣順南大街路16號新世界利瑩百貨購物中心1層望京南地鐵A口西北口往索尼大廈50-100米", "連結": "http://www.dianping.com/shop/23721600", "人均消費": 105, "評論數量": 1387}

六、拓展

1.有些網站有反爬蟲措施,當爬蟲訪問網站的頻率過高時,會需要輸入驗證碼,對於一般的爬蟲來說,專門設計一個自動識別驗證碼並填寫的模組沒有必要,而且對於初學者來說也有點難度。最簡單粗暴的方法就是在每次訪問完一個頁面後加入這樣一句程式碼,人為降低訪問頻率:

import time
import random
time.sleep(1)#停頓一秒
#或者這樣
time.sleep(random.uniform(1,4))#表示設定一個隨機數作為停頓時間,為1~4之間的一個數.

2.有些網站需要載入動態頁面,建議使用selenium包,也可配合PhantomJS(模擬瀏覽器,不是python的包,在需要滑鼠點選等操作時用起來很便捷)一起使用。對於這個包的使用方法網上有大致的介紹,可以參考一下,或許以後我也會單另再寫一篇文章來介紹~

3.Python實現爬蟲功能的包還有很多,比如urlliburllib2等,這些包比較基礎,也比較簡單,但是沒有requests好用、強大,大部分爬蟲都可以用requestsbs4的搭配組合來完成。

4.還有一個爬蟲的包是scrapy。這個包用起來稍微複雜一些,但是上手後很便捷,很多程式碼不用自己去寫,資料間的傳遞等工作它都可以自動完成,而不需要手動去操作。在熟練使用requestsbs4等包後,對於爬蟲原理和方法有了一定的瞭解後再去使用這個包可能會好一些。

七、祝爬蟲順利。