requests重试必须加随机延时,因固定间隔易被识别为爬虫导致封禁;需通过自定义JitteredRetry类在sleep中注入抖动,配合合理次数、状态码过滤及动态延时策略实现隐蔽高效重试。
固定间隔重试(比如每次等 1 秒)容易被服务端识别为爬虫模式,尤其在批量请求同一接口时,IP 或账号可能被临时封禁。requests 默认不带重试逻辑,靠 urllib3 底层的 Retry 类实现,但它默认重试延迟是固定的(指数退避但无抖动),必须手动注入随机性才能有效绕过基础限流策略。
核心是在每次重试前插入一个随机等待时间,不能只靠 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),再配合外部随机 delayRetry 的 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)
随机延时不是万能解药,没控制好反而加剧服务端压力或触发更严惩罚:
X-RateLimit-Remaining 或 Retry-After 响应头,优先读它而不是依赖随机延时全局重试策略太粗,有时只想对某个特定请求加抖动。这时可在发请求前手动 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./link/46b315dd44d174daf561
7e22b3ac94ca')
真正难的是平衡成功率和隐蔽性:延时太短,重试无效;太长,效率崩盘;完全随机又可能撞上服务端的滑动窗口峰值。实际中建议先抓包看目标接口的限流周期(比如 60 秒内最多 10 次),再按窗口均摊请求+抖动。