重试、退避与限速:失败时该怎么做
重试的目的不是“直到成功”,而是在临时网络问题下提高可靠性,同时避免给对方服务制造更大压力。对访问控制、明确拒绝或不符合规则的页面,重试不是解决办法。
1. 先区分失败类型
| 情况 | 推荐动作 |
|---|---|
| DNS、连接或读取超时 | 有限次数重试,并逐次增加等待时间 |
429 Too Many Requests |
停止高频请求,优先遵守 Retry-After,否则人工评估后延长等待 |
500、502、503、504 |
可有限重试;仍失败则记录并结束该 URL |
401、403 |
不要尝试绕过;检查权限、条款或停止任务 |
404、410 |
通常记录为不存在,不重试 |
HTTP 标准定义了 Retry-After 响应头,服务器可用它指示客户端何时再次尝试。没有该字段也不意味着可以立即反复请求。
2. 指数退避
一种常见策略是每次失败后增加等待:
from time import sleep
for attempt in range(3):
try:
response = fetch_response(url)
break
except TemporaryFetchError:
if attempt == 2:
raise
sleep(2 ** attempt) # 1 秒、2 秒;实际任务应再加入随机抖动
实际生产任务通常还会加入随机抖动(jitter),避免多个客户端同时重试。无论采用哪种策略,都应设置最大尝试次数和总任务上限。
3. urllib3 的 Retry 配置
urllib3.util.Retry 提供了连接、读取、重定向和状态码重试的配置对象。若在 Requests 中使用它,需要通过 HTTPAdapter 显式挂载;不要误以为 Requests 默认会自动重试所有失败。
配置时应谨慎限定:只对明确可安全重试的方法和临时错误状态启用有限重试,并保留正常的请求间隔。对于学习脚本,先使用单次请求加人工检查,通常比复杂自动重试更合适。
4. 限速始终独立于重试
即使请求全部成功,也要在请求之间等待。限速是正常行为;退避是失败后的额外保护,两者不能互相替代。