scrapy | scrapy-redis實現分散式爬取:原理,實戰案例(虛擬機器)
1.概念:分散式爬蟲
由於需要爬取的資料量大,任務多,一臺機器效率太低,需要多臺機器共同協作處理。分散式爬蟲將多臺主機組合起來, 共同完成一個爬取任務,快速高效地提高爬取效率。
分散式爬蟲可以分為若干個分散式層級,不同的應用可能由其中部分層級構成。
大型分散式爬蟲主要分為以下3個層級:分散式資料中心、分散式抓取伺服器及分散式爬蟲程式。整個爬蟲系統由全球多個分散式資料中心共同組成,每個資料中心又由多臺高速網路連線的抓取伺服器構成,而每臺伺服器又可以部署多個爬蟲程式。通過多層級的分散式爬蟲體系,才可能保證抓取資料的及時性和全面性。
作為一個分散式爬蟲,是需要有一個Master端(核心伺服器)的,在Master端,會搭建一個數據庫,用來儲存start_urls、request、items。Master的職責是負責url指紋判重,Request的分配,以及資料的儲存(一般在Master端會安裝一個mongodb用來儲存items)。出了Master之外,還有一個角色就是slaver(爬蟲程式執行端),它主要負責執行爬蟲程式爬取資料,並將爬取過程中新的Request提交到Master的資料庫中。
如上圖,假設我們有四臺電腦:A, B, C, D ,其中任意一臺電腦都可以作為 Master端 或 Slaver端。整個流程是:
- 首先Slaver端從Master端拿任務(Request、url)進行資料抓取,Slaver抓取資料的同時,產生新任務的Request便提交給 Master 處理;
- Master端只有一個數據庫,負責將未處理的Request去重和任務分配,將處理後的Request加入待爬佇列,並且儲存爬取的資料。
2.背景
Scrapy 是一個通用的爬蟲框架,但是不支援分散式。有能人改變了scrapy的佇列排程,將起始的網址從start_urls裡分離出來,改為從redis資料庫讀取,多個客戶端可以同時讀取同一個redis,從而實現了分散式的爬蟲。
Redis,非關係型資料庫,Key-Value形式儲存,結構靈活,是記憶體中的資料結構儲存系統,處理速度快,效能好;並且能提供佇列、集合等多種儲存結構,方便佇列維護。
Scrapy-redis是為了更方便地實現Scrapy分散式爬取,而提供了一些以redis為基礎的元件(僅有元件)。通過Scrapy-redis可以快速地實現簡單分散式爬蟲程式,該元件本質上提供了三大功能:
- scheduler --排程器
- dupefilter -- URL去重規則
- item pipeline --資料持久化
3.環境配置
- scrapy
- redis
- scrapy-redis:pip install scrapy-redis
- python 3.6
4.scrapy-redis架構

