Scrapy-redis實現分佈式爬取的過程與原理

Scrapy是一個比較好用的Python爬蟲框架,你只須要編寫幾個組件就能夠實現網頁數據的爬取。可是當咱們要爬取的頁面很是多的時候,單個主機的處理能力就不能知足咱們的需求了(不管是處理速度仍是網絡請求的併發數),這時候分佈式爬蟲的優點就顯現出來。html

而Scrapy-Redis則是一個基於Redis的Scrapy分佈式組件。它利用Redis對用於爬取的請求(Requests)進行存儲和調度(Schedule),並對爬取產生的項目(items)存儲以供後續處理使用。scrapy-redi重寫了scrapy一些比較關鍵的代碼,將scrapy變成一個能夠在多個主機上同時運行的分佈式爬蟲。python

原生的Scrapy的架構是這樣子的:git

加上了Scrapy-Redis以後的架構變成了:github

scrapy-redis的官方文檔寫的比較簡潔,沒有說起其運行原理,因此若是想全面的理解分佈式爬蟲的運行原理,仍是得看scrapy-redis的源代碼才行,不過scrapy-redis的源代碼不多,也比較好懂,很快就能看完。redis

scrapy-redis工程的主體仍是是redis和scrapy兩個庫,工程自己實現的東西不是不少,這個工程就像膠水同樣,把這兩個插件粘結了起來。數據庫

scrapy-redis提供了哪些組件?

scrapy-redis所實現的兩種分佈式:爬蟲分佈式以及item處理分佈式。分別是由模塊scheduler和模塊pipelines實現。json

connection.py服務器

負責根據setting中配置實例化redis鏈接。被dupefilter和scheduler調用,總之涉及到redis存取的都要使用到這個模塊。網絡

connect文件引入了redis模塊,這個是redis-python庫的接口,用於經過python訪問redis數據庫,可見,這個文件主要是實現鏈接redis數據庫的功能(返回的是redis庫的Redis對象或者StrictRedis對象,這倆都是能夠直接用來進行數據操做的對象)。這些鏈接接口在其餘文件中常常被用到。其中,咱們能夠看到,要想鏈接到redis數據庫,和其餘數據庫差很少,須要一個ip地址、端口號、用戶名密碼(可選)和一個整形的數據庫編號,同時咱們還能夠在scrapy工程的setting文件中配置套接字的超時時間、等待時間等。數據結構

dupefilter.py

負責執行requst的去重,實現的頗有技巧性,使用redis的set數據結構。可是注意scheduler並不使用其中用於在這個模塊中實現的dupefilter鍵作request的調度,而是使用queue.py模塊中實現的queue。當request不重複時,將其存入到queue中,調度時將其彈出。

這個文件看起來比較複雜,重寫了scrapy自己已經實現的request判重功能。由於自己scrapy單機跑的話,只須要讀取內存中的request隊列或者持久化的request隊列(scrapy默認的持久化彷佛是json格式的文件,不是數據庫)就能判斷此次要發出的request url是否已經請求過或者正在調度(本地讀就好了)。而分佈式跑的話,就須要各個主機上的scheduler都鏈接同一個數據庫的同一個request池來判斷此次的請求是不是重複的了。

在這個文件中,經過繼承BaseDupeFilter重寫他的方法,實現了基於redis的判重。根據源代碼來看,scrapy-redis使用了scrapy自己的一個fingerprint接request_fingerprint,這個接口頗有趣,根據scrapy文檔所說,他經過hash來判斷兩個url是否相同(相同的url會生成相同的hash結果),可是當兩個url的地址相同,get型參數相同可是順序不一樣時,也會生成相同的hash結果(這個真的比較神奇。。。)因此scrapy-redis依舊使用url的fingerprint來判斷request請求是否已經出現過。這個類經過鏈接redis,使用一個key來向redis的一個set中插入fingerprint(這個key對於同一種spider是相同的,redis是一個key-value的數據庫,若是key是相同的,訪問到的值就是相同的,這裏使用spider名字+DupeFilter的key就是爲了在不一樣主機上的不一樣爬蟲實例,只要屬於同一種spider,就會訪問到同一個set,而這個set就是他們的url判重池),若是返回值爲0,說明該set中該fingerprint已經存在(由於集合是沒有重複值的),則返回False,若是返回值爲1,說明添加了一個fingerprint到set中,則說明這個request沒有重複,因而返回True,還順便把新fingerprint加入到數據庫中了。 DupeFilter判重會在scheduler類中用到,每個request在進入調度以前都要進行判重,若是重複就不須要參加調度,直接捨棄就行了,否則就是白白浪費資源。

