requests重试必须加随机延时,因固定间隔易被识别为爬虫导致封禁;需通过自定义JitteredRetry类在sleep中注入抖动,配合合理次数、状态码过滤及动态延时策略实现隐蔽高效重试。

requests 重试时为什么必须加随机延时
固定间隔重试(比如每次等 1 秒)容易被服务端识别为爬虫模式,尤其在批量请求同一接口时,IP 或账号可能被临时封禁。requests 默认不带重试逻辑,靠 urllib3 底层的 Retry 类实现,但它默认重试延迟是固定的(指数退避但无抖动),必须手动注入随机性才能有效绕过基础限流策略。
用 urllib3.Retry + random 混合构造抖动重试
核心是在每次重试前插入一个随机等待时间,不能只靠 backoff_factor。推荐在 Retry 的 sleep 行为里劫持或封装一层:
- 继承
urllib3.util.retry.Retry,重写sleep方法,在调用父类 sleep 前加time.sleep(random.uniform(0.5, 2.0)) - 或者更轻量:用
requests.adapters.HTTPAdapter的max_retries接收自定义Retry实例,该实例的backoff_factor设为较小值(如 0.3),再配合外部随机 delay - 注意:不要在
Retry的allowed_methods中漏掉'POST'(默认只重试 GET/HEAD),否则表单提交类请求不会重试
示例关键片段:
import time import random from urllib3.util.retry import Retry from requests.adapters import HTTPAdapter from requests import Sessionclass JitteredRetry(Retry): def sleep(self, response=None): if self.backoff_factor > 0: base_sleep = self.get_backoff_time() jitter = random.uniform(0.5, 1.5) # ±50% 抖动 total_sleep = base_sleep * jitter time.sleep(max(0.3, total_sleep)) # 至少睡 300ms
session = Session() adapter = HTTPAdapter(max_retries=JitteredRetry( total=3, backoff_factor=0.5, allowed_methods=frozenset(['GET', 'POST', 'PUT', 'DELETE']) )) session.mount('https://', adapter) session.mount('http://', adapter)
避免重试放大问题的三个硬约束
随机延时不是万能解药,没控制好反而加剧服务端压力或触发更严惩罚:
- 总重试次数别超 3–4 次 —— 多数限流是按窗口计数,反复刷会直接进黑名单
- 单次最大延时建议 ≤ 3 秒,否则用户等待过长,且长延时在高并发下易堆积请求队列
- 对 429(Too Many Requests)、403、503 等状态码显式重试,但跳过 400、401、404 —— 这些是客户端错误,重试无效
- 如果目标接口有
X-RateLimit-Remaining或Retry-After响应头,优先读它而不是依赖随机延时
requests.Session 里混入 request-level 延时更灵活
全局重试策略太粗,有时只想对某个特定请求加抖动。这时可在发请求前手动 sleep,绕过 Retry 机制:
- 用
random.expovariate(lambd)生成符合泊松过程的随机间隔,比均匀分布更接近真实用户行为 - 对敏感接口,可结合上一次响应头里的
date或server时间戳做动态 delay 计算 - 务必把
time.sleep()放在session.request()调用之前,而非之后 —— 否则失败后才睡,起不到“错峰”作用
例如:
import random import time from requests import Sessionsession = Session()
模拟用户自然间隔:均值 1.2 秒,但有波动
delay = random.expovariate(1.0 / 1.2) time.sleep(delay) resp = session.get('https://www.php.cn/link/46b315dd44d174daf5617e22b3ac94ca')
真正难的是平衡成功率和隐蔽性:延时太短,重试无效;太长,效率崩盘;完全随机又可能撞上服务端的滑动窗口峰值。实际中建议先抓包看目标接口的限流周期(比如 60 秒内最多 10 次),再按窗口均摊请求+抖动。










