日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

scrapy_redis种子优化

發布時間:2024/1/8 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 scrapy_redis种子优化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言:

繼《scrapy_redis去重優化(已有7億條數據),附Demo福利》優化完去重之后,Redis的內存消耗降了許多,然而還不滿足。這次對scrapy_redis的種子隊列作了一些優化(嚴格來說并不能用上“優化”這詞,其實就是結合自己的項目作了一些改進,對本項目能稱作優化,對scrapy_redis未必是個優化)。

scrapy_redis默認是將Request對象序列化后(變成一條字符串)存入Redis作為種子,需要的時候再取出來進行反序列化,還原成一個Request對象。
現在的問題是:序列化后的字符串太長,短則幾百個字符,長則上千。我的爬蟲平時至少也要維護包含幾千萬種子的種子隊列,占用內存在20G~50G之間(Centos)。想要縮減種子的長度,這樣不僅Redis的內存消耗會降低,各個slaver從Redis拿種子的速度也會有所提高,從而整個分布式爬蟲系統的抓取速度也會有所提高(效果視具體情況而定,要看爬蟲主要阻塞在哪里)。



記錄:

1、首先看調度器,即scrapy_redis模塊下的scheduler.py文件,可以看到enqueue_request()方法和next_request()方法就是種子入隊列和出隊列的地方,self.queue指的是我們在setting.py里面設定的SCHEDULER_QUEUE_CLASS值,常用的是'scrapy_redis.queue.SpiderPriorityQueue'。


2、進入scrapy模塊下的queue.py文件,SpiderPriorityQueue類的代碼如下:

class SpiderPriorityQueue(Base):"""Per-spider priority queue abstraction using redis' sorted set"""def __len__(self):"""Return the length of the queue"""return self.server.zcard(self.key)def push(self, request):"""Push a request"""data = self._encode_request(request)pairs = {data: -request.priority}self.server.zadd(self.key, **pairs)def pop(self, timeout=0):"""Pop a requesttimeout not support in this queue class"""pipe = self.server.pipeline()pipe.multi()pipe.zrange(self.key, 0, 0).zremrangebyrank(self.key, 0, 0)results, count = pipe.execute()if results:return self._decode_request(results[0])

可以看到,上面用到了Redis的zset數據結構(它可以給種子加優先級),在進Redis之前用_encode_request()方法將Request對象轉成字符串,_encode_request()和_decode_request是Base類下面的兩個方法:

def _encode_request(self, request):"""Encode a request object"""return pickle.dumps(request_to_dict(request, self.spider), protocol=-1)def _decode_request(self, encoded_request):"""Decode an request previously encoded"""return request_from_dict(pickle.loads(encoded_request), self.spider)

可以看到,這里先將Request對象轉成一個字典,再將字典序列化成一個字符串。Request對象怎么轉成一個字典呢?看下面的代碼,一目了然。

def request_to_dict(request, spider=None):"""Convert Request object to a dict.If a spider is given, it will try to find out the name of the spider methodused in the callback and store that as the callback."""cb = request.callbackif callable(cb):cb = _find_method(spider, cb)eb = request.errbackif callable(eb):eb = _find_method(spider, eb)d = {'url': to_unicode(request.url), # urls should be safe (safe_string_url)'callback': cb,'errback': eb,'method': request.method,'headers': dict(request.headers),'body': request.body,'cookies': request.cookies,'meta': request.meta,'_encoding': request._encoding,'priority': request.priority,'dont_filter': request.dont_filter,}return d


注:d為Request對象轉過來的字典,data為字典序列化后的字符串。


3、了解完scrapy_redis默認的種子處理方式,現在針對自己的項目作一些調整。我的是一個全網爬蟲,每個種子需要記錄的信息主要有兩個:url和callback函數名。此時我們選擇不用序列化,直接用簡單粗暴的方式,將callback函數名和url拼接成一條字符串作為一條種子,這樣種子的長度至少會減少一半。另外我們的種子并不需要設優先級,所以也不用zset了,改用Redis的list。以下是我新建的SpiderSimpleQueue類,加在queue.py中。如果在settings.py里將SCHEDULER_QUEUE_CLASS值設置成'scrapy_redis.queue.SpiderSimpleQueue'即可使用我這種野蠻粗暴的種子。

from scrapy.utils.reqser import request_to_dict, request_from_dict, _find_methodclass SpiderSimpleQueue(Base):""" url + callback """def __len__(self):"""Return the length of the queue"""return self.server.llen(self.key)def push(self, request):"""Push a request"""url = request.urlcb = request.callbackif callable(cb):cb = _find_method(self.spider, cb)data = '%s--%s' % (cb, url)self.server.lpush(self.key, data)def pop(self, timeout=0):"""Pop a request"""if timeout > 0:data = self.server.brpop(self.key, timeout=timeout)if isinstance(data, tuple):data = data[1]else:data = self.server.rpop(self.key)if data:cb, url = data.split('--', 1)try:cb = getattr(self.spider, str(cb))return Request(url=url, callback=cb)except AttributeError:raise ValueError("Method %r not found in: %s" % (cb, self.spider))__all__ = ['SpiderQueue', 'SpiderPriorityQueue', 'SpiderSimpleQueue', 'SpiderStack']



4、另外需要提醒的是,如果scrapy中加了中間件process_request(),當yield一個Request對象的時候,scrapy_redis會直接將它丟進Redis種子隊列,未執行process_requset();需要一個Request對象的時候,scrapy_redis會從Redis隊列中取出種子,此時才會處理process_request()方法,接著去抓取網頁。
所以并不需要擔心process_request()里面添加的Cookie在Redis中放太久會失效,因為進Redis的時候它壓根都還沒執行process_request()。事實上Request對象序列化的時候帶上的字段很多都是沒用的默認字段,很多爬蟲都可以用 “callback+url” 的方式來優化種子。



5、最后,在Scrapy_Redis_Bloomfilter(Github傳送門)這個demo中我已作了修改,大家可以試試效果。


結語:

經過以上優化,Redis的內存消耗從42G降到了27G!里面包含7億多條種子的去重數據 和4000W+條種子。并且六臺子爬蟲的抓取速度都提升了一些。

兩次優化,內存消耗從160G+降到現在的27G,效果也是讓人滿意!




轉載請注明出處,謝謝!(原文鏈接:http://blog.csdn.net/bone_ace/article/details/53306629)

總結

以上是生活随笔為你收集整理的scrapy_redis种子优化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。