结论取决于一个条件:这批结果能否在限流解除后低成本重取。如果单次抓取成本高、目标页面变动快,或者限流窗口可能持续数小时,就应该先落盘再补抓;如果只是偶发的一两次429、任务总量很小、重取几乎不花时间,先做带退避的重试更省事。判断错方向的代价不对称——把已拿到的数据丢在内存里等重试,进程一崩就全没了;而先落盘最多多花一点磁盘和合并逻辑。
先落盘的核心逻辑是:把“已成功获取”和“尚未获取”在时间上分开,让限流只影响后者。它成立需要三个条件同时满足。
status字段区分ok、pending、failed,否则补抓时无法判断哪些是真正缺失的。一个具体动作:在脚本里把写文件放在解析成功之后、发起下一个请求之前。这样即使下一个请求立刻触发限流,上一条结果已经安全。结果是中断后重启时,脚本只需读取状态文件里pending的条目继续跑,而不是从头再来。这一步直接决定了限流恢复后你是补几十条还是重跑几千条。
带指数退避的重试并非错误做法,它在任务规模小、目标稳定时更简单。成立条件是:总请求量在几百以内、目标页面短期内不会改版、限流阈值只是轻微触碰。
但重试有一个容易被忽略的代价:重试期间已获取的结果仍留在内存或临时变量里。如果脚本因为连续失败退出、机器重启或容器被回收,这部分结果就没了。更麻烦的是,重试逻辑本身可能放大问题——多个并发worker同时退避又同时恢复,会在限流窗口结束时制造一波新的请求高峰,再次触发限流,形成循环。
所以判断的关键不是“重试好不好”,而是“内存里的结果值不值得保护”。如果这批数据是几小时抓取换来的,重试前先落盘几乎总是更稳。
假设你的脚本不是逐条抓取,而是调用某个批量接口:一次请求返回100条,接口按“请求次数”限流,且不提供分页续传。这种情况下,已经拿到的部分结果无法单独标记为完成——第3次请求失败,意味着第201到300条整体缺失,而前200条虽然在内存里,却和缺失段混在同一批次语义中。
此时“先落盘”并不能真正保护进度,因为你落盘的仍是半批数据,补抓时无法从第201条精确续起,只能整批重取。结论失效,正确做法变成:先确认接口是否支持游标或偏移量续传。如果不支持,落盘只能作为备份,真正的进度保护要靠记录“最后成功的整批边界”,而不是逐条状态。
这个反例说明:落盘策略的有效性依赖数据能否被切分成可独立重取的单元。不能切分时,先落盘只是备份,不是续传。
看到429或连接被拒,不要立刻归因为“请求太快”。还有几种合理解释会让你的应对方向完全不同:
区分方法很简单:暂停所有请求,用单个请求手动测试一次。如果单请求也失败,就不是并发限流;如果单请求成功、并发失败,才是频率问题。这个动作的结果决定下一步是调低并发、换凭证,还是先排查网络。
不管最终选哪种策略,先做一件事:让脚本在每次成功解析后立即把结果和状态写入文件,写入完成再发下一个请求。这个动作的成本很低,却把“限流导致进程中断”从数据丢失问题降级为进度暂停问题。
写完状态文件后再判断:如果状态文件里pending条目可以按主键逐条补抓,就放心用退避重试;如果只能整批重取,就先记录批次边界,并在限流恢复后从最后一个完整批次之后继续。两种路径的取舍依据始终是同一句话——已获取的结果能否被单独保护,决定了你该先落盘还是先重试。