scrapy是一個爬蟲框架,但不支持分佈式,scrapy-redis是爲了更方便的實現scrapy分佈式爬蟲的組件。python
能夠在pypi上找到:https://pypi.org/project/scrapy-redis/mysql
可使用pip安裝redis
pip install scrapy-redis算法
pip show scrapy-redissql
目前最新版是0.6.8。mongodb
Scrapy-redis提供了下面四種組件(components):(意味着原始scrapy爬蟲這四個部分都要作相應的修改)數據庫
Schedulercentos
Duplication Filter服務器
Item Pipeline網絡
Base Spider
先無論那麼多,先跑一個案例;
主:虛擬機 centos6.5
從:物理機win8
見筆記-redis安裝
centos下已有python環境,安裝參考文檔:筆記-python3環境安裝-centos6.5
安裝相關包:
pip3 install redis,scrapy,scrapy-redis,lxml
包括redis-py,scrapy-redis,
經過pip安裝就能夠了,比較簡單。
與scrapy爬蟲代碼大同小異,主要是spider類和settings中設置調度器,去重功能:
徹底同樣;
spider類的基類改成RedisSpider
from scrapy_redis.spiders import RedisSpider
註釋掉start_urls。
新增屬性:
redis_key = ‘sinanewsspider:start_urls’
這個屬性是給redis中建組用的,:做爲組名和key名的間隔。
須要設置如下內容:
#使用scrapy_redis調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
#使用scrapy_redis的去重處理器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
#不清理Redis隊列
SCHEDULER_PERSIST = True
若是這一項爲True,那麼在Redis中的URL不會被Scrapy_redis清理掉,這樣的好處是:爬蟲中止了再從新啓動,它會從上次暫停的地方開始繼續爬取。可是它的弊端也很明顯,若是有多個爬蟲都要從這裏讀取URL,須要另外寫一段代碼來防止重複爬取。
若是設置成了False,那麼Scrapy_redis每一次讀取了URL之後,就會把這個URL給刪除。這樣的好處是:多個服務器的爬蟲不會拿到同一個URL,也就不會重複爬取。但弊端是:爬蟲暫停之後再從新啓動,它會從新開始爬。
#redis服務器地址,主機寫本地,從機寫遠程IP
REDIS_HOST = "192.168.199.129"
#redis端口
REDIS_PORT = 6379
其餘設置(可選)
爬蟲請求的調度算法
爬蟲的請求調度算法,有三種狀況可供選擇:
1.隊列
SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.SpiderQueue'
若是不配置調度算法,默認就會使用這種方式。它實現了一個先入先出的隊列,先放進Redis的請求會優先爬取。
2.棧
SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.SpiderStack'
這種方式,後放入到Redis的請求會優先爬取。
3.優先級隊列
SCHEDULER_QUEUE_CLASS='scrapy_redis.queue.SpiderPriorityQueue'
這種方式,會根據一個優先級算法來計算哪些請求先爬取,哪些請求後爬取。這個優先級算法比較複雜,會綜合考慮請求的深度等各個因素。
原本就是一個分離的組件,想改就改,不改也沒問題。
scrapy-redis自帶的pipeline是將items寫入redis數據庫中的items中。
前面聲明的redis_key = ‘sinanewsspider:start_urls’
提供了組名,完整的key名爲sinanewsspider:items
由於都要去redis數據庫的sina_news:start_urls中取連接,但如今沒有該KEY,因此都在等待;
lpush sinanewsspider:start_urls http://news.sina.com.cn/guide/
scrapy-redis在數據庫中新增三個key,dupefilter,items,requests:
我的理解:爲了實現分佈式爬蟲,須要一個跨平臺的信息傳遞,目前是經過redis的遠程訪問知足這一點,至於爲何是redis而不是其它數據庫或中間件那是另一個問題了;
下一個問題是傳遞什麼信息,最簡單也是數據量最小的是傳遞url地址,但這樣功能不夠豐富,scrapy-redis放進去的是requests;
總之具體實現就是scrapy-redis把請求放到redis數據庫中,爬蟲去數據庫中拿到請求,爬取,再把結果放到items中。
關於dupefilter,它是用於去重,看上去是hash的結果,相似於指紋。
由於官方文檔沒什麼內容,下面的內容取自網絡及我的理解。
從爬蟲實現的過程來說,爬蟲分佈式最容易實現的方式是共享請求,也就是「待爬隊列」;
從爬蟲總體的合理設計來說,爬蟲要作的事就是獲得請求,去重,採集,存儲數據四部分,下面一一解釋scrapy-redis的實現方法。
怎麼發起就不廢話了,scrapy是從爬取隊列中獲取請求的,它具體的數據結構就是python自帶的collection.deque,固然是改造事後的啦(後面所說到的deque均是指scrapy改造以後的隊列)。
scrapy-redis提供了一個分佈式解決方法,把deque換成redis數據庫,在同一個redis服務器寫/讀要爬取的request,這樣就讓多個spider共享了請求。
問題來了,換了redis來存放隊列,怎麼去發起/獲取請求?
scrapy中作這個事的是調度器「Scheduler」,它負責對新request入列(加入deque),取出待爬取的request(從deque中出殯)等操做。
另外,在scrapy中,Scheduler爲deque提供了一個比較高級的組織方法,它把待爬隊列按照優先級創建了一個字典結構,好比:
{
priority0:隊列0
priority1:隊列2
priority2:隊列2
}
而後根據request中的priority屬性,來決定該入哪一個隊列。而出列時,則按priority較小的優先出列。爲了管理這個比較高級的隊列字典,Scheduler須要提供一系列的方法。
這樣作的結果就是若是換了redis作隊列,scrapy下的Scheduler就用不了,須要重寫。
待爬隊列+調度器解決了,分佈式爬蟲也就基本能夠運行了。
2.去重
爬蟲有一個重要的功能是去重,scrapy中用集合解決;scrapy-redis用dupefilter存放請求的指紋,在進行調度前作對比。
3.採集
請求的格式與接口不變,這一部分也不須要變化,與scrapy沒什麼不一樣。
4數據存儲
分佈式爬取帶來的一個問題是數據在不一樣的主機上,那麼理論上有兩種方法:
實際中 通常使用第二種方法,另備一臺mongodb/mysql服務器,用於採集數據存儲。
scrapy-redis的RedisPipeline將數據存入master的redis數據庫中。
scrapy-redis官方文檔內容有限,要想理解其實現過程,仍是得看看源碼。
鏈接redis
有一個問題是沒有提供password參數的鏈接模式
在源碼中對於這一部分的處理以下:
url = kwargs.pop('url', None)
if url:
return redis_cls.from_url(url, **kwargs)
else:
return redis_cls(**kwargs)
它仍是調用redis模塊的函數去鏈接,能夠參照redis模塊中的格式redis://[:password]@localhost:6379/0
注意,在settings中的變量名須要作一個轉換:REDIS_URL
主要是將內容推動數據庫的items中,用於解析結果的共享,通常能夠寫入其它服務器數據庫,減小master的壓力。
# 最主要的方法
def process_item(self, item, spider):
# 調用了一個線程方法
return deferToThread(self._process_item, item, spider)
def _process_item(self, item, spider):
key = self.item_key(item, spider)
# 序列化item
data = self.serialize(item)
# 將item贊成添加到redis隊列裏面, 存到主機
self.server.rpush(key, data)
return item
實現了三種隊列,
SpiderQueue = FifoQueue
SpiderStack = LifoQueue
SpiderPriorityQueue = PriorityQueue
LIFO,FIFO很好看懂,優先級隊列的實現沒看懂,調用redis的函數,須要詳細去看redis接口代碼。
主要是實現了判重,根據源代碼來看,scrapy-redis在計算特徵碼時使用了scrapy自己的一個fingerprint接request_fingerprint。
特徵碼保存在redis的組名:dupefilter中。
爬取隊列管理,核心部分以下:
def enqueue_request(self, request):
if not request.dont_filter and self.df.request_seen(request):
self.df.log(request, self.spider)
return False
if self.stats:
self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider)
self.queue.push(request)
return True
def next_request(self):
block_pop_timeout = self.idle_before_close
request = self.queue.pop(block_pop_timeout)
if request and self.stats:
self.stats.inc_value('scheduler/dequeued/redis', spider=self.spider)
return request
不在使用scrapy原有的Spider類,重寫的RedisSpider繼承了Spider和RedisMixin這兩個類,RedisMixin是用來從redis讀取url的類。