scrapy是一個python爬蟲框架,爬取的效率極高,具有高度的定製性,但是不支持分佈式。而scrapy-redis是一套基於redis庫,運行在scrapy框架之上的組件,可以讓scapy支持分佈式策略
Slaver端共享Master端redis數據庫裏的item 隊列、請求隊列和請求指紋集合。
選擇redis數據庫的原因:
redis支持主從同步,而且數據都是緩存在內存中的,所以基於redis的分佈式爬蟲,對請求和數據的高頻率讀取效率都非常高
scrapy-redis和scrapy的關係就像電腦和固態硬盤一樣,是電腦中的一個插件,能讓電腦更快的運行
scrapy是一個爬蟲框架,scrapy-redis則是這個框架上可以選擇的插件,它可以讓爬蟲跑得更
解釋說明:
- 從優先級隊列中獲取requests對象,交給engine
- engine將requests對此昂交給下載器下載,期間會通過downlomiddleware的process_request方法
- 下載器完成下載,獲得response對象,將該對象交給engine,期間會經過downloadmiddleware的process_response()方法
- engine將獲得的response對象交給spider進行解析,期間會經過spidermiddleware的process_spider_input()方法
- spider解析下載器下載下來的response,返回item或links(url)
- item或者link經過spidermiddleware的process_spider_out()方法,交給engine
- engine將item交給item pipeline,將links交給調度器
- 在調度器中,先將requests對象利用scrapy內置的指紋函數生成一個指紋
- 如果requests對象中的don't filter參數設置爲False,並且該requests對象的指紋不在信息指紋的隊列中,那麼就把該requests對象放到優先級隊列中
中間件:
spider與engine之間(爬蟲中間件)
介於scrapy引擎和爬蟲之間的框架,主要工作就是處理爬蟲的響應輸入和請求的輸出
download與engine之間(下載器中間件)
介於scrapy引擎和下載器之間的框架,主要是處理scrapy引擎與下載器之間的請求和響應
scrapy框架中的middleware.py
- Scrapy Middleware有以下幾個函數被管理
- process_spider_input:接收一個response對象並處理
- process_spider_exception:spider出現異常時被調用
- process_spider_output:當spider處理response返回result時,就會調用該方法
- process_spider_requests:當spider發出請求時,被調用
- Download Middleware有以下幾個函數被管理
- process_requests:requests通過下載中間件的時候,該方法被調用,這裏可以通過設置代理,設置request.meta['proxy']就OK了
- process_response:下載結果經過中間件的時候會被這個方法解惑來進行處理
- process_exception:下載過程中出現異常的時候會被調用
Scrapy的優缺點:
- 優點
- scrapy是異步處理的,寫middleware,方便寫一些統一的過濾器
- 缺點
- 基於python的爬蟲框架,擴展性比較差,基於twisted框架,運行中的exception是不會幹掉reactor,並且異步框架出錯後是不會停掉其他任務的,數據出錯後難以察覺
Scrapy-Redis提供了四種組件(四種組件也就意味着這四個模塊都需要做出相應的改動):
- Scheduler
- scrapy改變了python原本的collection.deque(雙向隊列)形成了自己的Scrapy queue,但是Scrapy多個spider不能共享爬取隊列Scrapy queue,也就是Scrapy本身不支持爬蟲的分佈式,scrapy-redis的解決是把這個Scrapy queue換成了redis數據庫(也是指redis隊列),從同一個redis-server存放要爬取的request,就可以讓多個爬蟲去同一個數據庫裏讀取了
- 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不重複的特性,巧妙的實現了Duplicating Filter去重。scrapy-redis調度器從引擎接受request,將request的指紋存入redis的set檢查是否產生了重複,並將不重複的request push寫入redis的request queue
- 引擎請求request時,調度器從redis的request queue隊列里根據優先級進行pop出一個request返回給engine,engine將這個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函數,這個函數調用scheduler_next_request函數,保證spider是一直活着的狀態,並且拋出DontCloseSpider異常,還有一個就是當抓到一個item的signal,會調用item_scrapy函數,這個函數會調用scheduler_next_request函數,獲取下一個request。
Scrapy-Redis分佈式策略:
首先要說一下Master端和Slaver端
- Master
- 核心服務器,搭建Redis數據庫,不負責爬取,只負責url指紋判斷是否重複、request的分配、以及數據的存儲
- Slaver
- 爬蟲程序執行端,負責執行爬蟲程序,運行過程中提交新的request給Master
- Master
首先Slaver端從Master端拿任務(request, url)進行數據爬取,Slaver抓取數據的同時,產生新的request就提交給Master進行處理
Master端只有一個Redis數據庫,負責將未處理的request去重和任務分配,將處理後的request加入待爬取的隊列,並且存儲爬取的數據
將scrapy變成scrapy-redis的過程(前提是已經安裝好了scrapy-redis)
- 修改settings.py配置文件,最簡單的方式就是使用redis來替換當前電腦的內存,並且同時配置好redis數據庫相關的內容
# 這個是需要手動加上的,通過scrapy-redis自帶的pipeline將item存入redis中 ITEM_PIPELINES = {'scrapy_redis.pipelines.RedisPipeline': 400 } # 啓動redis自帶的去重 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 啓用調度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 是否在關閉spider的時候保存記錄 SCHEDULER_PERSIST = True # 使用優先級調度請求隊列(默認使用) SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue' # 指定redis的地址和端口,有密碼的需要加上密碼 REDIS_HOST = '127.0.0.1' REDIS_PORT = '6379'
# 如果你的redis設了密碼就需要加上密碼, REDIS_PARAMS = { 'password': '123456', }