429的本质:请求过多,而非IP被封
采集任务把并发从5提高到30后,日志中开始大量出现429 Too Many Requests;即使更换一批代理出口重新运行,429仍然存在。这是采集团队常见的示意场景,并非实测记录。看到429就加大重试力度,往往适得其反:限流方承受的压力不会下降,反而会上升。
正确的处理顺序是:暂停密集重试、保存完整响应、定位产生方、按等待与额度规则处理、用小流量验证恢复。很多人首先会问429怎么解决,但答案取决于状态码是谁返回的:目标站点、CDN、代理网关,三者的含义和处理方式完全不同。误判产生方的代价很高——对网关限流只降速没有用,对目标限流只换出口也没有用。
RFC 6585第4节只定义了“给定时间内请求过多”,并未规定如何识别与计数,因此429不等于IP被封。能产生429的有三层:目标站点或其前方的CDN、代理网关、客户端包装层。Cloudflare文档说明边缘节点可生成429;urllib3的Retry会把429纳入自动重试。
第一步:快速判断429是谁返回的
同一个HTTP 429状态码,来自不同环节含义不同。可对照下表逐行操作,每次只改一项:
- 带Retry-After、提示访问频繁:可能来自目标站点或CDN限流,应保存响应,按等待时间降速。
- 带供应商细分码或专有头:可能为代理网关主动限流,应读细分码,核对账户与zone额度。
- 换出口后比例不变:可能按账号或zone共享额度计数,应核对总速率与额度,停止盲目更换出口。
- 提高并发后集中出现:说明总压力超过容忍度,应暂停重试,降低速率与并发。
- 仅某客户端或版本出现:可能是客户端包装层重试叠加,应核对客户端版本与重试配置。
定位需要完整证据,建议保存以下字段:
- 时间与脱敏URL;
- 状态码与Retry-After响应头;
- 请求ID与正文error.code;
- 客户端版本与代理端点;
- 并发、速率与已重试次数。
目标限流:先降总压力
确认429来自目标站或CDN后,方向只有一个:降低总请求压力。零散的“慢一点”不够,需要成体系地调整:
- 停掉重复失败的任务,避免继续消耗额度;
- 核对目标官方政策与robots约定,确认被允许的频率范围;
- 同时限制速率与在途并发,只限其一容易留下缺口;
- 多个worker共用同一个限速队列——各自低速不等于全局低速,要按凭证汇总统计;
- 做缓存与去重,用增量采集代替全量重抓。
恢复判定不能只看单次结果:需在低负载观察窗口内看到429比例明显下降,且持续拿到有效业务数据才算恢复;单次200、空白页、验证码页都不算数。降下来的速率要覆盖所有入口,包括定时任务、手动补跑与其他环境的副本。
Retry-After与退避的落地方式
目标返回429时可能带Retry-After头,格式有两种:整数秒,或一个HTTP日期。例如:
- Retry-After: 120
- Retry-After: Wed, 04 Nov 2026 08:00:00 GMT
头部有效就按秒数或日期等待,超过任务总预算则终止或重新排队。头部缺失时才使用有限指数退避:时长按倍数递增、叠加随机抖动,并设置最大尝试次数与总预算。多个实例要共享冷却状态,避免同一时刻集体重试形成重试风暴。
以下是一个基于requests的示意实现,仅演示读操作:
import random
import time
import requests
MAX_ATTEMPTS = 5
BUDGET_SECONDS = 300
def get_with_backoff(url):
deadline = time.monotonic() + BUDGET_SECONDS
delay = 1.0
with requests.Session() as session:
for _ in range(MAX_ATTEMPTS):
resp = session.get(url, timeout=(5, 20))
if resp.status_code != 429:
return resp
retry_after = resp.headers.get("Retry-After", "")
if retry_after.isdigit():
wait = int(retry_after)
else:
wait = delay + random.uniform(0, delay)
delay = min(delay * 2, 60)
if time.monotonic() + wait > deadline:
raise RuntimeError("超出总预算,终止或重新排队")
time.sleep(wait)
raise RuntimeError("达到最大尝试次数")
# 仅演示 GET/HEAD;POST 要确认接口幂等方可重试
POST请求要确认接口幂等性,否则重试可能产生重复数据;退避参数按目标官方政策与自身任务预算设定,不要无限重试。
代理网关429:为什么换IP没用
429也可能由代理服务主动返回,并附带供应商自己的细分码。例如:
- sr_rate_limit:按目标host动态节流;
- repeat_query_rejected:短时间内的重复查询被拒,要求至少等待15秒;
- bucket_rate_limit:按zone限速;
- client_10110:账户层限制。
状态码相同,细分码不同,含义与处理方式就不同。网关侧429的处理按四步走:
- 读响应里的细分码与专有响应头,确认限制类型;
- 核对账户与zone的剩余额度和总速率;
- 按规定时长等待并降低请求速率;
- 额度确实不够,再向供应商申请扩容。
为什么换IP常常没用:账户或zone级别的共享限制不会因为换出口而增加。有过明确反例——同一账号下共享额度,换多少个出口节点,429依旧。这类限制的出路只有等待、降速或扩容;申请扩容也要附上细分码、速率与等待记录,才能判断是峰值还是常态不足。
动态IP与429的真实关系
动态IP是出口管理能力,不是通用的429修复手段,解决的是出口层问题:节点失效替换、出口故障定位、授权多出口任务的调度。只有证据表明429与特定出口相关时,换节点才是对症分支;目标按账号计数时换出口没有意义。
在明确的网络分支里,值得核对节点可用性、会话保持、生命周期与全局调度;“轮换即可突破限制”并不成立。选择这类服务时,应核对接入配置、会话保持需求、节点可用性与额度文档,而非“高信誉出口即可修复”的宣传;固定IP同样不是通用修复。
降速之后仍然429,可按清单继续查:
- 是否有其他worker共用同一凭证,绕过了本进程限速;
- 重试是否层层叠加,实际速率远高于配置值;
- Cookie会话是否不一致,被目标判定为异常;
- 客户端版本是否过旧、与网关不兼容;
- 要求的等待时间是否真的结束了。
常见问题解答
429是不是IP被封了?
不是。429只表示请求过多,可能按IP、账号或会话计数;封禁更常表现为403或连接失败。
为什么换成动态IP之后还是429?
换出口只改变来源IP;限额记在账号或zone上时,共享额度不随换出口增加。
没有Retry-After要等多久?
没有统一标准。惯例是有限指数退避:起步翻倍、加随机抖动,设最大尝试与总预算,超预算就终止或重新排队。
降了并发为什么还报429?
常见原因是多worker各自限速、全局超标,重试叠加抬高实际速率,或其他任务共用同一凭证。应按全局总速率核对。
收到200就算恢复了吗?
不算。200可能是登录页、验证码页或空白页;要看窗口内429比例下降且持续拿到预期字段。
总结
429 Too Many Requests不是IP被封的同义词,而是“请求过多”的限流信号,可能来自目标站、CDN、代理网关或客户端包装层中的任何一层。处理主线是暂停密集重试、保存响应、定位产生方、按等待规则退避、小流量验证。目标限流就降总压力,速率与在途并发一起限,多worker共享限速队列;网关限流就读细分码、核账户与zone额度,共享限制不会随换出口消失。动态IP是出口管理能力而非通用修复,只在出口相关的分支里讨论节点可用性与会话保持。需要稳定对照环境排查限流的团队,可了解IPxSS动态住宅IP方案(https://www.ipxss.com/),把节点管理与全局调度交给服务端,将排查精力留给真正的限流来源。