scrapy_redis种子优化

article/2025/8/30 6:05:59

前言:

继《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

scrapy_redis种子
注: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)


http://chatgpt.dhexx.cn/article/8FhaiiHh.shtml

相关文章

总结一下Python的模块加载解析

正式的Python专栏第16篇,同学站住,别错过这个从0开始的文章! 学委之前分享了很多代码,也发布了几个库。 新手可以从两篇基础文章入手学习,积累了一些代码,可以考虑做成一个个模块,包装代码有利…

C++ 函数(模块化编程)

目录 函数定义 函数 函数定义的语法形式 函数调用 调用函数需要先声明函数原型 函数调用形式 嵌套调用 例3-1编写一个求x的n次方的函数 例3-2 数制转换 源代码: 例3-3 编写程序求π的值 arctan函数 主程序 例3-4 寻找并输出11~999之间的数M&#xf…

单片机常开常闭检测

单片机检测常开常闭点(干接点信号)的方法:检测干接点的闭合和断开,从而单片机实施一定的操作,它具有两根线,具体步骤:1、引出单片机一个IO口线,连接干接点一端。2、干接点另一端通过…

干海星怎么吃做法 干海星的功效与作用有哪些

干海星是生活中并不常见的一种海鲜类食材,在很多人眼中干海星都是一种中药材,其实它也可以当做一种菜品来食用,那么干海星应该怎么吃呢?接下来我就把自己总结的干海星吃法大全写出来分享给大家。 干海星怎么吃 干海星的功效与作用…

大前端干货资料

JavaScript 文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference ES6 入门教程:https://es6.ruanyifeng.com/ Vue 官网:https://cn.vuejs.org router 官网:https://router.vuejs.org/zh UI框架 Ele…

干簧管(磁开关)

其实干簧管的工作原理并不复杂 干簧管的特点: 干簧管是干式舌簧管的简称,是一种有触点的无源电子开关元件,具有结构简单,体积小便于控制等优点,其外壳一般是一根密封的玻璃管,管中装有两个铁质的弹性簧片…

苹果手机位置共享后没开启定位服务器,iPhone默认开启但无卵用的设置开着干嘛?难怪手机这么耗电...

原标题:iPhone默认开启但无卵用的设置开着干嘛?难怪手机这么耗电 如果你的苹果手机开启了定位功能,那么在iPhone【设置】-【隐私】-【定位服务】-系统服务】,点击进入后有很多项开关,而很多都是在打开定位服务后默认开…

adams打不开提示msc license_adams安装后打不开

虽然要支持正版……但是 安装后打不开,借鉴了adams2017安装教程后台留言 图片发自简书App 1.首先我这么做是因为其实在安装过程中就有一次没找到路径 。所以该方法仅供参考。 图片发自简书App 此时我直接在“此电脑”内搜索“MSC.Software”了 图片发自简书App 耐心…

使用Arduino开发板连接干簧管(Reed Switch)的方法

topic 1.干簧管(Reed Switch)干簧管的特点:模块使用说明:干簧管有两种常见的类型:干簧管常见的应用需要的组件代码 1.干簧管(Reed Switch) 干簧管,又称磁力开关或磁簧开关&#xff0…

查看电脑使用日志------电脑干了些什么,别人是否动了你的电脑

** 现在我们都有了电脑,有时候短暂的离开可能会有人偷偷的查看你的电脑,有意无意的,总是会感觉心里不是很踏实,那么怎么知道自己的电脑有没有被别人偷偷的查看呢,下面我们通过一些列操作查看电脑是否被别人动过&#x…

高版本envi打不开Landsat8 C2L2数据解决教程

引申部分不写,直接开干 首先找到你的Landsat8 C2L2数据 进入后将首行GROUP LANDSAT_METADATA_FILE改为GROUP L1_METADATA_FILE 编辑—查找—GROUP LEVEL1_PROCESSING_RECORD,查找下一处可得该区域总和 将 GROUP LEVEL1_PROCESSING_RECORD 到 END_…

【错误记录】Melodyne 报错 ( 无法打开音频文件 )

文章目录 一、报错信息二、修改音源格式三、继续使用 Melodyne 打开 一、报错信息 打开一个 48000Hz , 立体声音源 , 采样位数 32 位的采样 , 无法打开 , 报下面的错误 ; 这是用 Bandicam 录制的音频 , 感觉有点问题 , 之后使用 Au 录制音频 ; 二、修改音源格式 在 Adobe Audit…

带你了解测开

测开——测试开发工程师 测试开发工程师 (Software Development Engineer in Test,简称SDET)是指那些既可以称作是开发人员,同时也负责软件开发阶段和测试周期的测试工作的技术人员。一个专业的SDET更关注软件产品的可测性,稳健性和性能。他…

开干,开源 k8s 负载均衡器大比拼

原文链接:https://ewhisper.cn/posts/29610/ 🧠 译者声明: 请注意文章发布时间,时间比较久远,部分观点可能已经过时。原文作者为 PureLB 的利益相关者,所以本文可能不尽客观。 词汇表 英文中文备注LoadBala…

开干!Elasticsearch官方文档离线访问实操指南

Elasticsearch 最少必要知识实战教程直播回放 1、开发不能联网怎么访问Elasticsearch文档? 这是最近被频繁问到的问题。直接上个图: 不能让 Elasticsearch 文档限制了你我的学习热情! 2、直接开干 步骤1:访问:https://…

开干!

阅读本文大概需要 2.6 分钟。 很多人知道,我自由了,但很多人会误以为自由就是在家混吃等死,那人生得多没意思,我也有自己的事要做的,只是会自主安排时间,自主决策事情而已,下周开始,…

python中读写Protobuf总结

Protobuf是谷歌开源的协议框架,以高效传输著称并且支持多种语言。工作中也用到了,在此做个总结。 Protobuf运行原理 下面一张图可以说明: Python中使用Protobuf 2.1安装protoc程序 protoc --version 我安装的是3.6.1版本 2.2定义协议 syntax …

protobuf 入门

参考自 https://juejin.cn/post/7029961388411846664 介绍了protobuf基本概念、优缺点、与protobuf在C上的基本使用 1. 什么是protobuf 它是一个灵活、高效、结构化的序列化数据结构,它与传统的XML、JSON等相比,它更小、更快、更简单。 ProtoBuf是由…

protobuf语法详解

文章目录 一、包(package)二、选项(option)三、消息类型(message)3.1、常规消息类型3.1.1、字段修饰符3.1.2、字段类型3.1.2.1、标量类型3.1.2.2、枚举类型3.1.2.3、Any类型3.1.2.4、oneof类型3.1.2.5、map…

ProtoBuf在中C++使用介绍

ProtoBuf 我们先来看看官方文档给出的定义和描述: protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。 Protocol Buffers 是一种灵活,高效,自…