商品条码查询API实战从扫码到资料补全

为什么条码查询是零售系统里的高频能力

在电商、收银、仓储、记账 App、自动售货机这类场景里,用户扫到的往往不是商品名称,而是一串条形码。系统如果能基于条码自动补全商品名称、品牌、规格、厂商、参考价和图片,就可以显著减少人工录入成本。

极数本源的「商品条码查询-免费版」接口正好覆盖这个流程:

  • 支持 EAN-13UPC-AUPC-EEAN-8 等主流条码。
  • 入参只需要 barcode,也就是 8 到 13 位纯数字。
  • 命中商品时返回 namebrandmanufacturerspecpriceimage 等字段。
  • 未命中商品时仍然返回 code=0,但 data.found=false,业务侧必须单独判断。
  • 图片通过接口代理懒加载,图片资源本身不计入文本查询调用次数。

接口能力速览

接口地址:

text 复制代码
GET https://v1.apizero.cn/api/barcode-lookup

请求参数:

参数 必填 说明 示例
barcode 8 到 13 位纯数字条形码 6921168509256
mode 保留参数;mode=image 时返回商品图片二进制,主要由响应里的 image 链接自动使用 image

JSON 查询时通常只传 barcode

bash 复制代码
curl "https://v1.apizero.cn/api/barcode-lookup?barcode=6921168509256"

如果接口命中商品,data.image 会给出一个图片代理 URL,前端可以把它放进 <img> 标签中;不建议为了查询商品文本信息主动带上 mode=image,因为它的语义是取图片资源。

认证、额度与限流

接口支持匿名调用和 API Key 鉴权。生产环境建议使用 API Key,并放在后端服务中,不要暴露在小程序、网页前端或 App 包里。

text 复制代码
Authorization: Bearer sk_live_xxxxxxxxxxxxxx

调用边界如下:

项目 规则
匿名每日额度 20 次
认证用户每日额度 200 次
QPS 限制 2 req/s
商品文本数据 按次计入调用
商品图片资源 公开懒加载代理,不计入接口调用次数

这里的工程含义很明确:条码查询不要做无节制轮询,扫码后查一次即可;对同一条码的查询结果应该本地缓存,尤其是高频商品。

响应结构:code=0 不等于商品命中

响应顶层字段包括 codemsgdatarequest_id。其中 code=0 只表示接口调用成功,不表示商品一定被收录。

未命中时,接口仍然会返回成功状态,但 found=false,其余商品字段为 null

json 复制代码
{
  "code": 0,
  "msg": "成功",
  "data": {
    "barcode": "6921168509256",
    "found": false,
    "name": null,
    "brand": null,
    "manufacturer": null,
    "spec": null,
    "price": null,
    "category": null,
    "description": null,
    "image": null
  },
  "request_id": "mqj7btdz45ee9ec8"
}

命中时,核心字段会被填充:

json 复制代码
{
  "code": 0,
  "msg": "成功",
  "data": {
    "barcode": "6921168509256",
    "found": true,
    "name": "农夫山泉 饮用天然水550ml",
    "brand": "农夫山泉",
    "manufacturer": "农夫山泉股份有限公司",
    "spec": "550ml",
    "price": 1.5,
    "category": null,
    "description": null,
    "image": "https://v1.apizero.cn/api/barcode-lookup?mode=image&barcode=6921168509256"
  },
  "request_id": "mqx8x12345abc"
}

字段语义如下:

字段 含义 业务建议
barcode 回显输入条码 可作为商品资料表的候选唯一键
found 是否命中商品 必须判断,不能只看 code
name 商品名称 可用于自动补全和搜索展示
brand 品牌 适合做筛选、统计和品牌维度分析
manufacturer 生产厂商或经销商 适合采购、溯源、后台审核
spec 规格 展示容量、重量、包装等信息
price 参考价 只能作参考,不是实时电商价格
image 商品图片 URL 前端懒加载,失败时降级默认图

Python 实战:批量查询并导出 CSV

下面的脚本只使用 Python 标准库,可以直接运行。它做了五件事:

  • 校验条码必须是 8 到 13 位纯数字。
  • 支持命令行批量查询多个条码。
  • 自动读取环境变量 APIZERO_KEY 并放入 Authorization 头。
  • 按 QPS 2 做基础限速,避免批量查询过快。
  • 输出控制台摘要,并导出 barcode_results.csv

