基於Scrapy分佈式爬蟲的開發與設計

這個項目也是初窺python爬蟲的一個項目,也是個人畢業設計,當時選題的時候,發現大多數人選擇的都是網站類,實在是普通不過了,都是一些簡單的增刪查改,業務類的給人感受一種很普通的系統設計,當時也恰好在知乎上看到了一個回答,你是如何利用計算機技術解決生活的實際問題,連接就不放了,有興趣的能夠搜索下,而後就使用了這個課題。
html

摘要:基於 python 分佈式房源數據抓取系統爲數據的進一步應用即房源推薦系統作數據支持。本課題致力於解決單進程單機爬蟲的瓶頸,打造一個基於 Redis 分佈式多爬蟲共享隊列的主題爬蟲。本系統採用 python 開發的 Scrapy 框架來開發,使用 Xpath 技術對下載的網頁進行提取解析,運用 Redis 數據庫作分佈式,使用MongoDb 數據庫作數據存儲,利用 Django web 框架和 Semantic UI開源框架對數據進行友好可視化,最後使用了Docker對爬蟲程序進行部署。設計並實現了針對 58 同城各大城市租房平臺的分佈式爬蟲系統。python

1、系統功能架構

 系統功能架構圖git

分佈式爬蟲抓取系統主要包含如下功能:github

1.爬蟲功能:web

爬取策略的設計正則表達式

內容數據字段的設計redis

增量爬取spring

請求去重docker

2.中間件:數據庫

爬蟲防屏蔽中間件

網頁非200狀態處理

爬蟲下載異常處理

3.數據存儲:

抓取字段設計

數據存儲

4.數據可視化

2、系統分佈式架構

分佈式採用主從結構設置一個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數據庫,因此調度和判重都統一進行統一管理,達到了分佈式爬蟲的目的。

3、系統實現

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抽取下一頁連接的方法:

response_selector.xpath(u'//div[contains(@class,"pager")]/a[contains(@class,"next")]/@href').extract()


Xpath抽取內容詳情頁連接的方法:

response_selector.xpath(u'//div[contains(@class,"listBox")]/ul[contains(@class,"listUl")]/li/@logr').extract(),

由於網站對內容詳情頁作了反爬措施,詳情頁點擊以後,把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狀態中間件。

(1)爬蟲防屏蔽組件的實現

訪問一個網站的網頁的時候,會給網站帶了必定的負載,而爬蟲程序則是模擬了咱們正常訪問網頁的過程,可是。大規模的爬蟲會給網站增長大量的負載,影響正經常使用戶的訪問。爲保證網頁可以別大多數正經常使用戶的訪問,大多數網站都有相應的防爬蟲策略。一旦訪問行爲被認定爲爬蟲,網站將會採起必定的措施,限制你的訪問,好比提示你,訪問過於頻繁讓你輸入驗證碼,更嚴重者,會封掉你的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同城網絡房源數據爲例,經過分析網頁結構,定義字段詳情以下表所示。

序號

字段名稱

字段含義

1

title

帖子標題

2

money

租金

3

method

租賃方式

4

area

所在區域

5

community

所在小區

6

targeturl

帖子詳情

7

city

所在城市

8

Pub_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,效果以下圖所示:



4、系統運行

系統以58同城租房平臺爲抓取目標,運行十小時以後,持續抓取網頁數量共計幾萬條房源數據。

Master端運行截圖:

Slave端運行截圖:


5、系統部署

環境部署,由於分佈式部署所需環境都是相似的,若是一個服務器部署程序都須要在配置下環境顯得很麻煩,這裏使用了docker鏡像對爬蟲程序進行部署,使用了Daocloud上的scrapy-env對程序進行了部署,具體docker部署過程能夠參考網上。

代碼放在github上面,有興趣能夠查看

以上!
相關文章
相關標籤/搜索