queue.py

其做用如dupefilter.py所述,可是這裏實現了三種方式的queue:FIFO的SpiderQueue,SpiderPriorityQueue,以及LIFI的SpiderStack。默認使用的是第二種,這也就是出現以前文章中所分析狀況的緣由(連接)。

該文件實現了幾個容器類,能夠看這些容器和redis交互頻繁,同時使用了咱們上邊picklecompat中定義的serializer。這個文件實現的幾個容器大致相同,只不過一個是隊列,一個是棧,一個是優先級隊列,這三個容器到時候會被scheduler對象實例化,來實現request的調度。好比咱們使用SpiderQueue最爲調度隊列的類型,到時候request的調度方法就是先進先出,而實用SpiderStack就是先進後出了。

咱們能夠仔細看看SpiderQueue的實現,他的push函數就和其餘容器的同樣,只不過push進去的request請求先被scrapy的接口request_to_dict變成了一個dict對象(由於request對象實在是比較複雜,有方法有屬性很差串行化),以後使用picklecompat中的serializer串行化爲字符串,而後使用一個特定的key存入redis中(該key在同一種spider中是相同的)。而調用pop時,其實就是從redis用那個特定的key去讀其值(一個list),從list中讀取最先進去的那個,因而就先進先出了。

這些容器類都會做爲scheduler調度request的容器,scheduler在每一個主機上都會實例化一個,而且和spider一一對應,因此分佈式運行時會有一個spider的多個實例和一個scheduler的多個實例存在於不一樣的主機上,可是,由於scheduler都是用相同的容器,而這些容器都鏈接同一個redis服務器,又都使用spider名加queue來做爲key讀寫數據,因此不一樣主機上的不一樣爬蟲實例公用一個request調度池,實現了分佈式爬蟲之間的統一調度。

picklecompat.py

這裏實現了loads和dumps兩個函數,其實就是實現了一個serializer,由於redis數據庫不能存儲複雜對象(value部分只能是字符串,字符串列表,字符串集合和hash,key部分只能是字符串),因此咱們存啥都要先串行化成文本才行。這裏使用的就是python的pickle模塊,一個兼容py2和py3的串行化工具。這個serializer主要用於一會的scheduler存reuqest對象,至於爲何不實用json格式,我也不是很懂,item pipeline的串行化默認用的就是json。

pipelines.py

這是是用來實現分佈式處理的做用。它將Item存儲在redis中以實現分佈式處理。另外能夠發現,一樣是編寫pipelines,在這裏的編碼實現不一樣於文章中所分析的狀況,因爲在這裏須要讀取配置,因此就用到了from_crawler()函數。

pipeline文件實現了一個item pipieline類,和scrapy的item pipeline是同一個對象,經過從settings中拿到咱們配置的REDIS_ITEMS_KEY做爲key,把item串行化以後存入redis數據庫對應的value中(這個value能夠看出出是個list,咱們的每一個item是這個list中的一個結點),這個pipeline把提取出的item存起來,主要是爲了方便咱們延後處理數據。

scheduler.py

此擴展是對scrapy中自帶的scheduler的替代(在settings的SCHEDULER變量中指出),正是利用此擴展實現crawler的分佈式調度。其利用的數據結構來自於queue中實現的數據結構。

scrapy-redis所實現的兩種分佈式:爬蟲分佈式以及item處理分佈式就是由模塊scheduler和模塊pipelines實現。上述其它模塊做爲爲兩者輔助的功能模塊。

相關文章
相關標籤/搜索