這個項目也是初窺python爬蟲的一個項目,也是我的畢業設計,當時選題的時候,發現大多數人選擇的都是網站類,實在是普通不過了,都是一些簡單的增刪查改,業務類的給人感覺一種很普通的系統設計,當時也剛好在知乎上看到了一個回答,你是如何利用計算機技術解決生活的實際問題,鏈接就不放了,有興趣的可以搜索下,然后就使用了這個課題。
摘要:基于 python 分布式房源數據抓取系統為數據的進一步應用即房源推薦系統做數據支持。本課題致力于解決單進程單機爬蟲的瓶頸,打造一個基于 Redis 分布式多爬蟲共享隊列的主題爬蟲。本系統采用 python 開發的 Scrapy 框架來開發,使用 Xpath 技術對下載的網頁進行提取解析,運用 Redis 數據庫做分布式,使用MongoDb 數據庫做數據存儲,利用 Django web 框架和 Semantic UI開源框架對數據進行友好可視化,最后使用了Docker對爬蟲程序進行部署。設計并實現了針對 58 同城各大城市租房平臺的分布式爬蟲系統。
?系統功能架構圖
分布式爬蟲抓取系統主要包含以下功能:
1.爬蟲功能:
爬取策略的設計
內容數據字段的設計
增量爬取
請求去重
2.中間件:
爬蟲防屏蔽中間件
網頁非200狀態處理
爬蟲下載異常處理
3.數據存儲:
抓取字段設計
數據存儲
4.數據可視化
分布式采用主從結構設置一個Master服務器和多個Slave服務器,Master端管理Redis數據庫和分發下載任務,Slave部署Scrapy爬蟲提取網頁和解析提取數據,最后將解析的數據存儲在同一個MongoDb數據庫中。分布式爬蟲架構如圖所示。
分布式爬蟲架構圖
??應用Redis數據庫實現分布式抓取,基本思想是Scrapy爬蟲獲取的到的detail_request的urls都放到Redis Queue中,所有爬蟲也都從指定的Redis Queue中獲取requests,Scrapy-Redis組件中默認使用SpiderPriorityQueue來確定url的先后次序,這是由sorted set實現的一種非FIFO、LIFO方式。因此,待爬隊列的共享是爬蟲可以部署在其他服務器上完成同一個爬取任務的一個關鍵點。此外,在本文中,為了解決Scrapy單機局限的問題,Scrapy將結合Scrapy-Redis組件進行開發,Scrapy-Redis總體思路就是這個工程通過重寫Scrapu框架中的scheduler和spider類,實現了調度、spider啟動和redis的交互。實現新的dupefilter和queue類,達到了判重和調度容器和redis的交互,因為每個主機上的爬蟲進程都訪問同一個redis數據庫,所以調度和判重都統一進行統一管理,達到了分布式爬蟲的目的。
1)爬取策略的設計
由scrapy的結構分析可知,網絡爬蟲從初始地址開始,根據spider中定義的目標地址獲的正則表達式或者Xpath獲得更多的網頁鏈接,并加入到待下載隊列當中,進行去重和排序之后,等待調度器的調度。
在這個系統中,新的鏈接可以分為兩類,一類是目錄頁鏈接,也就是我們通常看到的下一頁的鏈接,一類是內容詳情頁鏈接,也就是我們需要解析網頁提取字段的鏈接,指向的就是實際的房源信息頁面。網絡需從每一個目錄頁鏈接當中,提取到多個內容頁鏈接,加入到待下載隊列準備進一步爬取。爬取流程如下:
此處是Master端的目標鏈接的爬取策略,因為采取的分布式主從模式,Master端爬蟲主要爬取下載到內容詳情頁鏈接,通過redis分享下載任務給其他slave端的爬蟲。Slave端主要是負責對詳情頁鏈接的進一步解析提取存儲到數據庫中。
本論文以58同城租房為例,其初始頁鏈接,其實也就是每個分類的第一頁鏈接,主要有(以廣東省幾個城市為例):
① 東莞租房:(http://dg.58.com/chuzu/),
② 深圳租房:(http://sz.58.com/chuzu/),
③ 汕尾租房:(http://sw.58.com/chuzu/),
④ 廣州租房:(http://gz.58.com/chuzu/),
其目錄頁鏈接如下所述:
⑤ 第二頁(http://dg.58.com/chuzu/pn2)
⑥ 第三頁(http://dg.58.com/chuzu/pn3)
⑦ 第四頁(http://dg.58.com/chuzu/pn4)
其內容詳情頁如下:
⑧ (http://taishan.58.com/zufang/29537279701166x.shtml)
綜上所述,網絡房源爬取系統使用以下爬取策略:
1)?對于Master端:
最核心模塊是解決翻頁問題和獲取每一頁內容詳情頁鏈接。Master端主要采取以下爬取策略:
1.?向redis往key為nest_link插入初始鏈接,從初始頁鏈接開始
2.?爬蟲從redis中key為next_link中取到初始鏈接,開始運行爬蟲
3.?將下載器返回的Response,爬蟲根據spider定義的爬取規則識別是否有下一頁鏈接,若有鏈接,存儲進redis中,保存key為next_link,同時根據匹配規則是否匹配到多個內容詳情頁鏈接,若匹配到,則存儲進Redis,保存key為detail_request插入下載鏈接,給slave端的spider使用,即是Slave端的下載任務。
4.?爬蟲繼續從redis中key為next_link取值,若有值,繼續步驟2,若為空,爬蟲則等待新的鏈接。
2)?對于Slave端:
最核心模塊是從redis獲得下載任務,解析提取字段。Slave端主要采取以下爬取策略:
1.爬蟲從redis中key為detail_request中取到初始鏈接,開始運行爬蟲
2.將下載器返回的Response,爬蟲根據spider定義的爬取規則識別是否有匹配規則的內容字段,若有將字段存儲,返回到模型中,等待數據存儲操作。
重復步驟1,直到帶爬取隊列為空,爬蟲則等待新的鏈接。
2)爬蟲的具體實現
爬蟲程序的包含四個部分,分別是對象定義程序,數據抓取程序,數據處理程序和下載設置程序,此處的組成是Slave端,Master少了對象定義程序以及數據處理程序,Master端主要是下載鏈接的爬取。
(1)數據抓取程序
???數據抓取程序分Master端和Slave端,數據抓取程序從Redis中獲得初始地址,數據抓取程序中定義了抓取網頁的規則和使用Xpath提取字段數據的方法等,這里著重介紹Xpath提取字符數據的方法,Xapth使用路徑表達式來選取網頁文檔中的節點或者節點集。在Xpath中有其中類型的幾點:元素、屬性、文本、命名空間、處理指令、注釋和文檔節點。網頁文檔是被當做節點樹來對待,樹的跟被稱為文檔節點和根節點,通過Xpath表達式定位目標節點即可抽取網頁文檔的字段數據,下面以Master抓取內容頁鏈接和Slave提取字段數據為例。
a.?Master端的例子
Xpath抽取下一頁鏈接的方法:
Xpath抽取內容詳情頁鏈接的方法:
因為網站對內容詳情頁做了反爬措施,詳情頁點擊之后,把id獲取到再跳轉到某個域名,因此,自己構造詳情頁id,實現如下:
response_url[0]+'/zufang/' + detail_link.split('_')[3] + 'x.shtml'
a.?slave端的例子:
Xpath抽取內容頁的方法:
帖子名稱:
response_selector.xpath(u'//div[contains(@class,"house-title")]/h1[contains(@class,"c_333 f20")]/text()').extract()
帖子發布時間:
response_selector.xpath(u'//div[contains(@class,"house-title")]/p[contains(@class,"house-update-info c_888 f12")]/text()').extract()
因為有些數據不是用Xpath就可以提取出來,還需要正則進行匹配,如果出現異常也要進行處理,一般頁面匹配不到相應的字段的時候,都應該設置為0,到item后進行處理,對itme進行過濾處理。
3)去重與增量爬取
去重與增量爬取,對于服務器有很重大的意義,能夠減少服務器的壓力以及保證數據的準確性。如果不采取去重處理,那么抓取的內容會抓取大量重復內容,讓爬蟲效率極大的下降。其實去重流程很簡單,核心就是每次請求的時候,先判斷這個請求是否在已經爬取的隊列當中。如果已存在,則舍棄當前請求。
具體實現步驟:
(1)?從待爬隊列中獲取url
(2)?將即將請求的url判斷是否已經爬取,若已爬取,則將請求忽略,未爬取,繼續其他操作并將url插入已爬取隊列中
(3)?重復步驟1
這里我們使用scrapy-redis的去重組件,所以也沒有實現,不過原理還是要看懂的,具體可以看源碼。
4)爬蟲中間件
爬蟲中間件能夠幫助我們在scrapy抓取流程中自由的擴展自己的程序,以下有爬蟲防屏蔽中間件,下載器異常狀態中間件以及非200狀態中間件。
訪問一個網站的網頁的時候,會給網站帶了一定的負載,而爬蟲程序則是模擬了我們正常訪問網頁的過程,但是。大規模的爬蟲會給網站增加大量的負載,影響正常用戶的訪問。為保證網頁能夠別大多數正常用戶的訪問,大多數網站都有相應的防爬蟲策略。一旦訪問行為被認定為爬蟲,網站將會采取一定的措施,限制你的訪問,比如提示你,訪問過于頻繁讓你輸入驗證碼,更嚴重者,會封掉你的ip,禁止你訪問該網站。本系統定向抓取網頁數據的時候,將不間斷的訪問網站內容,如果不采取偽裝措施,很容易被網站識別為爬蟲行為而屏蔽掉。
本系統采用以下方法來防止爬蟲被屏蔽:
1.?模擬不同的瀏覽器行為
2.?以一定的頻率更換代理服務器和網關
3.?本著君子協議,降低爬蟲爬取網頁的頻率,減少并發爬取的進程,限制每個ip并發爬取的次數,犧牲一定的效率來換取系統的穩定性。
4.?禁用cookie,網站會在用戶訪問時在cookie中插入一些信息來判斷是否是機器人,我們屏蔽調cookie,也有利于我們的身份不同意暴露。
5.?人工打碼,這應該是無懈可擊的防被禁措施,所有系統也比不過人工的操作,但是減少了自動化,效率也不高,但確實最有效的措施。爬蟲被禁的時候,會重定向到一個驗證碼頁面去,輸入驗證碼即可重新有權限訪問頁面,為此,我加了郵件提醒模塊,當爬蟲被禁,發郵件提醒管理員解封,同時將重定向的請求重新加入到待爬取的下載隊列當中,保證數據的完整度。
爬蟲防網站屏蔽原理如下圖所示:
(a)模擬不同瀏覽器行為實現思路及代碼
原理:從scrapy的介紹我們可以知道,scrapy有下載中間件,在這個中間件我們可以對請求跟響應進行自定義處理,類似于spring面向切面編程,像一個鉤子嵌入到程序的運行前后。核心就是對請求的屬性進行修改
????首先主要是對下載中間件進行了擴展,首先在seetings.py上面增加中間件,
其次,擴展中間件,主要是寫一個useragent列表,將常用的瀏覽器請求頭保存為一個列表,如下所示:
再讓請求的頭文件隨機在列表中取一個agent值,然后到下載器進行下載。
綜上,每次發出請求的時候模擬使用不同的瀏覽器對目標網站進行訪問。
(b)使用代理ip進行爬取的實現思路及代碼。
首先在seetings.py上面增加中間件,擴展下載組件請求的頭文件隨機從代理ip池中取出一個代理值然后到下載器進行下載。
1.?代理ip池的設計與開發流程如下:
a.?對免費代理ip網站進行抓取。
b.?對代理ip進行存儲并驗證
c.?驗證通過存儲進數據庫
d.?如果滿足ip最大數量,則停止爬去,一定時間后驗證數據的ip有效性,將失效的ip刪除
e.?直到數據庫ip小于0,繼續爬取ip,重復步驟a。
代理ip模塊這里使用了七夜代理ip池的開源項目
代理ip爬蟲運行截圖:
(c)爬蟲異常狀態組件的處理
爬蟲沒有被屏蔽運行時,訪問網站不是一直都是200請求成功,而是有各種各樣的狀態,像上述爬蟲被禁的時候,其實返回的狀態是302,防止屏蔽組件就是捕捉到302狀態加以實現的。同時異常狀態的處理有利于爬蟲的健壯性。
在settings中擴展中間件捕捉到異常的情況之后,將請求Request重新加入到待下載隊列當中流程如下:
(d)數據存儲模塊
數據存儲模塊主要負責將slave端爬取解析的頁面進行存儲。使用Mongodb對數據進行存儲。
Scrapy支持數據存儲的格式有json,csv和xml等文本格式,用戶可以在運行爬蟲時設置,例如:scrapy crawl spider -o items.json -t json,也可以在Scrapy工程文件額ItemPipline文件中定義,同時,Scrapy也支持數據庫存儲,如Monogdb,Redis等,當數據量大到一定程度時,可以做Mongodb或者Reids的集群來解決問題,本系統數據存儲如下圖所示:
(e)抓取字段設計
本文以網絡房源數據為抓取目標,由slave端解析抓取字段數據,因此抓取的內容必須能夠客觀準確地反映網絡房源數據特征。
以抓取58同城網絡房源數據為例,通過分析網頁結構,定義字段詳情如下表所示。
序號字段名稱字段含義
1title帖子標題
2money租金
3method租賃方式
4area所在區域
5community所在小區
6targeturl帖子詳情
7city所在城市
8Pub_time帖子發布時間
字段選取主要是依據本系統的應用研究來進行的,因為系統開發單機配置比較低,沒有下載圖片文件到本機。減少單機承受的壓力。
(f)數據處理
1)?對象定義程序
Item是定義抓取數據的容器。通過創建一個scrapy.item.Item類來聲明。定義屬性為scrapy.item.Field對象,通過將需要的item實例化,來控制獲得的站點數據。本系統定義了九個抓取對象,分別是:帖子標題,租金,租賃方式,所在區域,所在小區,所在城市,帖子詳情頁鏈接,發布時間。此處字段的定義基于數據處理端的需要來定義的。關鍵代碼如下:
class TcZufangItem(Item):
????#帖子名稱
????title=Field()
????#租金
????money=Field()
????#租賃方式
????method=Field()
????#所在區域
????area=Field()
????#所在小區
????community=Field()
????#帖子詳情url
????targeturl=Field()
????#帖子發布時間
????pub_time=Field()
????#所在城市
????city=Field()
2)?數據處理程序
Pipeline類中定義了數據的保存和輸出方法,從Spider的parse方法返回的Item,數據將對應ITEM_PIPELINES列表中的Pipeline類處理后以頂一個格式輸出。本系統傳回管道的數據使用Mongodb來進行存儲。關鍵代碼如下:
?def process_item(self, item, spider):
????????if item['pub_time'] == 0:
????????????raise DropItem("Duplicate item found: %s" % item)
????????if item['method'] == 0:
????????????raise DropItem("Duplicate item found: %s" % item)
????????if item['community']==0:
????????????raise DropItem("Duplicate item found: %s" % item)
????????if item['money']==0:
????????????raise DropItem("Duplicate item found: %s" % item)
????????if item['area'] == 0:
????????????raise DropItem("Duplicate item found: %s" % item)
????????if item['city'] == 0:
????????????raise DropItem("Duplicate item found: %s" % item)
????????zufang_detail = {
????????????'title': item.get('title'),
????????????'money': item.get('money'),
????????????'method': item.get('method'),
????????????'area': item.get('area', ''),
????????????'community': item.get('community', ''),
????????????'targeturl': item.get('targeturl'),
????????????'pub_time': item.get('pub_time', ''),
????????????'city':item.get('city','')
????????}
????????result = self.db['zufang_detail'].insert(zufang_detail)
????????print '[success] the '+item['targeturl']+'wrote to MongoDB database'
????????return item
(g)數據可視化設計
數據的可視化其實也就是,將數據庫的數據轉換成我們用戶容易觀察的形式,本系統使用Mongodb對數據進行存儲。對數據進行可視化基于Django+Semantiui,效果如下圖所示:
系統以58同城租房平臺為抓取目標,運行十小時之后,持續抓取網頁數量共計幾萬條房源數據。
Master端運行截圖:
Slave端運行截圖:
環境部署,因為分布式部署所需環境都是類似的,如果一個服務器部署程序都需要在配置下環境顯得很麻煩,這里使用了docker鏡像對爬蟲程序進行部署,使用了Daocloud上的scrapy-env對程序進行了部署,具體docker部署過程可以參考網上。