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






