原來的scrapy中的Scheduler維護的是當前機器中的任務隊列(存放着Request對象以及回調函數等信息) + 當前的去重隊列(存放訪問過的url地址)
實現分佈式的關鍵就是需要找一臺專門的主機在上面運行一個共享的隊列,比如redis。然後重寫scrapy的Scheduler,讓新的Scheduler到共享的隊列中存取Request,並且去除重複的Request請求
1、共享隊列
2、重寫Scheduler,讓它不論是去重還是執行任務都去訪問共享隊列中的內容
3、爲Scheduler專門定製去重規則(利用redis的集合類型)
# 在scrapy中使用redis的共享去重隊列 # 1、在settings中配置redis鏈接 REDIS_HOST='localhost' # 主機名稱 REDIS_PORT='6379' # 端口號 REDIS_URL='redis://user:[email protected]:9001' # 連接url,優先於上面的配置 REDIS_PARAMS={} # redis連接參數 REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定連接redis的python模塊 REDIS_ENCODING = 'utf-8' # redis的編碼類型 # 2、讓scrapy使用共享的去重隊列 # 使用scrapy_redis提供的去重功能,其實是利用redis的集合來實現的 DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 3、需要指定Redis中集合的Key名稱,Key=存放不重複Request字符串的集合 DUPEFILTER_KEY = 'dupefilter:%(timestamp)s'
# scrapy_redis去重+調度實現分佈式採集 # settings中的配置 SCHEDULER = 'scrapy_redis.scheduler.Scheduler' # 調度器將不重複的任務用pickle序列化後放入共享的任務隊列中,默認是 使用優先級隊列(默認),別的還有PriorityQueue(有序集合),FifoQueue(列表),LifoQueue(列表)。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue' # 對保存到redis中的request對象進行序列化,默認是通過pickle來進行序列化的 SCHEDULER_SERIALIZER = 'scrapy_redis.picklecompat' # 調度器中請求任務序列化後存放在redis中的key SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 是否在關閉時候保留原來的調度器和去重記錄,True=保留,False=清空 SCHEDULER_PERSIST = True # 是否在開始之前清空調度器和去重記錄,True=清空,False=不清空 SCHEDULER_FLUSH_ON_START = False # 去調度器中獲取數據的時候,要是是空的話最多等待的時間(最後沒數據、未獲取到數據)。如果沒有的話就立刻返回會造成空循環次數過多,cpu的佔有率會直線飆升 SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 去重規則,在redis中保存的時候相對應的key SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重規則對應處理的類,將任務request_fingerprint(request)得到的字符串放到去重隊列中 SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.REPDupeFilter'
數據的持久化
# 當從目標站點解析出我們想要的內容以後保存成item對象,就會由引擎交給pipeline來進行數據持久化操作/保存到指定的數據庫中,scrapy_redis提供了一個pipeline組件,可以幫助我們將item存儲到redis中 # 將item持久化保存到redis的時候,指定key和序列化函數 REDIS_ITEMS_KEY = '%(spider)s:items' REDIS_ITEMS_SERIALIZER = 'json.dumps'
# 從redis中獲取起始的URL scrapy程序爬蟲目標站點,一旦爬取完成以後就結束了,萬一目標站點內容更新了,拿着時候我們要是還想在此採集的話,就需要重新啓動這個scrapy項目,這就會變的非常麻煩,scrapy_redis提供了一種讓scrapy項目從redis中獲取起始的url,如果沒有scrapy就會過一段時間再來取而不會直接結束,所以我們只想要寫一個簡單的程序腳本,定期的往redis隊列中放入一個起始的url就可以了 # 編寫腳本的時候,設置起始url從redis中的Key進行獲取 REDIS_START_URLS_KEY = '%(name)s:start_urls' # 獲取起始URL的時候,去集合中獲取呢還是去列表中獲取:True=集合, False=列表 REDIS_START_URLS_AS_SET = False # 獲取起始URL的時候,要是爲True的話,就會使用self.server.spop;False的話就是self.server.lpop