先给结论:限流发生时,最该保护的往往不是“继续拿到新数据”,而是把已经成功返回的部分固化下来。具体做法取决于你的脚本能否判断每条结果是否完整——能判断的,就按条落盘并记录断点;不能判断的,就先暂停写入、只保留原始响应,等确认口径后再决定是否续跑。下面把两种条件下的选择、动作和例外分开说。
如果每次调用返回的是结构化记录,并且你能识别某条记录是否字段齐全,那么限流不是灾难,而是一次可控中断。此时正确的顺序是:先写盘,再记录断点,最后才考虑重试。
实施动作可以拆成三步。第一步,每拿到一条合格记录就立即追加写入本地文件或数据库,而不是等整批跑完再统一保存。第二步,在写入成功后记录该条的唯一标识或序号,形成断点位置。第三步,捕获限流类错误后停止发起新请求,把断点写入一个单独的状态文件。
这个动作的结果会直接决定下一步:因为断点存在,续跑时可以从上次成功的位置继续,而不必从头再查一遍;因为结果已落盘,即使后续长时间无法调用,你手里仍有一份可用数据。代价是写入更频繁,脚本结构略复杂,但对缺少完整数据或权限的场景更划算。
需要说明的是,断点只能证明“这条已成功写入”,不能证明“这条之后没有遗漏”。如果接口在限流前已经跳过了某些记录,断点续跑仍会留下空洞,所以续跑结束后应做一次数量与关键字段的核对。
另一种情况更常见:返回内容是非结构化文本、分页边界不清,或者你无法确认字段缺失是限流导致还是本来就没有。这时不要急着解析和入库,因为一旦按错误口径写入,后续很难区分哪些是真实结果、哪些是残缺记录。
更稳妥的做法是先把每次调用的原始响应整体保存,附上时间、请求参数和返回状态。此时不解析、不合并、不覆盖已有文件。等到限流解除或换用其他方式补齐后,再统一解析。
这样做的依据是:原始响应保留了全部可回溯信息,而解析后的结果一旦丢失上下文就无法复原。代价是占用更多存储,且短期内无法直接使用数据。例外是,如果原始响应体积过大或包含不宜长期保存的内容,可以只保留必要片段,但要同时记录片段对应的位置,否则等于没有保存。
不要凭感觉选方案,先看三组可区分的证据。
这三组证据里,只要“可重放性”为否,就应优先保存原始响应,哪怕它暂时不能直接用。
假设某脚本需要按关键词逐条查询并汇总,接口在返回若干条后开始限流。若脚本每拿到一条就写入 results.jsonl 并更新 checkpoint.txt,那么限流后停止请求,已写入的部分仍然可用,续跑从断点开始。若脚本把全部结果放在内存里、最后统一写出,那么限流时进程退出,内存中的结果全部丢失。这个对比说明的是保存时机的差别,不代表任何具体工具的现行行为。
还要注意一种反常现象:有时限流后请求量统计归零,看起来像“已经恢复正常”,但这可能只是脚本停止发请求,也可能是接口返回了空结果,还可能是统计口径延迟。请求量归零本身不能证明限流已解除,必须用一次小范围探测请求的结果来判断。
保护已有结果不等于一定要把任务跑完。出现以下情况时,继续续跑反而会扩大损失:
这些情况下,更合理的下一步是保留现有结果、标注未完成范围,等条件明确后再决定是补齐还是重新查询。对缺少完整数据或权限的读者来说,能交付一份范围清楚的部分结果,通常比一份来源不明的完整结果更安全。