假設需求
現在大概有三千個scrapy的爬蟲檔案,10臺機器,如何合理的分配爬蟲?什么,這么簡單的數學題還要問,一臺機器分300個爬蟲不就行了,確實,這樣分配最簡單也最直接,但會帶來一些問題,就比如,有些站點網頁少而有些網站很大,每個爬蟲運行的時間是不一樣的,最后可能導致一臺累死,九臺圍觀,而且一臺機器同時運行300個爬蟲,在硬體資源上的消耗會很大,也可能會導致很多爬蟲無法正常運行,所以即使是這樣分配爬蟲我們也要限制同時運行的爬蟲數量,當某個爬蟲運行完了,才執行下一個,
解決方法
可以先創建出一個佇列,佇列里存放待抓取的爬蟲(通常會創建三個,分別是pending(待抓取)、running(正抓取)、finished(已抓取)),然后每臺機器開始都取指定數量的爬蟲運行,當其中一個運行完,在去任務佇列里取,直到佇列空了,
實作
這種數量不多的佇列使用Redis的集合就行,我們創建pending、running、finished三個集合,然后將所有的爬蟲的name欄位存放在pending集合中(當然爬蟲檔案名也可以,只是啟動爬蟲的方式稍微不一樣),接著我們就可以寫個腳本來運行爬蟲了,
方式有兩種:
一、crawl命令
偽代碼:
pending.add(所有爬蟲)
while True:
if len(running) < 指定數量:
spider = pending.pop()
多行程執行:f'scrapy crawl {spider}'
else:
time.sleep(指定時間)
然后只要撰寫一個extension來同步爬蟲狀態到Redis里
class SpiderCountLimit:
def __init__(self, count):
self.spider_count = count
self.r = redis.Redis(decode_responses=True)
@classmethod
def from_crawler(cls, crawler):
count = crawler.settings.get('SPIDER_COUNT', 20)
ext = cls(count)
crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
return ext
def spider_closed(self, spider, reason):
self.r.srem('running', spider.name) # 爬蟲關閉時,洗掉running中的爬蟲
self.r.sadd('finished', spider.name) # 加入到已完成佇列
def spider_opened(self, spider):
self.r.sadd('running', spider.name) # 添加爬蟲到running中
這種方法就不多說了,因為我沒嘗試,直接看第二種
二、Crawler API
偽代碼:(不懂怎么用的Crawler API可以看scrapy自定義命令)
for i in range(指定數量):
crawler_process.crawl(pending.pop())
crawler_process.start()
while True:
if len(running) < 指定數量:
多行程執行:
crawler_process.crawl(pending.pop())
crawler_process.start()
else:
time.sleep(指定時間)
因為crawler_process.start()這個陳述句是阻塞的,所以需要多行程來執行,也可以去掉多行程,把extension的內容改成這樣:
class SpiderCountLimit:
def __init__(self, count):
self.spider_count = count
self.r = redis.Redis(decode_responses=True)
@classmethod
def from_crawler(cls, crawler):
count = crawler.settings.get('SPIDER_COUNT', 20)
ext = cls(count)
crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
return ext
def spider_closed(self, spider, reason):
self.r.srem('running', spider.name)
self.r.sadd('finished', spider.name)
spider = self.r.spop('pending')
process = CrawlerProcess()
process.crawl(spider)
process.start()
def spider_opened(self, spider):
self.r.sadd('running', spider.name)
不過我感覺這個方法不如多行程添加,因為上面提到process.start() 是阻塞的,也就是說spider_closed這個方法一直沒有結束,這可能會帶來一些無法預見的問題,
至于其他一些細節上的優化就自己思考了,比如改用行程池來管理行程等,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/144264.html
標籤:Python
