首页
/ Scrapy Spider Middleware 完全指南:钩子机制、顺序规则与内置中间件源码解析

Scrapy Spider Middleware 完全指南:钩子机制、顺序规则与内置中间件源码解析

2026-09-04 20:52:46作者:劳婵绚Shirley

Spider Middleware(Spider 中间件)是 Scrapy 中嵌入 Spider 处理机制的钩子框架:它位于引擎与 Spider 之间,允许你拦截进入 Spider 的响应、Spider 回调吐出的请求与 Item,以及 start() 的输出。本文以官方文档 Spider Middleware 为主体,结合当前仓库中的源码实现(scrapy/spidermiddlewares/ 目录)与默认配置(scrapy/settings/default_settings.py),完整讲解中间件的启用方式、执行顺序规则、五种钩子方法的语义、BaseSpiderMiddleware 基类的用法,以及 Depth、HttpError、MetaCopyDetection、Referer、Start、UrlLength 六个内置中间件的配置参数与底层实现,帮助你既能正确编写自定义中间件,也能深入理解 Scrapy 内置过滤与追踪逻辑的运行原理。

Scrapy 整体架构图:展示了引擎、下载器、Spider 中间件与 Spider 之间的请求/响应流转关系

启用与排序:SPIDER_MIDDLEWARES 配置机制

要启用一个 Spider 中间件,需将其类路径添加到 SPIDER_MIDDLEWARES 设置中。该设置是一个字典:键为中间件类路径,值为中间件顺序号:

SPIDER_MIDDLEWARES = {
    "myproject.middlewares.CustomSpiderMiddleware": 543,
}

SPIDER_MIDDLEWARES 会与 Scrapy 内置的 SPIDER_MIDDLEWARES_BASE 设置(不应用于覆盖)合并,然后按顺序号排序,得到最终启用的中间件列表。排序规则决定了执行流向:

  • 第一个中间件最靠近引擎,最后一个最靠近 Spider
  • 每个中间件的 process_spider_input 方法按升序(100、200、300、……)依次调用;
  • 每个中间件的 process_spider_output 方法按降序依次调用。

当前仓库中 SPIDER_MIDDLEWARES_BASE 的实际定义见 default_settings.py,共启用 6 个内置中间件:

SPIDER_MIDDLEWARES_BASE = {
    # Engine side
    "scrapy.spidermiddlewares.start.StartSpiderMiddleware": 25,
    "scrapy.spidermiddlewares.httperror.HttpErrorMiddleware": 50,
    "scrapy.spidermiddlewares.referer.RefererMiddleware": 700,
    "scrapy.spidermiddlewares.urllength.UrlLengthMiddleware": 800,
    "scrapy.spidermiddlewares.depth.DepthMiddleware": 900,
    "scrapy.spidermiddlewares.metacopy.MetaCopyDetectionMiddleware": 1000,
    # Spider side
}

从中可以看出:StartSpiderMiddleware(25)和 HttpErrorMiddleware(50)位于靠引擎一侧,负责输入侧处理;而 RefererMiddleware(700)、UrlLengthMiddleware(800)、DepthMiddleware(900)、MetaCopyDetectionMiddleware(1000)位于靠 Spider 一侧,process_spider_output 阶段会更早命中它们。

如何为自己的中间件选择顺序号? 查看 SPIDER_MIDDLEWARES_BASE 中各中间件的顺序,按你希望插入的位置就近取值。顺序非常重要,因为每个中间件执行的动作不同,你的中间件可能依赖某个前置(或后置)中间件已经生效。

如何禁用内置中间件? 在项目 SPIDER_MIDDLEWARES 中将对应类路径的值设为 None。例如禁用 Referer 中间件并替换为自己的实现:

SPIDER_MIDDLEWARES = {
    "scrapy.spidermiddlewares.referer.RefererMiddleware": None,
    "myproject.middlewares.CustomRefererSpiderMiddleware": 700,
}

最后需要注意:部分中间件还需要额外的设置项才能生效,具体见下文各中间件的“settings”小节(如 URLLENGTH_LIMIT 为 0 时 UrlLengthMiddleware 不会启用)。

编写自定义 Spider Middleware:五种钩子方法

每个 Spider 中间件都是一个组件,可定义下列一个或多个方法(参考文档 Writing your own spider middleware 小节):

process_start:拦截 start() 输出

process_start 是一个异步生成器方法,用于迭代 Spider.start() 的输出,或迭代上一个 Spider 中间件 process_start 方法的输出并对其进行改写。例如原样透传:

async def process_start(self, start):
    async for item_or_request in start:
        yield item_or_request

