我给 Scrapy Redis 开源库发的 PR 被合并了

2024-05-15 12:18

本文主要是介绍我给 Scrapy Redis 开源库发的 PR 被合并了,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这是「进击的Coder」的第 366 篇技术分享

作者:崔庆才

来源:崔庆才丨静觅

阅读本文大概需要 6 分钟。


不知道大家基于 Scrapy-Redis 开发分布式爬虫的时候有没有遇到一个比较尴尬的问题,且听我一一道来。

大家在运行 Scrapy 的的时候肯定见过类似这样输出吧:

2021-03-15 21:52:06 [scrapy.extensions.logstats] INFO: Crawled 33 pages (at 33 pages/min), scraped 172 items (at 172 items/min)
...

这里就是一些统计输出结果对不对,它告诉我们当前这个爬虫的爬取速度和总的爬取情况。

另外在爬取完成之后最后输出的的统计信息是这样的:

{'downloader/request_bytes': 2925,'downloader/request_count': 11,'downloader/request_method_count/GET': 11,'downloader/response_bytes': 23406,'downloader/response_count': 11,'downloader/response_status_count/200': 10,'downloader/response_status_count/404': 1,'elapsed_time_seconds': 3.917599,'finish_reason': 'finished','finish_time': datetime.datetime(2021, 3, 15, 14, 1, 36, 275427),'item_scraped_count': 100,'log_count/DEBUG': 111,'log_count/INFO': 10,'memusage/max': 55242752,'memusage/startup': 55242752,'request_depth_max': 9,'response_received_count': 11,'robotstxt/request_count': 1,'robotstxt/response_count': 1,'robotstxt/response_status_count/404': 1,'scheduler/dequeued': 10,'scheduler/dequeued/memory': 10,'scheduler/enqueued': 10,'scheduler/enqueued/memory': 10,'start_time': datetime.datetime(2021, 3, 15, 14, 1, 32, 357828)}
2021-03-15 22:01:36 [scrapy.core.engine] INFO: Spider closed (finished)

然而这个信息,当我们使用基于 Scrapy-Redis 来实现的时候,你会发现每个爬虫都在做自己的统计,比如其中一个 Spider 机器性能和网络比较好,爬取速度快,那么它的统计结果就更高,表现不太好的 Spider 它的统计结果就差一些。这些 Spider 的统计信息都是独立的互不影响的,数据也各不相同。

这是个麻烦事啊,统计信息不同步而且很分散,我想知道总共爬取了多少条数据也不知道,那怎么办呢?另外我还想对这些统计数据做数据分析和报表,根本不知道咋合并统计。

所以,我在想,如果这个统计信息也能基于 Redis 实现多爬虫同步不就好了吗?

实现

统计信息首先应该怎么写呢,先查下官方文档,找到这个:https://docs.scrapy.org/en/latest/topics/stats.html

这里介绍了一个 Stats Collection,官方介绍如下:

Scrapy provides a convenient facility for collecting stats in the form of key/values, where values are often counters. The facility is called the Stats Collector, and can be accessed through the stats attribute of the Crawler API, as illustrated by the examples in the Common Stats Collector uses p below.

However, the Stats Collector is always available, so you can always import it in your module and use its API (to increment or set new stat keys), regardless of whether the stats collection is enabled or not. If it’s disabled, the API will still work but it won’t collect anything. This is aimed at simplifying the stats collector usage: you should spend no more than one line of code for collecting stats in your spider, Scrapy extension, or whatever code you’re using the Stats Collector from.

Another feature of the Stats Collector is that it’s very efficient (when enabled) and extremely efficient (almost unnoticeable) when disabled.

The Stats Collector keeps a stats table per open spider which is automatically opened when the spider is opened, and closed when the spider is closed.

OK,没问题,这个就是一个信息收集器,然后我们可以根据它的一些接口来实现自己的信息收集器。

比如设置统计值:

stats.set_value('hostname', socket.gethostname())

比如增加统计值:

stats.inc_value('custom_count')

另外扒了一下源码,看到了默认的收集器就是 MemoryStatsCollector,就是基于内存的,源码见:https://docs.scrapy.org/en/latest/_modules/scrapy/statscollectors.html#MemoryStatsCollector。因为是基于内存的,所以每个爬虫一定是独立的,所以我只需要把它们的共享队列改成 Redis 就能实现分布式信息收集的同步了。

另外看来这些统计信息,基本上就是表示为 key-value 信息,存到 Redis 最合适的当然是 Hash 了。

OK,说干就干,改写了下 Memory,把存储换成 Redis,其他的实现基本差不多,实现了一个 RedisStatsCollector 如下:

from scrapy.statscollectors import StatsCollector
from .connection import from_settings as redis_from_settings
from .defaults import STATS_KEY, SCHEDULER_PERSISTclass RedisStatsCollector(StatsCollector):"""Stats Collector based on Redis"""def __init__(self, crawler, spider=None):super().__init__(crawler)self.server = redis_from_settings(crawler.settings)self.spider = spiderself.spider_name = spider.name if spider else crawler.spidercls.nameself.stats_key = crawler.settings.get('STATS_KEY', STATS_KEY)self.persist = crawler.settings.get('SCHEDULER_PERSIST', SCHEDULER_PERSIST)def _get_key(self, spider=None):"""Return the hash name of stats"""if spider:self.stats_key % {'spider': spider.name}if self.spider:return self.stats_key % {'spider': self.spider.name}return self.stats_key % {'spider': self.spider_name or 'scrapy'}@classmethoddef from_crawler(cls, crawler):return cls(crawler)def get_value(self, key, default=None, spider=None):"""Return the value of hash stats"""if self.server.hexists(self._get_key(spider), key):return int(self.server.hget(self._get_key(spider), key))else:return defaultdef get_stats(self, spider=None):"""Return the all of the values of hash stats"""return self.server.hgetall(self._get_key(spider))def set_value(self, key, value, spider=None):"""Set the value according to hash key of stats"""self.server.hset(self._get_key(spider), key, value)def set_stats(self, stats, spider=None):"""Set all the hash stats"""self.server.hmset(self._get_key(spider), stats)def inc_value(self, key, count=1, start=0, spider=None):"""Set increment of value according to key"""if not self.server.hexists(self._get_key(spider), key):self.set_value(key, start)self.server.hincrby(self._get_key(spider), key, count)def max_value(self, key, value, spider=None):"""Set max value between current and new value"""self.set_value(key, max(self.get_value(key, value), value))def min_value(self, key, value, spider=None):"""Set min value between current and new value"""self.set_value(key, min(self.get_value(key, value), value))def clear_stats(self, spider=None):"""Clarn all the hash stats"""self.server.delete(self._get_key(spider))def open_spider(self, spider):"""Set spider to self"""if spider:self.spider = spiderdef close_spider(self, spider, reason):"""Clear spider and clear stats"""self.spider = Noneif not self.persist:self.clear_stats(spider)

OK,我把这个代码放到 Scrapy-Redis 的源码里面,新建了一个 stats.py 的文件夹,然后本地重新安装下 Scrapy-Redis 这个包:

切换到 Scrapy-Redis 源码目录,执行安装命令如下:

pip3 install .

输出结果类似如下:

...
Installing collected packages: scrapy-redisAttempting uninstall: scrapy-redisFound existing installation: scrapy-redis 0.7.0.dev0Uninstalling scrapy-redis-0.7.0.dev0:Successfully uninstalled scrapy-redis-0.7.0.dev0
Successfully installed scrapy-redis-0.7.0.dev0

这样本地就装好最新版的 Scrapy-Redis 了。

然后本地测试下,切到 example-project/example 目录下,添加了一行代码:

STATS_CLASS = "scrapy_redis.stats.RedisStatsCollector"

意思就是信息收集器这个类使用我刚才创建的 RedisStatsCollector,然后运行:

scrapy crawl dmoz

运行起来了,然后我再开另外的命令行运行同样的命令,启动多个爬虫。

这时候我打开 Redis Desktop Manager,就看到了如下的四个键名:

看到了吧,这里多了一个 dmoz:stats,这个就是统计信息,打开之后内容如下:

可以看到所有的统计数据就被存到 Redis 了,而且每个 Spider 都会读取和写入,实现了多个 Spider 统计信息的同步。

发 PR

这个 Feature 我后来就给 Scrapy-Redis 的作者发了 PR,https://github.com/rmax/scrapy-redis/pull/186,幸运的是,今天发现已经被 Approve 并 merge 了:

作者 rmax 还说了声 Nice Feature:

激动!开心!

另外我还和作者联系了下,了解到他现在正在寻找 Scrapy-Redis 这个项目的 maintainer,然后我就跟他说我乐意帮忙维护这个项目,他给我加了一些权限。

后续 Scrapy-Redis 的维护我应该也会参与进来了。比如刚刚我发的 Feature,后续会发新版本的 Scrapy-Redis 的 Release。

这里不得不说一句,Scrapy-Redis 距离上次发新版本已经三年多了,新的改动都在 master,一直没有 release,我给作者提了 Issue 反馈了这个问题不过也一直没有发新版,后续应该我会帮忙发布一个新的 Release,把最新的 Feature 和 Bug Fix 都上了。

如果大家想体验刚才介绍的最新的 Feature 的话,可以直接安装 master 版本,命令如下:

pip3 install git+https://github.com/rmax/scrapy-redis.git

如有问题希望大家及时反馈和提 Issue,感谢支持!

作者:崔庆才

排版:崔庆才

崔庆才丨静觅

隐形字

同名公众号「崔庆才丨静觅」

在这里分享自己的一些经验、想法和见解。

长按识别二维码关注

好文和朋友一起看~

这篇关于我给 Scrapy Redis 开源库发的 PR 被合并了的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/991814

相关文章

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

HomeBank:开源免费的个人财务管理软件

在个人财务管理领域,找到一个既免费又开源的解决方案并非易事。HomeBank&nbsp;正是这样一个项目,它不仅提供了强大的功能,还拥有一个活跃的社区,不断推动其发展和完善。 开源免费:HomeBank 是一个完全开源的项目,用户可以自由地使用、修改和分发。用户友好的界面:提供直观的图形用户界面,使得非技术用户也能轻松上手。数据导入支持:支持从 Quicken、Microsoft Money