scrapy-redis在scrapy的架構上增加了redis,基於redis的特性拓展瞭如下四種元件:Scheduler,Duplication Filter,Item Pipeline,Base Spider。
4.1 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元件。
4.2 Duplication Filter
Scrapy中用集合set( )實現這個request去重功能,Scrapy中把已經發送的request指紋放入到一個集合中,把下一個request的指紋拿到集合中比對,如果該指紋存在於集合中,說明這個request傳送過了,如果沒有則繼續操作。
scrapy-redis中去重是由Duplication Filter元件來實現的,它通過redis的set不重複的特性,巧妙的實現了DuplicationFilter去重。scrapy-redis排程器從引擎接受request,將request的指紋存入redis的set檢查是否重複,並將不重複的request push寫入redis的 request queue。引擎請求request(Spider發出的)時,排程器從redis的request queue佇列里根據優先順序pop 出⼀個request 返回給引擎,引擎將此request發給spider處理。
4.3 Item Pipeline
引擎將(Spider返回的)爬取到的Item給Item Pipeline,scrapy-redis 的Item Pipeline將爬取到的 Item 存入redis的 items queue。修改過Item Pipeline可以很方便的根據 key 從 items queue 提取item,從而實現 items processes叢集。
4.4 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。
4.5 總結
這套元件通過重寫scheduler和spider類,實現了排程、spider啟動和redis的互動;
實現新的dupefilter和queue類,達到了判重和排程容器和redis的互動,因為每個主機上的爬蟲程序都訪問同一個redis資料庫,所以排程和判重都統一進行統一管理,達到了分散式爬蟲的目的;當spider被初始化時,同時會初始化一個對應的scheduler物件,這個排程器物件通過讀取settings,配置好自己的排程容器queue和判重工具dupefilter;
每當一個spider產出一個request的時候,scrapy引擎會把這個reuqest遞交給這個spider對應的scheduler物件進行排程,scheduler物件通過訪問redis對request進行判重,如果不重複就把他新增進redis中的排程器佇列裡。當排程條件滿足時,scheduler物件就從redis的排程器佇列中取出一個request傳送給spider,讓他爬取;
當spider爬取的所有暫時可用url之後,scheduler發現這個spider對應的redis的排程器佇列空了,於是觸發訊號spider_idle,spider收到這個訊號之後,直接連線redis讀取strart_url池,拿去新的一批url入口,然後再次重複上邊的工作。
4.6 優缺點
Slaver端從Master端拿任務(Request/url/ID)進行資料抓取,在抓取資料的同時也生成新任務,並將任務拋給Master。Master端只有一個Redis資料庫,負責對Slaver提交的任務進行去重、加入待爬佇列。
優點:scrapy-redis預設使用的就是這種策略,我們實現起來很簡單,因為任務排程等工作scrapy-redis都已經幫我們做好了,我們只需要繼承RedisSpider、指定redis_key就行了。
缺點:scrapy-redis排程的任務是Request物件,裡面資訊量比較大(不僅包含url,還有callback函式、headers等資訊),導致的結果就是會降低爬蟲速度、而且會佔用Redis大量的儲存空間。當然我們可以重寫方法實現排程url。
5.實戰演練
5.1 配置
- 兩臺主機:win7 & 虛擬機器 Ubuntu
- master: Ubuntu (ip:192.168.1.20)
- slave:win 7
tips:redis預設是不能被遠端連線的,需要修改檔案,並重啟伺服器。
#註釋bind
#bind 127.0.0.1
5.2 運作流程描述
- 我的爬蟲專案為blogjobbole
1.master的爬蟲執行時會把提取到的url封裝成request放到redis中的資料庫:“blogjobbole:requests”,並且從該資料庫中提取request後下載網頁,再把網頁的內容存放到redis的另一個數據庫中“blogjobbole:items”
2.slave從master的redis中取出待抓取的request,下載完網頁之後就把網頁的內容傳送回master的redis
3.重複上面的1和2,直到master的redis中的“blogjobbole:requests”資料庫為空,再把master的redis中的“blogjobbole:items”資料庫寫入到mongodb中
4.master裡的reids還有一個數據“blogjobbole:dupefilter”是用來儲存抓取過的url的指紋(使用雜湊函式將url運算後的結果),是防止重複抓取的
5.3安裝部署scrapy-redis
- 第二步:將scrapy-redis資料夾複製到對應的scrapyproject中
5.4 修改scrapyproject 中的spider.py
要繼承redisspider
from scrapy_redis.spiders import RedisSpider
class BlogjobboleSpider(RedisSpider):
name = 'blogjobbole'
allowed_domains = ['blog.jobbole.com']
redis_key = 'blogjobbole:start_urls'#原來spider檔案中的start_urls沒有了
5.5 修改scrapyproject 中的setting.py
master(Ubuntu)中setting.py
# 新增配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True
REDIS_HOST = "localhost" #主機中為localhost
REDIS_PORT = 6379
# 開啟pipeline
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 100,
}
slave(win 7 )中setting.py
# 新增配置
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER_PERSIST = True
REDIS_HOST = "192.168.1.20 " #slave連線到master主機中,ip地址192.168.1.20
REDIS_PORT = 6379
# 開啟pipeline
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 100,
}
5.6 執行爬蟲檔案
1.執行兩臺主機中的爬蟲檔案(啟動爬蟲沒有順序限制)
2.在master(Ubuntu)的終端中輸入
redis-cli lpush blogjobbole:start_urls http://blog.jobbole.com/all-posts/
隨後,兩臺主機上就開始爬取資料了,爬取的資料儲存在master(Ubuntu)中的redis,需要隨後讀取出來儲存到mongdb。
【注意點】:每次重新爬取需要將redis清空。當爬蟲停止後,redis資料庫中只剩下dupefilter 和 items。dupefilter 中儲存的是已經爬取過的url的md5值。如果未清空,爬蟲會過濾掉這些網址,不進行該網址的爬取。因此,如果需要重新爬取,需要將redis清空。
參考來源:
.