requests需手动检查response.status_code==429,解析Retry-After头(数字转秒数或HTTP-date计算差值),无此头时fallback至默认延迟;禁用urllib3指数退避,改用自定义Session实现带jitter的指数退避重试。

直接重试没用,429 是服务器明确拒绝你当前请求窗口内的所有调用。关键不是“多试几次”,而是“等对的时间再试”,且这个时间得动态算、分散开、不撞车。
requests 怎么识别 429 并提取 Retry-After
requests 不会自动处理 429,必须手动检查 response.status_code。常见错误是只捕获 Timeout 或 ConnectionError,却漏掉 429 ——它属于 HTTP 响应,请求已送达,只是被拒。
Retry-After 头才是核心信号,但它有两种格式:
- 纯数字(如
"60"):直接转为秒数 - HTTP-date(如
"Wed, 21 Oct 2024 07:28:00 GMT"):需用email.utils.parsedate_to_datetime()解析后与当前时间差值计算 - 缺失该头时,不能跳过,必须 fallback 到一个合理默认值(如 1 秒),否则可能无限循环
urllib3.Retry 配置 429 重试的三个致命坑
用 urllib3.util.Retry 注册 429 重试看似省事,但极易踩坑:
- 必须显式把
429加入status_forcelist,默认不包含 - 必须设
backoff_factor=0,否则会把指数退避和Retry-After叠加,比如服务器说等 2 秒,你却睡了 4 秒 - 重试前无法读取
response.headers,所以没法在 retry 逻辑里解析Retry-After—— 它只管“重发”,不管“等多久”
结论:Retry 适合 5xx 类错误,429 必须自己接管流程。
封装带 jitter 的指数退避 Session
最可控的方式是继承 requests.Session,重写 send() 方法,在收到 429 后主动 sleep 再重发。这样能完全控制延迟计算、日志注入、监控埋点。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
关键逻辑包括:
- 提取
Retry-After,解析失败则 fallback 到base_delay * (2 ** retry_count) - 叠加随机抖动:
wait_time *= random.uniform(0.5, 1.5),避免多个实例同时苏醒 - 设置最大重试次数(如 5 次),防止卡死;每次重试前记录日志,含
retry_count和wait_time - 用
time.sleep()而非异步等待,除非你整个栈都是 async
示例片段:
class BackoffSession(requests.Session):
def __init__(self, base_delay=1, max_retries=5):
super().__init__()
self.base_delay = base_delay
self.max_retries = max_retries
<pre class="brush:python;toolbar:false;">def send(self, request, **kwargs):
for i in range(self.max_retries + 1):
response = super().send(request, **kwargs)
if response.status_code != 429:
return response
if i == self.max_retries:
return response
wait = self._get_wait_time(response, i)
time.sleep(wait)
def _get_wait_time(self, resp, retry_count):
ra = resp.headers.get("Retry-After")
if ra and ra.isdigit():
return float(ra)
elif ra:
dt = email.utils.parsedate_to_datetime(ra)
if dt:
return max(1, (dt - datetime.now(timezone.utc)).total_seconds())
return min(60, self.base_delay * (2 ** retry_count)) * random.uniform(0.5, 1.5)
分布式环境下怎么避免所有节点一起重试
单机退避解决不了集群问题。如果 10 个爬虫节点同时收到 429,又都按相同公式算出等 2 秒,结果就是 2 秒后 10 个请求再次打过去,继续 429。
必须引入外部协调:
- 用 Redis 实现分布式锁 + 共享计时器,比如所有节点竞争
crawler:429_lock,抢到的节点负责 sleep 并广播“可重试时间”,其他节点监听该时间戳 - 或改用全局令牌桶,所有请求先从 Redis 扣 token,token 耗尽即限流,自然分流
- 更轻量的做法:各节点在计算出本地 wait_time 后,再加一层随机偏移(如 ±30%),不依赖中心服务也能显著降低同步概率
真正难的不是算法本身,而是你怎么知道“这次 429 是 IP 级限流还是用户级配额耗尽”,前者换代理就行,后者必须降速——这得靠响应体内容、错误码字段或历史行为建模,Retry-After 给不了这个答案。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










