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

這個專案也是初窺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抽取下一頁連結的方法:

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

四、系統執行

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

Master端執行截圖:

Slave端執行截圖:

五、系統部署

環境部署,因為分散式部署所需環境都是類似的,如果一個伺服器部署程式都需要在配置下環境顯得很麻煩,這裡使用了docker映象對爬蟲程式進行部署,使用了Daocloud上的scrapy-env對程式進行了部署,具體docker部署過程可以參考網上。

程式碼放在gayhub上面,有興趣可以檢視

以上!