结论先行:如果你的脚本已经拿到一部分结果,限流出现后最该做的不是继续重试,而是立刻停止请求、把内存里和临时文件里的数据落盘并标记断点。只有当限流是偶发的、间隔足够长、且你确认后续请求不会覆盖已有结果时,才值得原地等待重试;如果限流持续出现或返回内容开始异常,继续跑只会让已有结果被污染,这时应改为分批落盘加断点续跑。
限流至少有两种形态,处理方式完全不同。第一种是明确的拒绝响应,比如状态码或返回体里直接告诉你请求过于频繁;第二种是静默降级,请求看起来成功,但返回的是空数据、占位内容或与上一次高度重复的结果。第一种可以等,第二种不能等,因为它会悄悄把错误值写进你的结果集。
判断依据可以看三个信号:单位时间内被拒的比例、返回体结构是否稳定、以及同一参数重复请求的结果是否一致。如果被拒比例随时间下降、返回结构没变、重复请求结果一致,说明只是节奏问题,拉长间隔后可以继续。如果返回结构变了或重复请求结果互相矛盾,应视为数据源已不可信,立即停止写入。
脚本最容易犯的错是把所有结果攒在内存里,最后一次性写出。限流一来,进程被杀或重试逻辑清空列表,前面几十分钟的请求就白做了。改成每条或每批拿到结果就追加写入,代价是多一点磁盘写入,收益是任何时刻中断都不丢数据。
落盘时同时记录断点信息,至少包括:已完成的查询参数、已写入的条数、最后一次成功的时间戳、以及当前使用的请求间隔。这样续跑时能跳过已完成部分,而不是从头再来。一个假设例子:假如你有 500 个查询词,脚本在第 180 个词处开始被限流,如果每 10 个词落盘一次,你最多损失 9 个词的工作量;如果只在结束时写一次,你会损失全部 180 个词的结果。这个对比只用于说明落盘频率与损失量的关系,不代表任何真实工具的表现。
追加写入比覆盖写入更适合这种场景。覆盖写入在续跑时容易把新旧结果混在一起,尤其是当查询参数有重复时,你很难判断某一行是第一次跑的还是第二次补的。追加写入配合一个批次标识字段,就能在后续清洗时区分来源。
原地重试成立的条件比较窄:限流是间歇性的,等待一段时间后请求能正常返回,并且你的脚本有退避机制而不是固定间隔狂刷。固定间隔重试在持续限流下只会加重被拒,退避则让请求密度随时间下降,给数据源恢复的空间。
但要注意一个反例:如果限流是由你的请求特征触发的,比如并发数过高或参数组合异常,那么单纯拉长间隔可能无效,因为问题不在频率而在模式。这种情况下,退避等待再久也换不回正常响应,应该先降低并发、拆分参数范围,再决定是否继续。
很多人看到请求恢复正常就默认数据没问题,但限流期间写入的异常值可能已经混进结果集。恢复后应先做一次小范围校验,比如挑几个之前成功的参数重新请求,比对结果是否一致。如果一致,说明中间没有污染;如果不一致,需要定位是从哪个断点开始出现偏差,并考虑丢弃该断点之后的数据重跑。
这一步的实际动作是:用断点时间戳筛出限流期间写入的记录,单独存放而不是直接合并。这样即使后面发现这批数据不可用,也不会影响限流前已经确认的结果。校验通过后再合并,校验不通过就只重跑受影响的那一段。
先检查你当前的脚本有没有逐批落盘和断点记录。如果没有,优先补上这两个能力,再考虑优化请求节奏;如果有,确认断点信息是否包含参数级粒度,粒度太粗会导致续跑时重复请求大量已完成内容。完成这一步之后,再根据限流是间歇性还是持续性,分别选择退避重试或拆分任务重跑。