You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy并发运行多爬虫时基类变量跨实例数据泄漏问题咨询

问题根因

你遇到的跨爬虫数据泄漏和Scrapy并发逻辑无关,是Python类属性的基础特性导致的:
你在MyBaseSpider类层级直接定义的new_items = []属于类属性,由所有继承该基类的子类、以及子类的所有实例共享,本质是全局内存里的同一个列表对象。
之前逻辑运行正常,是因为此前你大概率是逐次启动独立进程跑单个爬虫,每个进程有独立的内存空间,类属性不会跨进程共享;现在你用CrawlerRunner在同一个Python进程内同时启动多个爬虫,所有爬虫实例执行self.new_items.append()时,由于实例自身没有同名属性,会顺着继承链找到基类上的共享列表直接修改,自然会出现不同站点的数据混在同一个列表里的问题。


1. Scrapy同进程并发爬虫的相关规则

Scrapy的运行逻辑明确区分进程隔离和同进程运行两种场景:

  • 用命令行单条启动爬虫、或者用CrawlerProcess启动时,每个爬虫默认运行在独立进程中,内存完全隔离,不会出现全局变量/类属性共享的问题
  • 用CrawlerRunner在同个脚本里启动多个爬虫时,所有爬虫共享同一个Python进程的内存空间,基于Twisted协程调度,没有多进程级别的内存隔离:类属性、全局模块变量、单例对象都会被所有并发爬虫共享读写
  • 同进程协程调度不会出现多线程级别的执行时序竞争,但共享可变对象的读写污染是确定性问题,只要多个实例同时修改同一个可变类属性,必然出现数据串流

2. 爬虫基类定义变量是否属于不规范实践

不存在“基类不能定义变量”的强制规范,核心是区分类属性和实例属性的适用场景,你的错误属于属性定义位置选错了:

  • 只读的常量类配置(比如全局统一的请求超时时间、默认请求头、固定的规则阈值),定义在基类作为类属性是完全合理的,所有子类可以统一继承复用
  • 每个爬虫实例独有的可变状态(比如你当前的暂存抓取结果列表、单实例的运行标记、站点级别的统计数据),绝对不能定义在类层级,必须在实例初始化方法中赋值为实例属性,才能保证每个实例持有独立的对象副本

基类实例属性的正确写法示例:

class MyBaseSpider(scrapy.Spider):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 以下属性为每个实例独有,不会跨实例/跨爬虫共享
        self.new_items = []
        self.run_id = None

3. 运行编号这类状态数据的存储方案

根据状态的作用范围选存储方式即可,不需要全部迁到外部组件:

  • 单爬虫实例私有状态:比如当前爬虫的运行编号、单站点暂存的抓取条目、站点级别的计数统计,直接在__init__方法中定义为实例属性就足够,天然隔离不会串数据
  • 同进程多爬虫共享的全局状态:比如整个批次的运行ID、跨爬虫去重的临时集合,不要存在基类属性里,可以挂载到Crawler上下文、或者自定义的全局配置单例上,明确标记为全局共享数据,避免误修改
  • 需要跨进程、跨运行周期持久化的状态:比如历史抓取指纹、批次运行记录,不要存在进程内存中,写入Redis、关系型数据库等持久化存储即可

补充说明

你提到的用Pipeline归集数据、在close_spider钩子中发邮件的方案是可行的,但要注意:Pipeline在同进程中是全局单例初始化的,处理条目时必须通过process_item方法传入的spider参数区分条目所属站点,按spider维度分桶存储,否则依然会出现数据混存的问题。
如果不想调整现有逻辑,最小修复成本就是把基类里的类属性移到__init__方法中赋值为实例属性,改完就能解决当前的数据泄漏问题。


内容的提问来源于stack exchange,提问作者Jim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:15:41