长时间批量采集不能简单地把所有 success: false 都视为致命错误。有些 API 会用失败信封表达“指定实体在该历史区间没有数据”,这在新上市标的、功能不支持的市场或早期年份中是正常结果。
推荐分类
| 类型 | 例子 | 处理方式 |
|---|---|---|
| 传输失败 | 超时、连接重置、HTTP 429/5xx | 有上限地退避重试 |
| 协议或结构失败 | JSON 损坏、字段结构完全未知 | 立即停止并保留原始响应 |
| 确定性请求错误 | 未授权、参数非法、命令不存在 | 立即停止,不重试 |
| 业务无数据 | 指定区间无数据、未找到历史数据 | 记为成功的零行分区并继续 |
| 部分失败 | 批次中部分实体成功、部分无数据 | 保存成功项,逐项记录失败原因 |
no-data 识别必须使用狭窄白名单,例如明确的错误码或文案。不要把任意 success: false 转为空数组,否则鉴权失败、限频和服务异常也会被错误标记为已完成。
function parseResponse(payload: ApiEnvelope, allowNoData: boolean) {
if (payload.success) return payload.data;
if (allowNoData && isKnownNoDataError(payload.error)) {
return [];
}
throw new Error(formatError(payload.error));
}
零行也是 coverage
合法空分区应写入覆盖记录:
dataset + partition + from + to + rows_written=0
否则下一次断点续跑仍会重复请求同一空区间。覆盖记录应与原始响应、请求参数和校验和关联,便于以后重新解释 no-data 规则。
不要在业务正文中搜索错误关键词
错误检测应优先依据退出码、HTTP 状态、标准错误或结构化错误字段。对整个成功响应正文执行宽泛正则,可能因为普通业务数据恰好包含 500、rate 等片段而误判有效响应。
正确顺序通常是:
- 检查进程退出码或 HTTP 状态。
- 解析响应信封。
- 验证目标数据结构。
- 分类结构化错误。
- 标准化并写入数据与 coverage。
批次大小要按响应规模校准
批次上限不能只按“实体数量”猜测。同样是一千个实体,单日数据和全年数据的响应行数、字节数、解析时间与峰值内存可能相差数百倍。
确定默认批次前,应对代表性窗口做阶梯测试:
小批次 → 中批次 → 大批次 → 全量批次
每一级至少记录:
- 请求实体数与实际返回实体数。
- 返回数据行数与覆盖率。
- 响应字节数。
- 服务端耗时与客户端解析耗时。
- 部分失败和 no-data 数量。
- 距离进程超时与可用内存的余量。
“全量单日能够返回”不能证明“全量全年也适合作为一个请求”。可靠默认值应给响应字符串、JSON 对象和标准化结果同时驻留内存留出余量,而不是刚好不超时。
改变批次不能让历史 coverage 失效
如果 coverage 只用 first-id ~ last-id 标识批次,调整批次大小后容易把旧数据误判为未完成。更稳妥的做法是从请求账本保存的原始参数恢复每个已完成批次的准确实体集合,然后新批次只请求差集:
const covered = restoreCoveredIds(requestLedger, partition);
const pending = nextBatch.filter((id) => !covered.has(id));
这样可以在不重抓历史数据的前提下逐步放大批次,也能正确处理后来插入到排序区间中的新实体。
测试边界
至少覆盖以下测试:
- 明确 no-data 响应得到空数组。
- no-data 分区会写入零行 coverage。
- 限频、未授权和参数错误仍然抛出。
- 无法识别的结构不会被当成空数据。
- 断点续跑会跳过已经完成的零行分区。
- 放大批次后仍能从旧请求账本恢复精确覆盖实体。
具体 API 的 no-data 错误码与文案应被视为版本化的适配器契约:从保存的真实响应建立白名单,并在供应方版本或响应结构变化时重新运行解析测试,不能凭猜测扩大匹配范围。