结论先说:如果脚本已经拿到一批结果,突然遇到限流,最该保护的不是“继续请求”,而是把已获得的结果先落盘、标记状态、停止重试。只有当已有结果可以稳定恢复、且剩余请求可以分批延后时,继续调用才成立。否则,继续重试往往会让已有结果和后续结果一起丢失。
限流不一定来自同一个地方。常见有三种:一是接口层返回明确的频率限制信息;二是网络层出现超时、连接重置;三是脚本自身并发过高,把请求压进了不稳定状态。三者的处理方式不同。
如果返回信息里带有等待时间或剩余额度,优先按它执行,不要自行猜测。若没有明确信息,只看到失败,就要先降低并发,而不是立刻换 IP 或换账号。后两种动作可能让问题更难定位,也可能触发更严格的限制。
一个可区分的证据是:把同一批请求拆成单线程、低频率重放。如果单线程能稳定通过,说明问题更可能在并发和节奏;如果单线程仍然失败,才需要检查接口状态、凭证或网络出口。
脚本运行中,结果通常先留在内存里。限流一来,进程退出或重试覆盖,内存里的结果就可能丢失。保护已有结果的最低动作是:每完成一条或一小批,就写入本地文件或数据库,并记录这条结果对应的请求参数、时间、状态。
状态至少要区分三类:成功、失败、未完成。不要把失败和未完成混在一起。失败通常表示请求已发出但未拿到有效结果;未完成表示还没轮到它。这个区分会直接影响下一步:失败项可以按策略重试,未完成项可以延后处理。
假设一个脚本要处理 200 个查询词,已经成功 80 个,限流后全部卡住。如果这 80 个结果没有落盘,重启后只能从零开始;如果已落盘,重启后可以只处理剩余 120 个。这个例子的数字只是说明比较方法,不代表任何工具的实际额度。
遇到限流时,无限重试是最危险的做法。更稳妥的方式是:设置最大重试次数、每次重试之间的等待时间,并规定重试只针对失败项。等待时间可以逐步增加,但不要写成固定值后就放任不管。
同时要保留最后一次有效结果。如果某条结果第一次成功、第二次因为重试被覆盖成失败,那就是保护失败。写入时可以用“只追加、不覆盖成功记录”的方式,或者先写临时文件,确认完整后再替换。
一个实际动作是:把成功结果写入 results_done,把失败项写入 results_failed,把未完成项写入 results_pending。下次启动时先读 results_done,跳过已完成项,只处理 results_failed 和 results_pending。这个动作的结果是:限流只影响剩余任务,不会抹掉已经拿到的内容。
继续调用成立的条件比较窄:已有结果已经完整落盘并可校验;限流信息明确给出了可等待的时间;剩余请求量不大,且可以分批执行;脚本能够在中途停止并恢复。缺少其中任何一项,继续调用都可能让状态更乱。
反例也要说清楚:如果结果只存在内存、没有断点记录,或者失败项和成功项混在一起,那么继续重试并不会“多拿一点”,只会让下一次恢复更困难。此时正确动作是停止脚本、保存现场、补充落盘逻辑,再重新开始。
另一个容易被忽略的条件是:如果限流来自账号或凭证级别,而不是单次请求频率,那么换并发、加等待都不一定能解决。需要先确认限制范围,再决定是降低频率、拆分任务,还是暂停到下一个可用时段。具体限制信息需要以实际返回和工具说明为准。
限流处理完后,不要只修一次脚本就结束。更稳的做法是留下一个可重复执行的恢复流程:启动时读取已完成记录,校验结果数量,列出待处理项,按低并发重新请求,遇到限流立即停止并保存。
可以用一个简单清单检查:已完成结果是否可读;失败项是否单独保存;未完成项是否可继续;重试是否有上限;停止后是否能从断点恢复。只要其中一项不成立,就先补这项,而不是继续扩大请求量。
限流本身不是结果丢失的原因,缺少落盘和断点才是。先把已有结果固定下来,再决定是否继续调用,后续每一步才有可恢复的基础。