快递批量查询与错误处理:一次查 10 单的正确姿势
2026-07-26 · 阅读约 7 分钟
订单一多,一个一个查快递就太慢了。查询接口支持快递批量查询:
items 数组里一次最多放 10 个运单号,一个请求全部返回。
批量查询真正的难点不在发请求,而在错误处理 —— 某条查不到、格式不对、
或者被限频了该怎么办。本文把这套流程讲透。
一、发起批量查询
把待查列表映射成 items,注意超过 10 条要自己分批:
const items = danhaoList.map(x => ({ courierCode: x.code, trackingNumber: x.no }));
// 单次最多 10 条,超过要分批(见文末分批小节)
const res = await fetch('https://api.kuaidichaxunapi.com/v1/tracking/trace', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.KUAIDI_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ items }),
});
const { data } = await res.json(); 二、先看 summary 汇总
返回的 data.summary 给出整批的统计,适合先记一条日志、快速判断这批的健康度:
| 字段 | 含义 |
|---|---|
total | 本次请求的条目总数 |
successful | 查询成功的条数 |
failed | 失败的条数 |
billable | 本次实际计费的条数 |
注意 billable 通常小于等于 total:
有些失败也计费(如 NOT_FOUND),有些不计费(如 COURIER_PREPARING),
具体见下一节的表。用 billable 对账最准。
三、逐条判断 success
批量查询里某条失败不会让整个请求报错,HTTP 仍是 200,
失败信息在对应条目的 success: false 和 error 里。挨条处理即可:
for (const r of data.results) {
if (r.success) {
save(r.data.trackingNumber, r.data.deliveryStatus);
continue;
}
switch (r.error.code) {
case 'INVALID_TRACKING_NUMBER': // 格式错误,改单号前别重试
case 'UNSUPPORTED_COURIER':
markInvalid(r.error.trackingNumber); break;
case 'NOT_FOUND': // 已计费,过段时间再查
scheduleRetry(r, 30 * 60); break;
case 'TRACKING_FAILED': // 一时失败,指数退避重试
scheduleRetry(r, backoff(r)); break;
case 'COURIER_PREPARING': // 该公司暂不支持,不计费
skip(r); break;
}
} 四、按错误码分级处理
不同错误码的应对方式完全不同,关键是分清「该不该重试」和「计不计费」:
| 错误码 | 含义 | 是否计费 | 建议处理 |
|---|---|---|---|
INVALID_TRACKING_NUMBER | 运单号格式不合法 | 否 | 检查并修正单号,不要原样重试 |
UNSUPPORTED_COURIER | 不支持的快递公司编码 | 否 | 核对 courierCode 拼写 |
MISSING_PARAMS | 缺少编码或运单号 | 否 | 修正请求体后再发 |
COURIER_PREPARING | 该公司查询功能开发中 | 否 | 视为暂不支持,不必重试 |
NOT_FOUND | 查询成功但暂无轨迹 | 是 | 过一段时间再查(注意已计费,别高频重试) |
TRACKING_FAILED | 查询过程一时出错 | 否 | 指数退避后重试 |
一句话记法:格式类错误(INVALID_TRACKING_NUMBER / UNSUPPORTED_COURIER /
MISSING_PARAMS)是你请求写错了,改对之前重试没用;
TRACKING_FAILED 是一时性故障,退避重试有意义;
NOT_FOUND 多是新单轨迹还没上网,稍后重查,但它会计费,别几秒钟就重试一次。
五、缓存字段与限频
成功条目里带一个 cache 对象:fromCache 为 true 表示这条来自
服务端缓存,cachedAt 是缓存生成的时间。物流状态几十分钟才变一次,
命中缓存很正常;如果你要判断数据新鲜度,看 cachedAt 即可。
免费额度带有频率限制(每分钟、每小时、每天各有上限,详见文档)。
触发限频会返回 429,正确做法是退避重试而不是立刻重发:
每次失败把等待时间翻倍(如 1s → 2s → 4s),并设一个上限。批量场景里,
与其撞上限频,不如主动放慢节奏。
六、大批量:分批 + 节流
要查几百上千单时,把列表切成每批 10 条,串行或小并发地发,批之间留一点间隔, 既符合单批上限也不容易撞限频:
function chunk(arr, n) {
const out = [];
for (let i = 0; i < arr.length; i += n) out.push(arr.slice(i, i + n));
return out;
}
for (const pi of chunk(danhaoList, 10)) {
await chaxun(pi); // 上面的批量查询
await sleep(300); // 批之间稍作停顿,避免限频
} 七、生产环境小结
- 先看
summary,再逐条判断success;用billable对账。 - 按错误码分级:格式错误改了再发,
TRACKING_FAILED退避重试,NOT_FOUND稍后再查(已计费)。 isDelivered为 true 就停止轮询,别再浪费额度。- 结合服务端
cache和你自己的缓存,同一单号 10–30 分钟内不必重复查。
状态码逐一解释见物流状态码详解; 完整字段与限额见接口文档。 还没有密钥就先注册(邮箱注册、无需企业认证), 或用首页在线演示跑一遍。