保存为 barcode_lookup.py

python 复制代码
import csv
import json
import os
import re
import sys
import time
from pathlib import Path
from typing import Any
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen


BASE_URL = "https://v1.apizero.cn/api/barcode-lookup"
API_KEY = os.getenv("APIZERO_KEY")
BARCODE_PATTERN = re.compile(r"^\d{8,13}$")

ERROR_HINTS = {
    4000: "条形码格式错误:barcode 必须为 8 到 13 位纯数字",
    4011: "API Key 无效:检查环境变量 APIZERO_KEY",
    4013: "API Key 已暂停:到控制台检查 Key 状态",
    4014: "当前 IP 不在 API Key 白名单内",
    4022: "余额不足:充值后再试",
    4029: "调用过快:降低并发或增加 sleep",
    4030: "今日免费额度已用完",
    5020: "商品数据库暂不可用:稍后重试",
    5030: "商品数据库未配置:联系平台处理",
}


def normalize_barcode(raw: str) -> str:
    barcode = raw.strip().replace(" ", "").replace("-", "")
    if not BARCODE_PATTERN.fullmatch(barcode):
        raise ValueError(f"非法条码:{raw},需要 8 到 13 位纯数字")
    return barcode


def request_json(barcode: str) -> dict[str, Any]:
    headers = {}
    if API_KEY:
        headers["Authorization"] = f"Bearer {API_KEY}"

    query = urlencode({"barcode": barcode})
    request = Request(f"{BASE_URL}?{query}", headers=headers, method="GET")

    try:
        with urlopen(request, timeout=15) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        body = exc.read().decode("utf-8", errors="replace")
        try:
            return json.loads(body)
        except json.JSONDecodeError as error:
            raise RuntimeError(f"HTTP {exc.code}: {body}") from error


def lookup_barcode(barcode: str) -> dict[str, Any]:
    payload = request_json(barcode)
    code = int(payload.get("code", -1))

    if code != 0:
        hint = ERROR_HINTS.get(code, "未收录错误码,请查看接口返回")
        request_id = payload.get("request_id", "")
        raise RuntimeError(f"查询失败 code={code}, hint={hint}, request_id={request_id}")

    data = payload["data"]
    data["request_id"] = payload.get("request_id", "")
    return data


def row_from_result(data: dict[str, Any]) -> dict[str, Any]:
    return {
        "barcode": data.get("barcode"),
        "found": data.get("found"),
        "name": data.get("name") or "",
        "brand": data.get("brand") or "",
        "manufacturer": data.get("manufacturer") or "",
        "spec": data.get("spec") or "",
        "price": data.get("price") if data.get("price") is not None else "",
        "image": data.get("image") or "",
        "request_id": data.get("request_id") or "",
    }


def write_csv(rows: list[dict[str, Any]], output: Path) -> None:
    fieldnames = [
        "barcode",
        "found",
        "name",
        "brand",
        "manufacturer",
        "spec",
        "price",
        "image",
        "request_id",
    ]
    with output.open("w", newline="", encoding="utf-8-sig") as file:
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)


def main() -> None:
    raw_barcodes = sys.argv[1:] or ["6921168509256"]
    rows: list[dict[str, Any]] = []

    for index, raw in enumerate(raw_barcodes, start=1):
        barcode = normalize_barcode(raw)
        data = lookup_barcode(barcode)
        rows.append(row_from_result(data))

        if data["found"]:
            print(f"[{index}] {barcode} 命中:{data['name']} / {data.get('brand') or '-'}")
        else:
            print(f"[{index}] {barcode} 未收录,可进入手动补录流程")

        if index < len(raw_barcodes):
            time.sleep(0.55)

    output = Path("barcode_results.csv")
    write_csv(rows, output)
    print(f"已导出:{output}")


if __name__ == "__main__":
    main()

运行单个条码:

bash 复制代码
python barcode_lookup.py 6921168509256

运行多个条码:

bash 复制代码
python barcode_lookup.py 6921168509256 6901028193495 6902538004044

配置 API Key:

bash 复制代码
export APIZERO_KEY="sk_live_xxxxxxxxxxxxxx"
python barcode_lookup.py 6921168509256

这段代码有一个关键设计:只要 code=0,就把结果交给业务层;业务层再根据 found 决定是自动补全,还是进入「未收录、手动补录」流程。

