bigywater.tech
Scrapy · 17

下载器 slot:每个域名一个槽位怎么工作

每个域名一个下载槽,延迟和并发都按槽算。槽的名字是怎么取的?能不能用 meta 把几个域名合并成一个槽、或者把一个域名拆开?空闲的槽什么时候被回收?这篇一项项量出来。

7.9 → 3.8 s
同样 40 个请求,合并成一个槽与拆成两个槽的总耗时(延迟 0.2 s)
60 s
槽的回收检查周期(首次回收在 60 s,另一个在 120 s)
51 ms
DOWNLOAD_SLOTS 给自定义槽 api 设 delay 0.05 后的实测间隔

槽名

实测 · 本机执行输出
  [默认]
    http://a.com/x            meta={}                          → 槽名 'a.com'
    https://a.com:8443/y      meta={}                          → 槽名 'a.com'
    http://www.a.com/z        meta={}                          → 槽名 'www.a.com'
    http://A.COM/p            meta={}                          → 槽名 'a.com'
    http://[::1]:8080/        meta={}                          → 槽名 '::1'
    http://a.com/x            meta={'download_slot': 'myslot'} → 槽名 'myslot'
    http://localhost:端口/h     meta={}                          → 槽名 'localhost'
    http://127.0.0.1:端口/h     meta={}                          → 槽名 '127.0.0.1'
  [CONCURRENT_REQUESTS_PER_IP=2(需要 ScrapyPriorityQueue)]
    http://a.com/x            meta={}                          → 槽名 'a.com'
    https://a.com:8443/y      meta={}                          → 槽名 'a.com'
    http://www.a.com/z        meta={}                          → 槽名 'www.a.com'
    http://A.COM/p            meta={}                          → 槽名 'a.com'
    http://[::1]:8080/        meta={}                          → 槽名 '::1'
    http://a.com/x            meta={'download_slot': 'myslot'} → 槽名 'myslot'
    http://localhost:端口/h     meta={}                          → 槽名 '127.0.0.1'
    http://127.0.0.1:端口/h     meta={}                          → 槽名 '127.0.0.1'

Downloader.get_slot_key 先看 request.meta["download_slot"],没有就取 URL 的主机名;设了 CONCURRENT_REQUESTS_PER_IP 则换成解析出的 IP。

合并与拆开

实测 · 本机执行输出
  (a) 两个主机名各 20 个,默认槽:共 40 个请求,总耗时 4.0 s
  (b) 同上,全部 meta download_slot="shared":共 40 个请求,总耗时 7.9 s
  (c) 一个主机名 40 个,按奇偶拆成槽 even / odd:共 40 个请求,总耗时 3.8 s
  理想值:(a) 20×0.2 = 4.0 s(两个槽并行);(b) 40×0.2 = 8.0 s(一个槽);(c) 20×0.2 = 4.0 s(两个槽并行)

延迟(和并发限制)只在同一个槽内生效。想让多个域名共享礼貌间隔,就用 meta 指到同一个槽;想把同一个域名当作几个独立的下载通道,就拆成多个槽。

DOWNLOAD_SLOTS

实测 · 本机执行输出
  DOWNLOAD_SLOTS={"api": {"delay": 0.05}},20 个请求都指到槽 api:间隔中位数 51 ms,总耗时 1.0 s(全局 DOWNLOAD_DELAY 是 0.2,若不生效应是 4.0 s)

按槽名给 delay、concurrency、jitter 设置覆盖值,槽名可以是主机名,也可以是自定义的。

回收

实测 · 本机执行输出
       0 s  槽 ['127.0.0.1', 'localhost']
      10 s  槽 ['127.0.0.1', 'localhost']
      20 s  槽 ['127.0.0.1', 'localhost']
      30 s  槽 ['127.0.0.1', 'localhost']
      40 s  槽 ['127.0.0.1', 'localhost']
      50 s  槽 ['127.0.0.1', 'localhost']
      60 s  槽 ['localhost']
      70 s  槽 ['localhost']
      80 s  槽 ['localhost']
      90 s  槽 ['localhost']
     100 s  槽 ['localhost']
     110 s  槽 ['localhost']
     120 s  槽 []

每 60 秒检查一次,回收没有活动请求且最后一次放行超过 60 秒的槽。回收是“新建槽、重新计时”的意思,不影响正确性。

分步演示

下载槽
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。槽名取自 Downloader.get_slot_key;合并/拆开实验用 DOWNLOAD_DELAY = 0.2、DOWNLOAD_DELAY_JITTER = 0 并读取服务器端到达时间;回收实验真实等待 130 秒
    • 没有验证:CONCURRENT_REQUESTS_PER_IP 在 2.19 里的弃用(单元测试里见过 “The CONCURRENT_REQUESTS_PER_IP setting is deprecated, use CONCURRENT_REQUESTS_PER_DOMAIN instead.”,真实抓取的日志里没有再看到);IP 槽对 DNS 多记录(多个 A 记录)的主机的行为;槽回收与 AutoThrottle 状态的关系(下一批文章);回收周期 _SLOT_GC_INTERVAL 是类属性,没有改过它
    京ICP备2026067502号-1京公网安备11010502063487号