先给结论:限流发生时,不要立刻重跑整个任务,而要把“已经拿到的结果”和“还没拿到的任务”分开处理。已有结果先落盘并标记来源与时间,未完成部分改为可续跑的分片队列;确认限流是暂时性还是策略性之后,再决定保留、改写还是退出这条调用链路。
限流本身不说明你的脚本写错了,也不说明对方要永久拒绝你。常见原因至少有四类:单位时间请求数超限、并发连接数超限、账号或密钥权限不足、以及对方对特定接口收紧了调用策略。这四类的处理方式完全不同,但表面现象都可能是返回错误码或直接断开。
可区分的证据是:如果降低频率后请求恢复正常,多半是速率问题;如果换密钥、换账号仍然被拒,可能是权限或策略问题;如果只有部分接口失败、其他接口正常,说明限制作用在特定端点上。把这几条分别测一遍,比反复重试更能定位原因。
这一步的实际动作是:暂停新请求,只做小流量探测。探测结果决定下一步是继续保留原链路,还是准备改写调用方式。
限流时最容易被破坏的不是没拿到的数据,而是已经拿到的数据。很多脚本在重试时会重新写入同一个文件或同一张表,一旦中途失败,旧结果被部分覆盖,反而比一开始就没跑更糟。
保留成立的前提是:已有结果仍然对应你要回答的问题,字段结构没有变化,且你能说清每一条结果的采集时间和来源参数。满足这些条件时,优先做三件事:
这样做的影响是:即使后续调用长时间不可用,你手上的结果仍然可交付、可核对。反过来,如果结果没有来源标记,一旦对方口径调整,你无法判断哪些数据还能用,保留就失去了意义。
如果限流是速率问题,改写通常比退出更划算。改写的核心不是换工具,而是把“一次性大批量”改成“小步、可中断、可续跑”。
具体做法是给每个任务分配一个稳定的标识,处理成功就记录完成状态,下次启动时跳过已完成项。伪代码可以写成这样:
for task in tasks: if done(task.id): continue; result = call(task); save(task.id, result); mark_done(task.id)
这段逻辑的价值在于:限流中断后重新运行,不会重复消耗配额,也不会重复写入。适用前提是你有稳定的任务标识,且保存动作在调用成功之后立即执行。如果任务本身没有唯一标识,或者结果依赖前后顺序,就需要先补上标识或调整批次划分,再谈改写。
改写后要观察一个信号:同样的限流条件下,单次运行能推进的完成量是否稳定。如果仍然频繁中断,说明限制可能不是速率,而是权限或策略,此时继续改写收益有限,应转向退出评估。
退出不是失败,而是一种取舍。以下条件同时出现时,继续投入调用通常不划算:限流长期存在且无法通过降低频率缓解;该接口返回的内容对当前决策已经不再关键;或者你已经有一份足够支撑结论的旧结果,只是缺少最新一批。
退出的正确姿势是“保留证据、停止增量”。把已有结果连同采集时间、参数和已知缺口一起归档,明确标注哪些结论有数据支撑、哪些是空白。这样做的结果是:后续如果有人质疑数据完整性,你能直接指出缺口范围,而不是让整份结果因为不完整而被整体否定。
需要避免的做法是:因为拿不到新数据,就把旧数据当成最新数据继续使用,且不标注时间。这会让保留变成误导,比直接退出更危险。
假设你有一个按关键词批量调用接口的脚本,原计划处理一千个任务,跑到四百个时开始持续被限流。
如果降低并发后能继续推进,选择改写:把剩余六百个拆成小批次续跑,已完成的四百个保持不动。如果降低并发仍然全部被拒,但旧结果已覆盖主要关键词,选择退出增量:归档这四百个结果,标注缺口,不再消耗时间。如果旧结果结构已经和当前需求不一致,且新调用也无法恢复,那么保留和改写都不成立,应重新评估这批结果是否还有使用价值。
这三种路径的共同点是:都不重跑已完成部分,都不在没有证据的情况下假设限流会自行消失。
限流发生时,先固化已有结果、再判断限制性质、最后在改写与退出之间做选择,比直接重跑更稳妥。具体工具的重试机制、配额规则和当前可用状态需要以你实际使用的服务说明为准,不要凭印象假设。