你可以 yieldSpider.start() 相同类型的对象(请求或 Item)。如果需要编写兼容 Scrapy 2.13 之前版本的中间件,还应额外定义一个同步的 process_start_requests() 方法并返回可迭代对象:

def process_start_requests(self, start, spider):
    yield from start

process_spider_input:响应进入 Spider 前的输入钩子

该方法在每个响应经过 Spider 中间件链进入 Spider 时被调用。语义规则:

  • 返回 None:Scrapy 继续处理该响应,执行其余中间件,最终把响应交给 Spider;
  • 抛出异常:Scrapy 不再调用其他中间件的 process_spider_input,转而调用请求的 errback(如果存在);若无 errback,则进入 process_spider_exception 链。errback 的输出会反向串联到 process_spider_output 链(或其抛出异常时进入 process_spider_exception 链)进行后续处理。

内置的 HttpErrorMiddleware 正是这一机制的典型应用:它在 process_spider_input 中对非 2xx 响应抛出 HttpError 异常(见 httperror.py)。

process_spider_output:Spider 输出钩子(异步生成器)

process_spider_output 是一个异步生成器,在 Spider 处理完响应后,带着 Spider 的结果被调用。关键要点:

  • result 是惰性的(lazy):回调函数体只有在 result 被迭代时才执行,因此写在迭代之前的代码会先于回调主体运行;
  • 产出数量无需与输入一致:可以丢弃部分对象、原样透传,也可以产出比输入更多的对象(例如把一个 Item 拆成多个)。

参数说明:response 是产生该 Spider 输出的响应对象;result 是一个异步可迭代对象,其中的元素为 scrapy.Request 对象与 Item 对象。

process_spider_output_async:通用中间件的替代名称

当实现“通用 Spider 中间件”(Universal spider middleware,见下节)时,process_spider_output_asyncprocess_spider_output 的替代方法名。

process_spider_exception:异常处理钩子

当 Spider 回调或前一个 Spider 中间件的 process_spider_output 抛出异常时,该方法被调用。返回值的语义:

  • 返回 None:Scrapy 继续处理该异常,执行后续中间件组件中的其他 process_spider_exception,直到所有中间件处理完毕,异常到达引擎(在那里被记录并丢弃);
  • 返回 Request 或 Item 对象的可迭代对象:process_spider_output 管道从下一个 Spider 中间件开始接管处理,且不会再调用其他 process_spider_exception

Universal Spider Middleware:兼容新旧版本的双轨写法

在 Scrapy 2.6.3 及更早版本中,process_spider_output() 必须是同步生成器。要在同一中间件里同时兼容旧版本与当前版本,做法是:把异步的 process_spider_output 改名为 process_spider_output_async,再定义一个同步的 process_spider_output() 供旧版本使用:

class UniversalSpiderMiddleware:
    async def process_spider_output_async(self, response, result):
        async for r in result:
            # ... do something with r
            yield r

    def process_spider_output(self, response, result):
        for r in result:
            # ... do something with r
            yield r

BaseSpiderMiddleware:官方基类

Scrapy 提供了自定义 Spider 中间件的基类(base.py,Scrapy 2.13 起引入)。使用它不是必须的,但可以简化中间件实现。它提供:

  • from_crawler(crawler):标准构造入口,保存 crawler 引用;
  • process_start(start)process_spider_output(response, result)process_spider_output_async(response, result):默认实现会逐个取出 Spider 输出对象,交给内部的 _get_processed() 处理——若对象是 Request 则调用 get_processed_request(),否则调用 get_processed_item();返回 None 即表示丢弃该对象;
  • 钩子方法 get_processed_request(request, response)get_processed_item(item, response):默认原样返回,你可以覆盖这两个方法来添加针对单个请求 / Item 的处理逻辑(返回原对象、新对象,或 None 丢弃)。其中 response 在处理 start() 输出时为 None

当前仓库中的 DepthMiddlewareUrlLengthMiddlewareRefererMiddlewareStartSpiderMiddlewareMetaCopyDetectionMiddleware 都继承自这个基类,仅覆盖 get_processed_request()(或额外覆盖输入侧钩子),实现非常精炼——这是编写 Spider 中间件时值得参照的范式。

内置 Spider Middleware 参考

本节逐一讲解随 Scrapy 发布的 6 个 Spider 中间件。默认启用列表及顺序见上文 SPIDER_MIDDLEWARES_BASE

DepthMiddleware:爬取深度追踪与限制