前端如何展示商品图片

命中商品时,响应里的 image 是一个可直接访问的图片代理 URL。前端可以这样使用:

html 复制代码
<img
  src="https://v1.apizero.cn/api/barcode-lookup?mode=image&barcode=6921168509256"
  alt="商品图片"
  onerror="this.src='/static/product-placeholder.png'"
/>

实际项目里更推荐使用接口返回的 data.image,不要自己拼接 URL。原因很简单:如果平台后续调整图片代理策略,服务端返回字段会比硬编码更稳定。

图片展示还要注意三点:

  • found=falseimagenull,前端要直接显示默认图。
  • 图片下载不等于文本查询,不要为了判断商品是否存在去请求图片。
  • 图片失败时用 onError 降级,不要让整条商品资料渲染失败。

错误码如何落到业务逻辑

常见错误码可以分成四类:

code 类型 处理建议
4000 参数错误 前端拦截非数字、长度不合法的输入
401140134014 鉴权问题 检查 Key、IP 白名单和账号状态
402240294030 额度或限流 降低频率、启用缓存、等待额度刷新
50205030 上游或平台问题 稍后重试,记录 request_id

业务上最值得单独强调的是 4029。接口 QPS 限制是 2 req/s,所以批量导入商品资料时,不要开几十个并发直接扫库。即使只是后台任务,也应该做限速、失败重试和断点续跑。

落库模型建议

商品条码查询返回的是外部参考资料,不建议直接覆盖你的正式商品主数据。更稳妥的做法是建立「候选资料」或「扫码补全记录」:

字段 建议
barcode 唯一索引或候选唯一索引
found 记录是否命中,便于后续补录统计
namebrandspec 作为自动补全字段,允许人工确认
price 参考价字段,不参与真实定价
image 存原始 URL,也可异步转存到自己的对象存储
source 固定写入 apizero_barcode_lookup
request_id 排障字段,接口异常时尤其有用
updated_at 用于缓存过期和定期刷新

对于零售系统,我更推荐「先查外部接口,再进入人工确认」:

  1. 扫码拿到 barcode
  2. 本地查缓存或商品库。
  3. 本地无数据时调用条码查询 API。
  4. found=true 则预填商品资料。
  5. found=false 则展示空态,让运营手动补录。
  6. 人工确认后写入正式商品表。

这样既能提升录入效率,又不会把外部参考数据未经审核地直接写成你的核心商品主数据。

实战注意事项

1. 先做本地格式校验

接口要求 barcode 是 8 到 13 位纯数字。扫码枪或移动端输入可能带空格、换行、短横线,发送请求前应该先清洗:

python 复制代码
barcode = raw.strip().replace(" ", "").replace("-", "")

如果清洗后仍不符合格式,直接在本地提示用户,不需要消耗一次接口调用。

2. 热点条码要缓存

同一个便利店、仓库或售货机系统里,高频商品会被反复扫描。对这些条码做本地缓存,可以明显节省调用次数。常见缓存策略:

  • found=true 的商品缓存 7 到 30 天。
  • found=false 的商品缓存 1 到 3 天,避免新上市商品长期不刷新。
  • 人工补录后的商品优先读本地库,不再依赖外部接口。

3. price 只能作为参考价

文档明确说明 price 是人民币参考价,不是各电商平台实时价格。不要把它用于自动定价、结算或价格监控。更合适的用途是:

  • 商品资料初始补全。
  • 记账 App 的默认金额建议。
  • 商品录入时给运营一个参考范围。

4. found=false 不是错误

冷门商品、新上市 SKU、区域性商品都有可能未收录。此时接口返回 code=0found=false,这属于正常业务分支,不应该抛成系统异常。

小结

商品条码查询 API 的价值不在于「发一个 GET 请求」本身,而在于把扫码后的不确定输入变成结构化商品资料。真正落地时,要把三个边界处理好:

  • barcode 先本地校验,减少无效调用。
  • code=0 后继续判断 data.found,区分命中和未收录。
  • priceimagerequest_id 都有明确工程语义,不能只取 name 就结束。

如果把缓存、限流、手动补录和日志记录一起设计进去,这个接口可以很快变成一个可用于零售、仓储、记账和扫码营销场景的商品资料补全模块。

参考资料:极数本源商品条码查询 API 文档