Scrapy Spider Middleware 完全指南:钩子机制、顺序规则与内置中间件源码解析
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 内置过滤与追踪逻辑的运行原理。
启用与排序: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
你可以 yield 与 Spider.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_async 是 process_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。
当前仓库中的 DepthMiddleware、UrlLengthMiddleware、RefererMiddleware、StartSpiderMiddleware 和 MetaCopyDetectionMiddleware 都继承自这个基类,仅覆盖 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.py 的get_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 元数据键说明)。
从源码结构看,DepthMiddleware 在 process_spider_output 阶段先对当前响应执行 _init_depth(response) 初始化基线深度,再走基类逻辑逐请求赋值深度——这也是它必须放在靠 Spider 一侧(顺序 900)的原因。
HttpErrorMiddleware:过滤非成功响应
HttpErrorMiddleware 用于过滤掉未成功的(错误)HTTP 响应,使 Spider 不必处理它们——否则大多时候只是徒增开销、消耗更多资源并复杂化 Spider 逻辑。按照 HTTP 标准,成功响应是状态码处于 200–300 区间的响应。
若你确实需要 Spider 处理区间外的状态码,可用两种方式指定:
- Spider 属性
handle_httpstatus_list,例如让 Spider 处理 404:
from scrapy.spiders import CrawlSpider
class MySpider(CrawlSpider):
handle_httpstatus_list = [404]
- 设置项
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_all 为 True 放行 → meta 中存在 handle_httpstatus_list 则以之为准 → 否则回落到 Spider 属性的 handle_httpstatus_list(Spider 未定义时用设置项)。被过滤的响应在 process_spider_exception 中记录日志并计入 httperror/response_ignored_count 与 httperror/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_proxy、download_latency、redirect_reasons、redirect_times、redirect_ttl、redirect_urls、retry_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.py 的 policy() 方法):请求 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.meta 键 is_start_request 为 True,使你能在下游(例如下载器中间件)区分起始请求与其他请求。实现非常直接:在 get_processed_request() 中,当 response is None(即输出来自 start())时以 setdefault 方式写入该键,默认值 True 保证已有值不被覆盖。
UrlLengthMiddleware:URL 长度过滤
UrlLengthMiddleware 过滤掉 URL 长度超过 URLLENGTH_LIMIT 的请求:超长请求被丢弃,日志记录被忽略的链接,并计入统计 urllength/request_ignored_count。
设置项
| 设置 | 默认值 | 说明 |
|---|---|---|
URLLENGTH_LIMIT |
2083 |
允许的最大 URL 长度。注意:从源码看(urllength.py 的 from_crawler),该值为 0 时中间件抛出 NotConfigured 直接不启用,因此“设为 0 关闭”与“禁用该中间件”效果等同 |
小结与扩展阅读
Scrapy 的 Spider 中间件体系由三条主线构成:一是 SPIDER_MIDDLEWARES 与 SPIDER_MIDDLEWARES_BASE 合并排序后的“引擎—Spider”双层管道,process_spider_input 升序、process_spider_output 降序的规则决定了钩子执行的精确次序;二是五种钩子方法(process_start、process_spider_input、process_spider_output/process_spider_output_async、process_spider_exception)各自清晰的返回语义;三是 BaseSpiderMiddleware 基类把“逐对象处理”抽象为 get_processed_request/get_processed_item,使 Depth、UrlLength、Referer、Start、MetaCopy 等内置中间件都能以十几行代码完成过滤与标注。内置的 HttpErrorMiddleware 则展示了在输入侧抛异常、异常链统计收尾的完整模式。
如需继续深入,可参阅仓库中的相关文档与测试:组件与中间件总览、下载器中间件、安全文档中关于凭证泄露的说明,以及测试文件 test_spidermiddleware_depth.py、test_spidermiddleware_httperror.py、test_spidermiddleware_referer.py 等,它们验证了上文各中间件的行为边界。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
