scrapy-分布式爬蟲
一 介紹
原來scrapy的Scheduler維護(hù)的是本機(jī)的任務(wù)隊(duì)列(存放Request對象及其回調(diào)函數(shù)等信息)+本機(jī)的去重隊(duì)列(存放訪問過的url地址)

所以實(shí)現(xiàn)分布式爬取的關(guān)鍵就是,找一臺專門的主機(jī)上運(yùn)行一個(gè)共享的隊(duì)列比如Redis,
然后重寫Scrapy的Scheduler,讓新的Scheduler到共享隊(duì)列存取Request,并且去除重復(fù)的Request請求,所以總結(jié)下來,實(shí)現(xiàn)分布式的關(guān)鍵就是三點(diǎn):
#1、共享隊(duì)列 #2、重寫Scheduler,讓其無論是去重還是任務(wù)都去訪問共享隊(duì)列 #3、為Scheduler定制去重規(guī)則(利用redis的集合類型)
以上三點(diǎn)便是scrapy-redis組件的核心功能

#安裝: pip3 install scrapy-redis #源碼: D:\python3.6\Lib\site-packages\scrapy_redis
二、scrapy-redis組件
1、只使用scrapy-redis的去重功能
#一、源碼:D:\python3.6\Lib\site-packages\scrapy_redis\dupefilter.py #二、配置scrapy使用redis提供的共享去重隊(duì)列 #2.1 在settings.py中配置鏈接Redis REDIS_HOST = 'localhost' # 主機(jī)名 REDIS_PORT = 6379 # 端口 REDIS_URL = 'redis://user:pass@hostname:9001' # 連接URL(優(yōu)先于以上配置) REDIS_PARAMS = {} # Redis連接參數(shù) REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定連接Redis的Python模塊 REDIS_ENCODING = "utf-8" # redis編碼類型 # 默認(rèn)配置:D:\python3.6\Lib\site-packages\scrapy_redis\defaults.py #2.2 讓scrapy使用共享的去重隊(duì)列 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #使用scrapy-redis提供的去重功能,查看源碼會發(fā)現(xiàn)是基于Redis的集合實(shí)現(xiàn)的 #2.3、需要指定Redis中集合的key名,key=存放不重復(fù)Request字符串的集合 DUPEFILTER_KEY = 'dupefilter:%(timestamp)s' #源碼:dupefilter.py內(nèi)一行代碼key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())} #2.4、去重規(guī)則源碼分析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請求轉(zhuǎn)成一串字符后再存入集合 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參數(shù)位置不同時(shí),計(jì)算結(jié)果一致; - 默認(rèn)請求頭不在計(jì)算范圍,include_headers可以設(shè)置指定請求頭 - 示范: 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```
2、使用scrapy-redis的去重+調(diào)度實(shí)現(xiàn)分布式爬取
#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" # 調(diào)度器將不重復(fù)的任務(wù)用pickle序列化后放入共享任務(wù)隊(duì)列,默認(rèn)使用優(yōu)先級隊(duì)列(默認(rèn)),其他:PriorityQueue(有序集合),F(xiàn)ifoQueue(列表)、LifoQueue(列表) SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue' # 對保存到redis中的request對象進(jìn)行序列化,默認(rèn)使用pickle SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat" # 調(diào)度器中請求任務(wù)序列化后存放在redis中的key SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 是否在關(guān)閉時(shí)候保留原來的調(diào)度器和去重記錄,True=保留,F(xiàn)alse=清空 SCHEDULER_PERSIST = True # 是否在開始之前清空 調(diào)度器和去重記錄,True=清空,F(xiàn)alse=不清空 SCHEDULER_FLUSH_ON_START = False # 去調(diào)度器中獲取數(shù)據(jù)時(shí),如果為空,最多等待時(shí)間(最后沒數(shù)據(jù),未獲取到)。如果沒有則立刻返回會造成空循環(huán)次數(shù)過多,cpu占用率飆升 SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 去重規(guī)則,在redis中保存時(shí)對應(yīng)的key SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重規(guī)則對應(yīng)處理的類,將任務(wù)request_fingerprint(request)得到的字符串放入去重隊(duì)列 SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
3、持久化
#從目標(biāo)站點(diǎn)獲取并解析出數(shù)據(jù)后保存成item對象,會由引擎交給pipeline進(jìn)行持久化/保存到數(shù)據(jù)庫,scrapy-redis提供了一個(gè)pipeline組件,可以幫我們把item存到redis中 #1、將item持久化到redis時(shí),指定key和序列化函數(shù) REDIS_ITEMS_KEY = '%(spider)s:items' REDIS_ITEMS_SERIALIZER = 'json.dumps' #2、使用列表保存item數(shù)據(jù)
4、從Redis中獲取起始URL
scrapy程序爬取目標(biāo)站點(diǎn),一旦爬取完畢后就結(jié)束了,如果目標(biāo)站點(diǎn)更新內(nèi)容了,我們想重新爬取,那么只能再重新啟動scrapy,非常麻煩 scrapy-redis提供了一種供,讓scrapy從redis中獲取起始url,如果沒有scrapy則過一段時(shí)間再來取而不會關(guān)閉 這樣我們就只需要寫一個(gè)簡單的腳本程序,定期往redis隊(duì)列里放入一個(gè)起始url。 #具體配置如下 #1、編寫爬蟲時(shí),起始URL從redis的Key中獲取 REDIS_START_URLS_KEY = '%(name)s:start_urls' #2、獲取起始URL時(shí),去集合中獲取還是去列表中獲取?True,集合;False,列表 REDIS_START_URLS_AS_SET = False # 獲取起始URL時(shí),如果為True,則使用self.server.spop;如果為False,則使用self.server.lpop

浙公網(wǎng)安備 33010602011771號