1. 程式人生 > >打造分布式爬蟲

打造分布式爬蟲

讓其 調度器 ide encoding 查看源碼 all art site 優先級隊列

原來scrapy的Scheduler維護的是本機的任務隊列(存放Request對象及其回調函數等信息)+本機的去重隊列(存放訪問過的url地址)

技術分享圖片

所以實現分布式爬取的關鍵就是,找一臺專門的主機上運行一個共享的隊列比如Redis,
然後重寫Scrapy的Scheduler,讓新的Scheduler到共享隊列存取Request,並且去除重復的Request請求,所以總結下來,實現分布式的關鍵就是三點:
#1、共享隊列
#2、重寫Scheduler,讓其無論是去重還是任務都去訪問共享隊列
#3、為Scheduler定制去重規則(利用redis的集合類型)
!!核心功能

技術分享圖片

#安裝:
pip3 install scrapy-redis

scrapy-redis組件

技術分享圖片

# 源碼分析
#一、源碼:D:\python3.6\Lib\site-packages\scrapy_redis\dupefilter.py



#二、配置scrapy使用redis提供的共享去重隊列

#2.1 在settings.py中配置鏈接Redis
REDIS_HOST = localhost                            # 主機名
REDIS_PORT = 6379                                   # 端口
REDIS_URL = redis://user:pass@hostname:9001
# 連接URL(優先於以上配置) REDIS_PARAMS = {} # Redis連接參數 REDIS_PARAMS[redis_cls] = myproject.RedisClient # 指定連接Redis的Python模塊 REDIS_ENCODING = "utf-8" # redis編碼類型 # 默認配置:D:\python3.6\Lib\site-packages\scrapy_redis\defaults.py #2.2 讓scrapy使用共享的去重隊列
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #使用scrapy-redis提供的去重功能,查看源碼會發現是基於Redis的集合實現的 #2.3、需要指定Redis中集合的key名,key=存放不重復Request字符串的集合 DUPEFILTER_KEY = dupefilter:%(timestamp)s #源碼:dupefilter.py內一行代碼key = defaults.DUPEFILTER_KEY % {‘timestamp‘: int(time.time())} #2.4、去重規則源碼分析dupefilter.py def request_seen(self, request): """Returns True if request was already seen. Parameters ---------- request : scrapy.http.Request Returns ------- bool """ fp = self.request_fingerprint(request) # This returns the number of values added, zero if already exists. added = self.server.sadd(self.key, fp) return added == 0 #2.5、將request請求轉成一串字符後再存入集合 from scrapy.http import Request from scrapy.utils.request import request_fingerprint req = Request(url=http://www.baidu.com) result=request_fingerprint(req) print(result) #75d6587d87b3f4f3aa574b33dbd69ceeb9eafe7b #2.6、註意: - URL參數位置不同時,計算結果一致; - 默認請求頭不在計算範圍,include_headers可以設置指定請求頭 - 示範: from scrapy.utils import request from scrapy.http import Request req = Request(url=http://www.baidu.com?name=8&id=1,callback=lambda x:print(x),cookies={k1:vvvvv}) result1 = request.request_fingerprint(req,include_headers=[cookies,]) print(result) req = Request(url=http://www.baidu.com?id=1&name=8,callback=lambda x:print(x),cookies={k1:666}) result2 = request.request_fingerprint(req,include_headers=[cookies,]) print(result1 == result2) #True 使用共享去重隊列+源碼分析

使用scrapy-redis的去重+調度實現分布式爬取

#1、源碼:D:\python3.6\Lib\site-packages\scrapy_redis\scheduler.py


#2、settings.py配置

# Enables scheduling storing requests queue in redis.
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.RFPDupeFilter

持久化

#從目標站點獲取並解析出數據後保存成item對象,會由引擎交給pipeline進行持久化/保存到數據庫,scrapy-redis提供了一個pipeline組件,可以幫我們把item存到redis中
     
#1、將item持久化到redis時,指定key和序列化函數 
REDIS_ITEMS_KEY = %(spider)s:items
REDIS_ITEMS_SERIALIZER = json.dumps
 
#2、使用列表保存item數據

從Redis中獲取起始URL

scrapy程序爬取目標站點,一旦爬取完畢後就結束了,如果目標站點更新內容了,我們想重新爬取,那麽只能再重新啟動scrapy,非常麻煩
scrapy-redis提供了一種供,讓scrapy從redis中獲取起始url,如果沒有scrapy則過一段時間再來取而不會關閉
這樣我們就只需要寫一個簡單的腳本程序,定期往redis隊列裏放入一個起始url。

#具體配置如下

#1、編寫爬蟲時,起始URL從redis的Key中獲取
REDIS_START_URLS_KEY = %(name)s:start_urls
    
#2、獲取起始URL時,去集合中獲取還是去列表中獲取?True,集合;False,列表
REDIS_START_URLS_AS_SET = False    # 獲取起始URL時,如果為True,則使用self.server.spop;如果為False,則使用self.server.lpop

# 完全復制粘貼過來的博客

打造分布式爬蟲