知其然,知其所以然
Scrapy 内部原理 2026-10-12

引擎主循环:一个请求从调度器到下载器再回到 spider 的路径

在 Scrapy 2.19 上给引擎的每一层挂钩子,记录一个请求的完整路径(29 个事件);默认队列是后进先出、不是严格深度优先;CONCURRENT_REQUESTS 从 1 到 24 总耗时 7.34 s 降到 0.34 s;回调返回字符串和整数也被当成 item;DontCloseSpider 之后 spider_idle 每约 5 秒重复一次。

Scrapy 引擎 调度器 下载器 信号 spider_idle 真实验证 动画演示
Scrapy 内部原理 2026-10-12

Twisted reactor 与 asyncio reactor:单线程里怎么同时处理几百个请求

在 Scrapy 2.19 上对比三种运行方式:默认 asyncio reactor、SelectReactor、TWISTED_REACTOR_ENABLED=False(无 reactor、aiohttp 下载);单线程并发 100 个慢请求只要 0.6 s;回调里 time.sleep(0.1) 让 20 个请求从 0.34 s 变成 2.39 s;SelectReactor 下 async 回调 await asyncio.sleep 全部抛 RuntimeError。

Scrapy Twisted asyncio reactor 事件循环 真实验证 动画演示
Scrapy 内部原理 2026-10-12

CrawlerProcess 与 CrawlerRunner:谁来启动和关闭 reactor

实测 Scrapy 2.19 的四个启动类:start() 只能调一次(第二次 ReactorNotRestartable);CrawlerProcess 并发两个 spider 0.62 s、CrawlerRunner 顺序 1.39 s;CrawlerRunner 不 stop reactor 进程永远不退出;asyncio.run 配 asyncio reactor 会卡死;第一次 SIGINT 优雅停止、第二次强制退出。

Scrapy CrawlerProcess CrawlerRunner reactor 信号 真实验证 动画演示
Scrapy 内部原理 2026-10-12

settings 的优先级:默认值、项目、spider、命令行谁覆盖谁

实测 Scrapy 2.19 的 settings 优先级:default 0 / command 10 / addon 15 / project 20 / spider 30 / cmdline 40;startproject 模板默认 DOWNLOAD_DELAY = 1;spider.update_settings 里用 "cmdline" 优先级输给 -s,要用 41 才赢;字典型设置整体替换;-s 的值是字符串('False' 当 if 是真);设置在 start() 里已冻结。

Scrapy settings 优先级 custom_settings 命令行 真实验证 动画演示
Scrapy 内部原理 2026-10-12

Spider 的起始请求:start() 与 start_requests 的惰性消费

实测 Scrapy 2.19 的 start():默认 start() 对 start_urls 的重复项也会请求(dont_filter=True);只写旧的 start_requests 的 spider 什么也不抓、没有任何警告;start() 一次性把 300 个请求塞进调度器(积压 295),用 needs_backout 退让后积压为 0;start 请求走独立的 FIFO 队列,其它请求优先。

Scrapy Spider start start_requests start_urls 调度器 真实验证 动画演示
Scrapy 内部原理 2026-10-12

Request 的 callback、errback、cb_kwargs 与 meta

实测 Scrapy 2.19 的 callback / errback / cb_kwargs / meta:404、500 走 errback(HttpError),超时、连接被拒、DNS 失败也走 errback(新的 Download…Error 异常类);回调里抛异常不触发 errback;cb_kwargs 与 meta 在重定向和重试后保留;response.follow 什么都不继承。

Scrapy Request callback errback cb_kwargs meta 真实验证 动画演示
Scrapy 内部原理 2026-10-12

Response 与编码:TextResponse 怎么决定用哪种编码解码

实测 Scrapy 2.19 怎么决定响应编码:构造参数 > BOM > HTTP 头 > <meta> > 自动探测;头写错时不报错但满是替换字符,JSON 的键会因此合并丢数据;短 GBK 文本被自动探测成 cp949;gb2312 → gb18030、latin1 → cp1252;只有文本类型的响应才有 .text。

Scrapy Response 编码 charset TextResponse 真实验证 动画演示
Scrapy 内部原理 2026-10-12

调度器:内存队列、磁盘队列与优先级

实测 Scrapy 2.19 的调度队列:有 JOBDIR 时 21 个请求全进磁盘队列;priority 大的先出、同优先级默认后进先出;不可序列化的请求退回内存队列(默认无日志)并因“内存先于磁盘”打乱优先级;默认的 DownloaderAwarePriorityQueue 让优先级只在域名内有效,且与 CONCURRENT_REQUESTS_PER_IP 冲突。

Scrapy 调度器 队列 优先级 JOBDIR DownloaderAwarePriorityQueue 真实验证 动画演示
Scrapy 内部原理 2026-10-12

去重:RFPDupeFilter 的请求指纹怎么算

实测 Scrapy 2.19 的请求去重:参数顺序、#片段、主机大小写算同一个,:80、末尾 /、http/https、www. 算不同;POST 体逐字节比较、请求头和 meta 默认不参与;requests.seen 每个指纹 22 字节;自己写的 start() 默认不带 dont_filter;100 万个指纹的 set 占 86.6 MB。

Scrapy 去重 RFPDupeFilter 请求指纹 JOBDIR dont_filter 真实验证 动画演示
Scrapy 内部原理 2026-10-12

dont_filter:什么时候该绕过去重

实测 dont_filter 何时该用:有环的站点全部 dont_filter=True 会一直转(1 秒 799 个请求),要靠 DEPTH_LIMIT / CLOSESPIDER_* 刹车;重试请求自带 True;重定向沿用原请求的值,重定向到已抓过的 URL 会被去重吃掉,回调不被调用;轮询同一个 URL 必须加。

Scrapy 去重 dont_filter 重试 重定向 CLOSESPIDER 真实验证 动画演示
Scrapy 内部原理 2026-10-12

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

实测下载器中间件的调用顺序:process_request 按数字从小到大、process_response 反过来;595 看到 gzip 的 64 字节、585 看到解压后的 2613 字节;process_request 返回 Response 时仍然调用全部 process_response;返回 Request 会重新走一遍中间件链;process_exception 按响应方向调用。

Scrapy 下载器中间件 process_request process_response 中间件顺序 真实验证 动画演示
Scrapy 内部原理 2026-10-12

Spider 中间件:它夹在引擎和 spider 之间做了什么

实测 Scrapy 2.19 爬虫中间件:input 从小到大、output 从大到小;input 抛异常直接进 errback 且不算 spider 异常;output 可以过滤请求和 item;process_spider_exception 按 C、B、A 调用,返回可迭代对象即可终止;同步版 process_spider_output 在 2.19 启动即 TypeError。

Scrapy 爬虫中间件 process_spider_input process_spider_output process_spider_exception 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号