DepthMiddleware 追踪站点内每个请求的深度:若 response.meta 中没有 depth 值(通常是首个请求),则置为 0,否则加 1,并写入 request.meta["depth"]。它通过以下设置发挥作用:

  • DEPTH_LIMIT:最大爬取深度,默认 0(不限制)。超过限制的请求被丢弃,并计入统计 depth/request_ignored_count,日志中记录第一条被忽略的链接(见 depth.pyget_processed_request);
  • DEPTH_PRIORITY:深度对优先级的影响系数,默认 0。设为非零值后,请求优先级会按 priority -= depth * DEPTH_PRIORITY 调整,可用于“深度越浅优先级越高”之类的策略;
  • DEPTH_STATS_VERBOSE:默认 False,开启后统计每个深度级别的请求计数(request_depth_count/N)。

此外,request.meta["depth_reset"] 被设为 True 时,该请求深度为 0 而非来源响应深度加 1,例如用于跨域名切换时避免 DEPTH_LIMIT 的延续(depth.py 中的 depth_reset 元数据键说明)。

从源码结构看,DepthMiddlewareprocess_spider_output 阶段先对当前响应执行 _init_depth(response) 初始化基线深度,再走基类逻辑逐请求赋值深度——这也是它必须放在靠 Spider 一侧(顺序 900)的原因。

HttpErrorMiddleware:过滤非成功响应

HttpErrorMiddleware 用于过滤掉未成功的(错误)HTTP 响应,使 Spider 不必处理它们——否则大多时候只是徒增开销、消耗更多资源并复杂化 Spider 逻辑。按照 HTTP 标准,成功响应是状态码处于 200–300 区间的响应。

若你确实需要 Spider 处理区间外的状态码,可用两种方式指定:

  1. Spider 属性 handle_httpstatus_list,例如让 Spider 处理 404:
from scrapy.spiders import CrawlSpider


class MySpider(CrawlSpider):
    handle_httpstatus_list = [404]
  1. 设置项 HTTPERROR_ALLOWED_CODES

Request.meta 也支持按请求粒度控制:

  • handle_httpstatus_list:指定单个请求允许的状态码列表;
  • handle_httpstatus_all:设为 True 允许该请求的任意状态码,设为 False 则显式禁用 handle_httpstatus_all 的效果。

处理非 200 响应通常是个坏主意,除非你非常清楚自己在做什么。

从源码看(httperror.py),检查优先级为:200 <= status < 300 直接放行 → meta 中 handle_httpstatus_allTrue 放行 → meta 中存在 handle_httpstatus_list 则以之为准 → 否则回落到 Spider 属性的 handle_httpstatus_list(Spider 未定义时用设置项)。被过滤的响应在 process_spider_exception 中记录日志并计入 httperror/response_ignored_counthttperror/response_ignored_status_count/<状态码> 统计。

设置项

设置 默认值 说明
HTTPERROR_ALLOWED_CODES [] 放行该列表中状态码的所有非 200 响应
HTTPERROR_ALLOW_ALL False 无论状态码如何,放行所有响应

MetaCopyDetectionMiddleware:内部 meta 键复制警告

MetaCopyDetectionMiddleware 在 Spider 产出携带内部 meta 键的请求时发出警告——这些键不应从 response.meta 复制到新请求中(原因见 Request.meta 的文档说明)。每次爬取最多发出 1 条警告。

从源码看,它监控的内部键集合(_INTERNAL_KEYS)包括:_auth_proxy_dont_cache_scheme_proxydownload_latencyredirect_reasonsredirect_timesredirect_ttlredirect_urlsretry_times(见 metacopy.py)。

设置项

设置 默认值 说明
META_COPY_WARN_SKIP_KEYS [] 从内部键检查中排除的内部 meta 键名列表。当你有意复制某个被监控的键、希望抑制警告又不想整体禁用中间件时使用

RefererMiddleware:按 Referrer Policy 填充 Referer 头

RefererMiddleware 基于生成该请求的 Response 的 URL,为 Request 填充 Referer 头。注意它使用 setdefault 方式写入(referer.py),即请求已显式携带 Referer 头时不会被覆盖;且 start() 产生的请求(response is None)不填充 Referer。

设置项

