Scrapy 扩展机制怎么用才不和中间件混在一起?
Scrapy 扩展到底负责什么?
Scrapy 的扩展机制适合处理“爬虫生命周期级别”的事情,比如启动时加载配置、运行中记录统计、关闭时发通知、把关键指标推到监控系统。它不是用来改每个请求和响应的;如果逻辑要拦截 Request、Response、异常或代理,那通常应该放在 downloader middleware 或 spider middleware。这个边界很重要,很多项目后期变乱,就是因为把告警、埋点、请求改写都塞进一个类里,最后谁也不敢动。
扩展本质上是一个普通 Python 类,Scrapy 通过 from_crawler 创建实例,并让它订阅信号。常见信号包括 spider_opened、spider_closed、item_scraped、request_dropped 等。你可以把它理解成 Scrapy 项目的“事件监听器”:平时不参与下载链路,等事件发生时再做自己的事。
pythonfrom scrapy import signals class StatsAlertExtension: @classmethod def from_crawler(cls, crawler): ext = cls(crawler.stats, crawler.settings) crawler.signals.connect(ext.opened, signal=signals.spider_opened) crawler.signals.connect(ext.closed, signal=signals.spider_closed) return ext def __init__(self, stats, settings): self.stats = stats self.threshold = settings.getint("ALERT_ITEM_MIN", 1) def opened(self, spider): spider.logger.info("stats alert extension enabled") def closed(self, spider, reason): count = self.stats.get_value("item_scraped_count", 0) if count < self.threshold: spider.logger.warning("too few items: %s, reason=%s", count, reason)
启用扩展时写进 EXTENSIONS,数字是优先级,值越小越早加载。项目里建议把自定义扩展放在独立模块,别直接堆在 spider 文件里,否则复用和测试都很麻烦。
pythonEXTENSIONS = { "myproject.extensions.StatsAlertExtension": 500, } ALERT_ITEM_MIN = 20
如果扩展要依赖配置,最好在 from_crawler 阶段读取并校验。配置缺失时可以抛出 NotConfigured,这样 Scrapy 会明确告诉你扩展没有启用,而不是跑到一半才出现空值错误。需要注意的是,扩展越靠近全局治理,越应该保持“可关闭、可降级”。告警接口挂了不应该让采集任务整体失败,除非这就是业务要求。
如果团队里有多个爬虫共享同一套监控规则,可以把阈值、告警开关和通知目标都放进 settings,再由扩展读取。这样开发环境可以只打日志,生产环境再接入真实告警,避免本地调试时频繁打扰别人。另一个实用做法是把扩展输出的统计字段命名固定下来,比如 business/empty_item_count、business/invalid_price_count,后续接 Grafana 或日志平台时不会因为字段名变化而断图。
追问
扩展和中间件应该怎么取舍?
扩展看全局生命周期,中间件看单次请求链路,这是最稳的判断标准。比如统计最终采集量、爬虫结束发企业微信通知,用扩展更自然;给请求加代理、处理 403、改 User-Agent,就应该用中间件。边界踩错以后,扩展可能变成“万能工具类”,调试时你会发现请求还没发出去,告警逻辑却先影响了调度。我的经验是:只要函数参数里必须拿到 request 或 response,先别急着写扩展。
from_crawler 里为什么经常连接 signals?
因为扩展实例需要拿到 Scrapy 的运行上下文,包括 settings、stats、signals、engine 等对象。直接在 __init__ 里 new 一个扩展也能写,但拿不到 crawler,就没法按 Scrapy 的方式订阅事件。这里的坑是信号函数签名要和信号匹配,少写参数可能运行到关闭阶段才报错。建议先从 spider_opened 和 spider_closed 两个信号开始,确认日志正常后再增加 item 或 request 相关信号。
扩展里能不能做耗时操作?
能做,但要非常克制。Scrapy 基于事件循环运行,扩展里长时间同步请求外部接口,会拖慢爬虫关闭、item 处理甚至整个 reactor。告警、上报监控这类操作最好设置短超时,或者丢给队列、后台服务处理。特别是 spider_closed,很多人会在这里上传文件、发报表,一旦接口卡住,任务看起来就像“明明爬完了却不退出”。
自定义统计指标放在哪里更合适?
如果指标和生命周期相关,放扩展里比较清楚,比如启动时间、关闭原因、最终 item 数、错误比例。若指标来自某个 item 清洗步骤,也可以在 pipeline 中 stats.inc_value(),扩展最后统一读取并汇总。取舍点在于数据产生的位置:不要为了集中管理,把所有业务 pipeline 都反向依赖扩展。否则扩展一改,数据入库链路也跟着抖。
生产环境使用扩展有什么边界?
扩展适合补齐监控和治理,不适合承载核心业务解析。它可以检查 item 数是否异常、记录关闭原因、输出错误分布,但不应该决定页面怎么解析、数据怎么清洗。另一个常见坑是配置优先级和内置扩展冲突,导致 Telnet、CoreStats 等行为被误关。上线前至少跑一次小流量任务,确认 stats、日志、告警都按预期出现。
小结
Scrapy 扩展的价值在于把生命周期治理从 spider 里拆出来。它越像事件监听器,项目越好维护;它越像业务大杂烩,后期越难排查。用它做统计、告警、监控和收尾动作,用中间件处理请求响应,用 pipeline 处理数据,这个分工通常最省心。