批量接口调用撞上额度上限,怎么断点续跑
批量调用第三方数据接口时,最常见的失败不是报错崩掉,而是跑到一半额度没了。任务停在那里,已查的不能重查,没查完的不知道从哪接。下面这套做法解决的就是这件事。
一、什么情况下会撞上它
典型场景:用企业信息查询类接口(例如企查查的 MCP 服务)按行政区划逐个拉取国资委下属企业的对外投资。
调用量一大就撞上额度上限,接口返回 HTTP 402,含义是“需要付费/余额不足”,不是网络错误也不是参数错误。重试没有意义,只能等额度恢复或充值。现象很明确:前几个区正常返回,某个区开始全部 402,之后每个请求都是 402。
二、为什么会这样:额度按调用次数算
这类接口的计费单位是调用次数,不是数据条数,也不是时间。一次请求查一家公司,就消耗一次额度。
所以任务规模必须提前拆成“可中断的单元”。如果整个任务是一个大循环、跑完才落盘,中途 402 等于全部白跑。正确做法是每完成一个单元就写一次状态,让任务随时可停、随时可接。
单元按业务维度切,不要按代码循环切。上面场景里天然单元是“区”,因为区与区之间无依赖,且每个区的数据可独立验收。
三、怎么做:三步
第一步:把任务拆成有序的区列表,并给每个区记状态。
状态只需三种:done(全部查完)、partial(查了一部分)、pending(没开始)。用一个 JSON 文件存:
{
"baoshan": { "level1": "done", "level2": "pending" },
"jiading": { "level1": "done", "level2": "pending" },
"minhang": { "level1": "done", "level2": "partial", "done_groups": 5, "total_groups": 18 }
}
记录每个区一级、二级分别查到哪,partial 额外记已完成数量,方便第二天从第 6 家集团接着跑。
第二步:循环里遇到 402 立刻停,不要重试。
for 区 in 待办区列表:
for 集团 in 该区未完成的集团:
响应 = 调用接口(集团)
if 响应.status == 402:
写状态(区, partial)
退出整个任务
写状态(区, 进度+1)
写状态(区, done)
402 是终止信号,不是异常。捕获它、落盘、退出,而不是抛异常或 sleep 重试。
第三步:第二天按固定顺序从断点继续。
顺序写死,不要每次随机。比如按“宝山 → 嘉定 → 金山 → 青浦 → 崇明 → 闵行”排,已 done 的区自动跳过,partial 的区从 done_groups 之后接着跑。
四、怎么确认成了、怎么判断哪一步失败
判断标准只有一条:状态文件里所有区的 level2 都是 done。
如果某个区卡在 partial 好几天不动,说明单日额度连一个区都跑不完,需要把单元从“区”再拆细到“集团”,或接受更长周期。
如果 402 之后仍有请求发出,检查循环里有没有把 402 当成可重试错误。这是最常见的实现错误。
五、边界和代价
三个前提,缺一个就不适用:
- 单元之间无依赖。区与区能独立查才能随便停。如果 B 区依赖 A 区结果,断点逻辑要复杂得多。
- 接口按次计费。如果额度按时间窗口重置(比如每小时 N 次),策略应该是限速而不是断点。
- 状态文件要持久化。写在内存里、进程一退就没了,等于没做。
代价是每天要触发一次续跑。可以挂定时任务,每天固定时间跑,跑完自己退出,不需要人盯着。
额度估算要保守。按“每天 50-60 次”估,实际可能因某天接口不稳定而少跑,留余量比精确排期更实际。
顺带解决“不重复花钱”
断点续跑只保证不丢进度,不保证不重复计费。真正省钱的是让每个单元只提交一次,做法是给每一条记录一个稳定键,并把“已入库”也写进同一份状态:
key = f"{区}-{集团}-{查询类型}"
if key in 已完成集合: # 先查本地,再发请求
continue
响应 = 调用接口(key)
写入数据库(key, 响应) # 先落库
已完成集合.add(key) # 再更新状态
顺序很关键:先落库、再标记完成。反过来一旦在两步之间断电,这条记录会被当成没做过,第二天重新买一次。
如果接口支持幂等键(Idempotency-Key 或业务侧去重 ID),优先交给服务端去重;不支持时,本地“先落库再标记”是唯一可靠的做法。别指望重试逻辑能兜住重复扣费——重试天然会重复请求。