设置 默认值 说明
REFERER_ENABLED True 是否启用 Referer 中间件(False 时中间件以 NotConfigured 退出,见 referer.py
REFERRER_POLICY 'scrapy.spidermiddlewares.referer.DefaultReferrerPolicy' 填充 Referer 头时应用的 Referrer Policy
REFERRER_POLICIES {} 策略名到 ReferrerPolicy 子类导入路径的映射字典;值设为 None 可禁用对应策略名。用于覆盖由响应 Referrer-Policy 头触发的策略;使用键 "" 可覆盖“空字符串”策略(Scrapy 2.14.2 新增)

REFERRER_POLICY 的取值可以是:

  • 一个 ReferrerPolicy 子类的导入路径(自定义策略或内置策略之一);
  • 一个或多个逗号分隔的 W3C 标准字符串值;
  • 特殊值 "scrapy-default"

也可通过请求级 meta 键 "referrer_policy" 按请求设置策略,取值规则与 REFERRER_POLICY 相同。从源码看,策略的解析优先级为(referer.pypolicy() 方法):请求 meta 中的有效策略 → 父响应 Referrer-Policy 头中的有效策略 → 设置项 REFERRER_POLICY 指定的默认策略;meta 中配置无效(如拼写错误)时回落到设置项策略。

REFERRER_POLICY 可接受值对照表

字符串值 对应类(字符串)
"scrapy-default"(默认) scrapy.spidermiddlewares.referer.DefaultReferrerPolicy
"no-referrer" scrapy.spidermiddlewares.referer.NoReferrerPolicy
"no-referrer-when-downgrade" scrapy.spidermiddlewares.referer.NoReferrerWhenDowngradePolicy
"same-origin" scrapy.spidermiddlewares.referer.SameOriginPolicy
"origin" scrapy.spidermiddlewares.referer.OriginPolicy
"strict-origin" scrapy.spidermiddlewares.referer.StrictOriginPolicy
"origin-when-cross-origin" scrapy.spidermiddlewares.referer.OriginWhenCrossOriginPolicy
"strict-origin-when-cross-origin" scrapy.spidermiddlewares.referer.StrictOriginWhenCrossOriginPolicy
"unsafe-url" scrapy.spidermiddlewares.referer.UnsafeUrlPolicy

重要提示:Scrapy 的默认 Referrer 策略与 W3C 推荐的浏览器取值 "no-referrer-when-downgrade" 类似——它会从任何 http(s):// URL 向任何 https:// URL 发送非空 Referer 头,即使域名不同。如果你想对跨域请求移除 referer 信息,"same-origin" 可能是更好的选择。另外,W3C 推荐的 "no-referrer-when-downgrade" 并非 Scrapy 的默认策略(Scrapy 默认是 DefaultReferrerPolicy);"unsafe-url" 策略会把 TLS 保护资源的路径与来源泄露到不安全来源,不推荐使用

StartSpiderMiddleware:标记起始请求

StartSpiderMiddleware 的作用是为起始请求(start() 产出的请求)设置 Request.metais_start_requestTrue,使你能在下游(例如下载器中间件)区分起始请求与其他请求。实现非常直接:在 get_processed_request() 中,当 response is None(即输出来自 start())时以 setdefault 方式写入该键,默认值 True 保证已有值不被覆盖。

UrlLengthMiddleware:URL 长度过滤

UrlLengthMiddleware 过滤掉 URL 长度超过 URLLENGTH_LIMIT 的请求:超长请求被丢弃,日志记录被忽略的链接,并计入统计 urllength/request_ignored_count

设置项

设置 默认值 说明
URLLENGTH_LIMIT 2083 允许的最大 URL 长度。注意:从源码看(urllength.pyfrom_crawler),该值为 0 时中间件抛出 NotConfigured 直接不启用,因此“设为 0 关闭”与“禁用该中间件”效果等同

小结与扩展阅读

Scrapy 的 Spider 中间件体系由三条主线构成:一是 SPIDER_MIDDLEWARESSPIDER_MIDDLEWARES_BASE 合并排序后的“引擎—Spider”双层管道,process_spider_input 升序、process_spider_output 降序的规则决定了钩子执行的精确次序;二是五种钩子方法(process_startprocess_spider_inputprocess_spider_output/process_spider_output_asyncprocess_spider_exception)各自清晰的返回语义;三是 BaseSpiderMiddleware 基类把“逐对象处理”抽象为 get_processed_request/get_processed_item,使 Depth、UrlLength、Referer、Start、MetaCopy 等内置中间件都能以十几行代码完成过滤与标注。内置的 HttpErrorMiddleware 则展示了在输入侧抛异常、异常链统计收尾的完整模式。

如需继续深入,可参阅仓库中的相关文档与测试:组件与中间件总览下载器中间件安全文档中关于凭证泄露的说明,以及测试文件 test_spidermiddleware_depth.pytest_spidermiddleware_httperror.pytest_spidermiddleware_referer.py 等,它们验证了上文各中间件的行为边界。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388