bigywater.tech
Scrapy · 11

下载器中间件:数字顺序与请求、响应的经过方向

DOWNLOADER_MIDDLEWARES 里的数字到底表示什么?请求和响应各按什么方向经过它们?中间件返回 Response 或 Request 会发生什么?这篇用五个自定义中间件加记录钩子,把每种情形的事件顺序一行行打出来。

小 → 大
process_request 的调用方向(process_response 反过来)
64 vs 2613 字节
595 与 585 的中间件看到的同一个 gzip 响应的 body 大小
0 个
process_request 返回 Response 时服务器收到的请求数(process_response 仍全部调用)

数字的含义

实测 · 本机执行输出
   1. A       process_request     url=page
   2. B       process_request     url=page
   3. C       process_request     url=page
   4. D       process_request     url=page
   5. E       process_request     url=page
   6. E       process_response    url=page body=14
   7. D       process_response    url=page body=14
   8. C       process_response    url=page body=14
   9. B       process_response    url=page body=14
  10. A       process_response    url=page body=14
  11. spider  callback            url=page body=<html>x</html>
  服务器收到 1 个请求
  日志里 “Enabled downloader middlewares” 的顺序(就是 process_request 的调用顺序,process_response 反着来):
    offsite.OffsiteMiddleware → 自定义 A → httpauth.HttpAuthMiddleware → 自定义 B →
    downloadtimeout.DownloadTimeoutMiddleware → defaultheaders.DefaultHeadersMiddleware →
    useragent.UserAgentMiddleware → retry.RetryMiddleware → 自定义 C → redirect.MetaRefreshMiddleware →
    httpcompression.HttpCompressionMiddleware → redirect.RedirectMiddleware →
    cookies.CookiesMiddleware → httpproxy.HttpProxyMiddleware → 自定义 D → stats.DownloaderStats → 自定义
    E

数字越小越靠近引擎:请求从小到大依次经过 process_request,下载完成后响应从大到小依次经过 process_response。

位置决定你看到什么

实测 · 本机执行输出
   1. H595    process_response    url=gz body=64
   2. L585    process_response    url=gz body=2613
   3. spider  callback            body=2613
  → 响应方向先经过 595(离下载器近),再经过 590 解压,最后到 585:595 看到 gzip 压缩后的字节,585 看到解压后的

内置的 HttpCompressionMiddleware 在 590,放在它外侧(数字大)的中间件看到压缩的 body,放在内侧(数字小)的看到解压后的 body。

返回 Response

实测 · 本机执行输出
   1. A       process_request     url=page
   2. B       process_request     url=page
   3. C       process_request     url=page
   4. E       process_response    url=page body=19
   5. D       process_response    url=page body=19
   6. C       process_response    url=page body=19
   7. B       process_response    url=page body=19
   8. A       process_response    url=page body=19
   9. spider  callback            url=page body=<html>from C</html>
  服务器收到 0 个请求(没有真正去下载)

process_request 返回 Response 会跳过后面的 process_request 和下载,但全部 process_response 仍然会按顺序调用。

返回 Request

实测 · 本机执行输出
   1. A       process_request     url=page
   2. C       process_request     url=page
   3. A       process_request     url=other
   4. C       process_request     url=other
   5. E       process_request     url=other
   6. E       process_response    url=other body=14
   7. C       process_response    url=other body=14
   8. A       process_response    url=other body=14
   9. spider  callback            url=other body=<html>x</html>
  服务器收到 ['/other'];scheduler/enqueued=2

无论从 process_request 还是 process_response 返回 Request,原流程中断,新请求回到调度器重新走。

process_response 返回 Request

实测 · 本机执行输出
   1. A       process_request     url=page
   2. B       process_request     url=page
   3. C       process_request     url=page
   4. D       process_request     url=page
   5. E       process_request     url=page
   6. E       process_response    url=page body=14
   7. D       process_response    url=page body=14
   8. A       process_request     url=replaced
   9. B       process_request     url=replaced
  10. C       process_request     url=replaced
  11. D       process_request     url=replaced
  12. E       process_request     url=replaced
  13. E       process_response    url=replaced body=14
  14. D       process_response    url=replaced body=14
  15. C       process_response    url=replaced body=14
  16. B       process_response    url=replaced body=14
  17. A       process_response    url=replaced body=14
  18. spider  callback            url=replaced body=<html>x</html>
  共 18 个事件;服务器收到 ['/page', '/replaced']

返回 Request 的中间件之后(更靠近引擎)的 process_response 不再被调用。

异常

实测 · 本机执行输出
  没有谁处理异常:
   1. E       process_exception   exc=DownloadConnectionRefusedError
   2. C       process_exception   exc=DownloadConnectionRefusedError
   3. A       process_exception   exc=DownloadConnectionRefusedError
   4. spider  errback             exc=DownloadConnectionRefusedError
  D=750 在 process_exception 里返回 Response:
   1. E       process_exception   exc=DownloadConnectionRefusedError
   2. D       process_exception   exc=DownloadConnectionRefusedError
   3. E       process_response    url=x body=7
   4. D       process_response    url=x body=7
   5. C       process_response    url=x body=7
   6. A       process_response    url=x body=7
   7. spider  callback            body=rescued

下载异常按响应方向依次调用 process_exception,谁返回 Response 谁就终止异常处理,并让这个 Response 走正常的 process_response。

分步演示

中间件顺序
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Scrapy 2.19.0 / Twisted 26.4.0 / lxml 6.1.3 / parsel 1.12.1(临时虚拟环境);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、重定向、压缩、断连等行为,并记录收到的原始请求;每个实验在临时目录里新建 Scrapy 项目或单文件 spider,命令输出来自真实的 scrapy 进程;计时用 time.perf_counter,数字每次运行会有小幅抖动。用工厂函数生成带记录钩子的中间件类,通过 spider 的 custom_settings 加入;内置中间件顺序取自 Scrapy 日志的 “Enabled downloader middlewares”
    • 没有验证:async def 版本的 process_request(2.19 支持);from_crawler 里抛 NotConfigured;中间件抛异常(不是下载异常)时的顺序;重试、重定向这类内置中间件返回 Request 与自定义的相互影响(只看了内置顺序);数字相同的两个中间件的先后(同一个数字只能出现一次,字典键不同的话顺序由实现决定)
    京ICP备2026067502号-1京公网安备11010502063487号