近在工作中寫了很多 scrapy_redis 分布式爬蟲,但是回想 scrapy 與 scrapy_redis 兩者區別的時候,竟然,思維只是局限在了應用方面,于是乎,搜索了很多相關文章介紹,這才搞懂內部實現的原理。
首先我們從整體上來講
scrapy是一個Python爬蟲框架,爬取效率極高,具有高度定制性,但是不支持分布式。而scrapy-redis一套基于redis數據庫、運行在scrapy框架之上的組件,可以讓scrapy支持分布式策略,Slaver端共享Master端redis數據庫里的item隊列、請求隊列和請求指紋集合。而為什么選擇redis數據庫,是因為redis支持主從同步,而且數據都是緩存在內存中的,所以基于redis的分布式爬蟲,對請求和數據的高頻讀取效率非常高。
有一篇文章是這么說的: scrapy-redis 與 Scrapy 的關系就像電腦與固態硬盤一樣,是電腦中的一個插件,能讓電腦更快的運行。
Scrapy 是一個爬蟲框架, scrapy-redis 則是這個框架上可以選擇的插件,它可以讓爬蟲跑的更快。
說的一點都對, Scrapy 是一個通用的爬蟲框架, scrapy-redis 則是這個框架上可以選擇的插件,為了更方便地實現Scrapy分布式爬取,而提供了一些以redis為基礎的組件(僅有組件),它可以讓爬蟲跑的更快。
然后介紹 scrapy 框架的運行流程及原理
scrapy作為一款優秀的爬蟲框架,在爬蟲方面有這眾多的優點。能快速、高層次的屏幕抓取和web抓取框架,用于抓取web站點并從頁面中提取結構化的數據。
為了方便理解,我找到了一張這樣的圖片:
解釋說明:
1、從優先級隊列中獲取request對象,交給engine
2、engine將request對象交給下載器下載,期間會通過downloadmiddleware的process_request方法
3、下載器完成下載,獲得response對象,將該對象交給engine,期間會經過downloadmiddleware的process_response( )方法
4、engine將獲得的response對象交給spider進行解析,期間會經過spidermiddleware的process_spider_input()的方法
5、spider解析下載器下下來的response,返回item或是links(url)
6、item或者link經過spidermiddleware的process_spider_out( )方法,交給engine
7、engine將item交給item pipeline ,將links交給調度器
8、在調度器中,先將requests對象利用scrapy內置的指紋函數生成一個指紋
9、如果requests對象中的don't filter參數設置為False,并且該requests對象的指紋不在信息指紋的隊列中,那么就把該request對象放到優先級隊列中
循環以上操作
中間件主要存在兩個地方,從圖片當中我們可以看到:
spider 與 engine 之間(爬蟲中間件):
介于Scrapy引擎和爬蟲之間的框架,主要工作是處理爬蟲的響應輸入和請求輸出
download 與 engine 之間(下載器中間件) :
位于Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應
借此機會,我們結合程序,解析一下框架中的 middleware.py :
1. Spider Middleware有以下幾個函數被管理:
- process_spider_input 接收一個response對象并處理,
位置是Downloader-->process_spider_input-->Spiders(Downloader和Spiders是scrapy官方結構圖中的組件)
- process_spider_exception spider出現的異常時被調用
- process_spider_output 當Spider處理response返回result時,該方法被調用
- process_start_requests 當spider發出請求時,被調用
2. Downloader Middleware有以下幾個函數被管理
- process_request request通過下載中間件時,該方法被調用,這里可以設置代理,設置request.meta['proxy'] 就行
- process_response 下載結果經過中間件時被此方法處理
- process_exception 下載過程中出現異常時被調用
個人理解scrapy的優缺點:
優點:scrapy 是異步的, 寫middleware,方便寫一些統一的過濾器
缺點:基于python的爬蟲框架,擴展性比較差, 基于twisted框架,運行中的exception是不會干掉reactor,并且異步框架出錯后是不會停掉其他任務的,數據出錯后難以察覺。
scrapy_redis分布式爬蟲
最后回到我們這篇文章的重點(敲黑板...)
Scrapy-redis提供了下面四種組件(components):(四種組件意味著這四個模塊都要做相應的修改)
Scheduler:
Scrapy改造了python本來的collection.deque(雙向隊列)形成了自己的Scrapy queue,但是Scrapy多個spider不能共享待爬取隊列Scrapy queue, 即Scrapy本身不支持爬蟲分布式,scrapy-redis 的解決是把這個Scrapy queue換成redis數據庫(也是指redis隊列),從同一個redis-server存放要爬取的request,便能讓多個spider去同一個數據庫里讀取。
Scrapy中跟“待爬隊列”直接相關的就是調度器Scheduler,它負責對新的request進行入列操作(加入Scrapy queue),取出下一個要爬取的request(從Scrapy queue中取出)等操作。它把待爬隊列按照優先級建立了一個字典結構,然后根據request中的優先級,來決定該入哪個隊列,出列時則按優先級較小的優先出列。為了管理這個比較高級的隊列字典,Scheduler需要提供一系列的方法。但是原來的Scheduler已經無法使用,所以使用Scrapy-redis的scheduler組件。
Duplication Filter:
Scrapy中用集合實現這個request去重功能,Scrapy中把已經發送的request指紋放入到一個集合中,把下一個request的指紋拿到集合中比對,如果該指紋存在于集合中,說明這個request發送過了,如果沒有則繼續操作。
在scrapy-redis中去重是由Duplication Filter組件來實現的,它通過redis的set 不重復的特性,巧妙的實現了Duplication Filter去重。scrapy-redis調度器從引擎接受request,將request的指紋存?redis的set檢查是否重復,并將不重復的request push寫?redis的 request queue。
引擎請求request(Spider發出的)時,調度器從redis的request queue隊列?里根據優先級pop 出?個request 返回給引擎,引擎將此request發給spider處理。
Item Pipeline:
引擎將(Spider返回的)爬取到的Item給Item Pipeline,scrapy-redis 的Item Pipeline將爬取到的 Item 存?redis的 items queue。
修改過Item Pipeline可以很方便的根據 key 從 items queue 提取item,從?實現items processes集 群。
Base Spider:
不在使用scrapy原有的Spider類,重寫的RedisSpider繼承了Spider和RedisMixin這兩個類,RedisMixin是用來從redis讀取url的類。
當我們生成一個Spider繼承RedisSpider時,調用setup_redis函數,這個函數會去連接redis數據庫,然后會設置signals(信號):
一個是當spider空閑時候的signal,會調用spider_idle函數,這個函數調用schedule_next_request函數,保證spider是一直活著的狀態,并且拋出DontCloseSpider異常。
一個是當抓到一個item時的signal,會調用item_scraped函數,這個函數會調用schedule_next_request函數,獲取下一個request。
Scrapy-redis架構:
如上圖所示,我們可以發現,scrapy-redis在scrapy的架構上增加了redis,與scrapy相差無幾。本質的區別就是,將scrapy的內置的去重的隊列和待抓取的request隊列換成了redis的集合。就這一個小小的改動,就使得了scrapy-redis支持了分布式抓取。
Scrapy-Redis分布式策略:
假設有四臺電腦:windows 10、mac OS X、Ubuntu 16.04、centos 7.2,任意一臺電腦都可以作為 Master端 或 Slaver端,比如:
--Master端(核心服務器) :使用 Windows 10,搭建一個Redis數據庫,不負責爬取,只負責url指紋判重、Request的分配,以及數據的存儲
--Slaver端(爬蟲程序執行端) :使用 Mac OS X 、Ubuntu 16.04、CentOS 7.2,負責執行爬蟲程序,運行過程中提交新的Request給Master
首先Slaver端從Master端拿任務(Request、url)進行數據抓取,Slaver抓取數據的同時,產生新任務的Request便提交給 Master 處理;
Master端只有一個Redis數據庫,負責將未處理的Request去重和任務分配,將處理后的Request加入待爬隊列,并且存儲爬取的數據。
明白了原理之后我們就要入手程序了
Scrapy-Redis默認使用的就是這種策略,我們實現起來很簡單,因為任務調度等工作Scrapy-Redis都已經幫我們做好了,我們只需要繼承RedisSpider、指定redis_key就行了。
將 scrapy 變成 scrapy-redis 的過程(前提是pip install scrapy-redis)
最簡單的方式是使用 redis 替換機器內存,你只需要在 settings.py 中加上三代碼,就能讓你的爬蟲變為分布式。
如果你現在運行你的爬蟲,你可以在redis中看到出現了這兩個key:
格式是set,即不會有重復數據。前者就是redis的去重隊列,對應 DUPEFILTER_CLASS ,后者是redis的請求調度,把里面的請求分發給爬蟲,對應 SCHEDULER 。(里面的數據不會自動刪除,如果你第二次跑,需要提前清空里面的數據)
缺點是,Scrapy-Redis調度的任務是Request對象,里面信息量比較大(不僅包含url,還有callback函數、headers等信息),可能導致的結果就是會降低爬蟲速度、而且會占用Redis大量的存儲空間,所以如果要保證效率,那么就需要一定硬件水平。