requests.get()循环调用易触发429因服务端按毫秒级滑动窗口限流,且默认无user-agent被识别为爬虫;固定sleep无效于多线程/异步场景,ratelimit装饰器不跨进程且无视响应头,需依赖x-ratelimit-reset精准休眠。

为什么requests.get()直接循环调用会立刻被429拦截
因为OpenAI、百度云、天气API等服务端根本不关心你本地有没有sleep,只看HTTP请求头和到达时间戳。requests.get()默认不带User-Agent,发出去的请求被识别为爬虫;更关键的是,连续5次调用间隔若小于1秒,哪怕每次只传一个token,也大概率触发429 Too many requests——这不是“运气差”,是服务端滑动窗口计数器在毫秒级就完成了判定。
- 错误现象:刚跑两轮就返回
{"error":{"message":"Rate limit exceeded","type":"rate_limit_exceeded"}} - 根本原因:没模拟真实用户行为,且未对请求节奏做任何约束
- 参数差异:
requests.get(url)和requests.get(url, headers=headers, timeout=10)在服务端眼里是两类流量
time.sleep(1)为什么经常失效
固定延迟看似简单,但实际踩坑最多:它只控制“本机代码执行节奏”,不解决并发、重试、多进程共享限流状态等问题。比如你在for循环里写time.sleep(1),但用了ThreadPoolExecutor开8个线程,结果是每秒发8个请求——sleep被并行绕过了。
- 常见错误:在异步协程中混用
time.sleep()(应改用asyncio.sleep()) - 性能影响:固定sleep会让低频请求白白等待,吞吐量下降30%以上
- 兼容性问题:部分API(如GPT-4)按
tokens per minute限流,sleep 1秒无法换算成token配额
ratelimit装饰器在生产环境为何容易漏判
@limits(calls=10, period=60)这类装饰器只在单进程内有效,一旦脚本被systemd重启、或用supervisor拉起多个实例,每个进程都从0开始计数,总请求数瞬间翻倍。它也不感知服务端返回的X-RateLimit-Remaining响应头,完全靠本地倒计时硬控。
- 典型场景:部署到两台服务器,各跑1个Python进程 → 实际每分钟20次,超出API配额
- 容易忽略的细节:
period单位是秒,但OpenAI的tpm限制是按自然分钟滚动窗口计算的 - 替代方案:优先读取响应头里的
X-RateLimit-Reset时间戳,而不是猜下次能发的时间
为什么加了随机delay还是被封IP
单纯在time.sleep(random.uniform(1, 3))基础上加随机,只能骗过最基础的规则引擎。真正被封IP,往往是因为:连续请求的源IP没变、没轮换Authorization token、没处理Set-Cookie带来的会话绑定。服务端看到同一IP+同一token+高频请求,直接进黑名单队列。
- 关键遗漏:没检查响应是否含
X-RateLimit-Limit和X-RateLimit-Used字段 - 必须动作:把
requests.Session()换成带持久cookie的实例,并定期刷新token - 隐蔽风险:日志里打印
response.headers比打印response.text更能提前发现限流征兆
X-RateLimit-Reset时间戳后做精准休眠,而不是凭经验sleep几秒。大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











