4.3 并发、限速与自动节流 本节摘要:节奏控制有两层:静态延迟按配置固定间隔发车,自动节流(AutoThrottle)按响应快慢实时调节间隔。本节讲两层机制的参数含义与协同方式,并给出"摸底试跑"的标准节奏——把爬速调到目标站容忍度的上限,而不是拍脑袋的数字。 第 3 章你配过 DOWNLOADDELAY,那是固定节奏。固定节奏的问题在于它假设目标站的承受力恒定——白天高峰与凌晨低谷显然不同。自动节流把这层假设去掉:框架持续测量响应延迟,延迟升高就自动放大间隔,延迟回落再收窄。本节讲清两层机制怎么协同。 静态层:三个参数的含义边界 三个参数管的是不同的东西,别混为一谈:总并发决定你的出口带宽怎么花,单域并发决定对端感受几路压力,延迟决定同域请求的疏密。
本节摘要:节奏控制有两层:静态延迟按配置固定间隔发车,自动节流(AutoThrottle)按响应快慢实时调节间隔。本节讲两层机制的参数含义与协同方式,并给出"摸底试跑"的标准节奏——把爬速调到目标站容忍度的上限,而不是拍脑袋的数字。
第 3 章你配过 DOWNLOAD_DELAY,那是固定节奏。固定节奏的问题在于它假设目标站的承受力恒定——白天高峰与凌晨低谷显然不同。自动节流把这层假设去掉:框架持续测量响应延迟,延迟升高就自动放大间隔,延迟回落再收窄。本节讲清两层机制怎么协同。
# settings 配置模块:静态节奏 CONCURRENT_REQUESTS = 16 # 引擎同时在飞的请求总数 CONCURRENT_REQUESTS_PER_DOMAIN = 4 # 单域名同时在飞数:单站采集的真闸门 DOWNLOAD_DELAY = 1.0 # 同一站点两次请求的间隔基数(秒)
三个参数管的是不同的东西,别混为一谈:总并发决定你的出口带宽怎么花,单域并发决定对端感受几路压力,延迟决定同域请求的疏密。对"抓一个站"的场景,起决定作用的是后两个——总并发调到 100,单域还是 1,速度纹丝不动。
延迟的随机抖动默认开启:实际间隔在 0.5 到 1.5 倍基数之间浮动。这是框架替你模拟人类节奏的细节,别关。
自动节流是一个闭环:测量响应延迟,与目标延迟比较,动态调整每域间隔。它有五个参数,真正要理解的是两个:
# settings 配置模块:自动节流 AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 1.0 # 起步间隔,试跑期的初始猜测 AUTOTHROTTLE_MAX_DELAY = 30.0 # 间隔天花板:再慢也不低于此速 AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0 # 目标:对单站维持约两路并发 AUTOTHROTTLE_DEBUG = True # 调参期打开,日志能看到每次调整
TARGET_CONCURRENCY 的语义值得说透:框架努力让"对每个域名的并发路数"稳定在这个值附近。响应快说明对端轻松,间隔收窄、并发上探;响应慢说明对端吃紧,间隔放宽。整个调节过程无需你参与,日志里的每次调整长这样:
DEBUG: AutoThrottle adjusting delay: start delay: 1.0s, target concurrency: 2.0 INFO: AutoThrottle adjusting delay: delay 0.6s (latency 0.34s) INFO: AutoThrottle adjusting delay: delay 2.4s (latency 1.87s)
延迟从 0.34 秒涨到 1.87 秒,间隔自动从 0.6 秒放大到 2.4 秒——对端吃紧时你的爬虫自动收油门。

两层机制的分工:静态层画底线(保底礼貌),动态层做微调(贴上限飞行)。开启节流后 DOWNLOAD_DELAY 仍生效——节流只在"对端允许更快"时收窄间隔,收窄的下限受延迟参数约束。我的推荐组合:延迟给个保守值,节流打开,目标并发从 2 起步。
调参不要靠算,靠摸底试跑:小并发跑半小时,读统计里的响应延迟分布与失败率,逐步上调目标并发,失败率抬头即回退。试跑时把 AUTOTHROTTLE_DEBUG 打开,每次调整都有日志可查,收工后关掉。
# 摸底试跑的观察点(统计摘要) 'downloader/response_received_count': 482, 'downloader/response_bytes': 31204480, 'downloader/request_latency/avg': 0.9, # 平均延迟稳定则节奏健康 'retry/count': 3, # 重试少说明没顶到容忍上限
💡 关键直觉:爬速上限不是你的配置算出来的,是对端的反馈量出来的。配置只画安全边界,AutoThrottle 负责贴边飞行。
还有一块拼图值得补上:延迟与节流的作用单位不是"整个爬虫",而是"插槽"(slot)。框架按域名自动建插槽,同一插槽内的请求共享一套延迟与节流参数,不同插槽互不干扰。所以"抓十个站、每个站延迟一秒"与"抓一个站延迟一秒"的总量语义完全不同——前者的总吞吐可以是后者的十倍。
这个机制解释了两类现象。其一,CONCURRENT_REQUESTS_PER_DOMAIN 与 DOWNLOAD_DELAY 都是插槽级参数,全局并发是唯一进程级的闸门;其二,个别站点响应慢时,只有它的插槽被节流拖慢,其他站点照常飞——天然的健康隔离。需要手工干预插槽时(比如对特殊地址用不同延迟),可以在中间件里改写请求的 download_slot 指派,让"同一域名下的敏感路径"独占一个插槽单独限速:
def process_request(self, request, spider): # 敏感接口单独开槽:慢一点,但绝不连累同域其他页面 if "/api/report" in request.url: request.meta["download_slot"] = "sensitive.slot" return None
调优的终点是接受一个事实:有些慢是必要的。目标站在协议里声明了延迟、在条款里写了频率,就按它的来——违规提速省下的时间,抵不上一次封禁的损失。我的经验排序:能用插槽隔离解决的,不动全局参数;能靠节流自适应解决的,不手工写死;实在要抢时间的采集(竞对价格监控之类),把并发预算花在多代理线路而不是压缩延迟上——前者扩大的是合法通道,后者透支的是对方容忍度。
背景:新站点接入,预估日更八千页,要求六小时内跑完。操作:小样本五百页摸底,先按默认参数跑十分钟——平均延迟 0.4 秒、零失败,看起来很健康;把目标并发从 2 提到 4,延迟均值微涨到 0.5 秒、依旧零失败;提到 6 时出现第一个 429,回退到 5,观察十分钟平稳。锁定目标并发 5,全量跑四小时二十分完成。解读:三次上调各是一次测量,429 的出现即是对方 tolerances 的边界信号——守住"出现即回退"的纪律,边界探索就是安全的。变式:对方偶尔抖动时,不要把参数贴着边界飞,留一档余量给 AutoThrottle 自行消化;边界只能探,不能住。
节奏稳了,还剩隧道最后一关:票失联了怎么办。下一节收拾失败件——重试、超时与错误处理。