英伟达发布开源大模型Nemotron 3 Super›

用 Code0 做一个发票识别助手:图片批量转结构化数据

发票识别 · 应用实战 · 财务自动化阅读时间:9 分钟发表时间:2026.07.06
用 Code0 做一个发票识别助手:图片批量转结构化数据

用 Code0 做一个发票识别助手:图片批量转结构化数据

本站为独立第三方技术服务平台,提供多模型 API 聚合接入服务,与 Anthropic、OpenAI、Google 等模型提供商无任何关联、授权或合作关系。

财务报销、费用录入这类活儿,最烦的就是对着一堆发票照片手动敲号码、金额、日期——又慢又容易看错。多模态模型能"看懂图",正好干这件事。这篇带你用一套 OpenAI SDK 代码,在 Code0 上做一个能批量识别发票、提取关键字段、导出 CSV 的小助手,一百来行搞定,识别引擎还能一键在 Claude / GPT / Gemini 间切换对比。

TL;DR

  • 核心就三步:把发票图片喂给多模态模型 → 强制输出 JSON → 汇总导出 CSV。
  • 用 OpenAI SDK 零改造,识别用的模型只改 model 字段就能换,挑识别效果最好的那个。
  • 本地图片转 base64 内联,一个文件夹批量跑完。
  • 加一层字段校验,识别不准的挑出来人工复核,别让脏数据进账。

前置准备

  1. 注册 Code0 账号:https://console.code0.ai/console/dashboard
  2. 获取 API Key(控制台 → API Keys → 创建),形如 sk-...
  3. 安装依赖:
pip install openai
  1. 准备一个放发票图片的文件夹,比如 invoices/,里面是 .jpg / .png。

完整代码示例

把上面几段拼起来就是一个能跑的助手,直接复制运行:

import os, base64, json, csv
from openai import OpenAI

client = OpenAI(
    base_url="https://hk.code0.ai/v1",
    api_key="sk-你的Key",  # 在 console.code0.ai 获取
)

FIELDS = ["发票号码", "开票日期", "金额", "销售方", "购买方"]

def to_data_url(path: str) -> str:
    with open(path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    ext = "jpeg" if path.lower().endswith((".jpg", ".jpeg")) else "png"
    return f"data:image/{ext};base64,{b64}"

def read_invoice(path: str, model: str = "claude-opus-4-8") -> dict:
    prompt = (
        f"这是一张发票,请提取以下字段:{ '、'.join(FIELDS) }。"
        "只输出 JSON,键用中文字段名,找不到的值填空字符串,不要任何解释。"
    )
    resp = client.chat.completions.create(
        model=model,
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {"url": to_data_url(path)}},
            ],
        }],
    )
    return json.loads(resp.choices[0].message.content)

def check(row: dict) -> bool:
    if not row.get("发票号码") or not row.get("金额"):
        return False
    amount = str(row.get("金额", "")).replace("¥", "").replace(",", "").strip()
    try:
        float(amount)
    except ValueError:
        return False
    return True

def run(folder: str, out: str = "result.csv"):
    rows = []
    for name in sorted(os.listdir(folder)):
        if not name.lower().endswith((".jpg", ".jpeg", ".png")):
            continue
        try:
            data = read_invoice(os.path.join(folder, name))
            data["文件名"] = name
            print(f"✅ {name} → {data.get('发票号码', '')}")
        except Exception as e:
            data = {"文件名": name, "识别失败": str(e)}
            print(f"⚠️ {name} 失败:{e}")
        rows.append(data)

    cols = FIELDS + ["文件名", "需复核"]
    with open(out, "w", newline="", encoding="utf-8-sig") as f:
        w = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore")
        w.writeheader()
        for r in rows:
            r["需复核"] = "" if check(r) else "是"
            w.writerow(r)
    print(f"完成,共 {len(rows)} 条 → {out}")

if __name__ == "__main__":
    run("invoices")

跑完你会得到一份 result.csv:每行一张发票,字段齐全,识别可疑的那几行"需复核"列标了"是",人工只看这几张就行。

换个模型对比效果

不同模型对票据的识别各有强弱。想对比,只改 read_invoice 的 model 参数——一个 Key、一套代码,不用重配环境:

data = read_invoice("invoices/demo.jpg", model="gpt-5.4")     # 换 GPT
data = read_invoice("invoices/demo.jpg", model="gemini-3-pro") # 换 Gemini

挑一批真实发票各跑一遍,看哪个模型在你的票据类型上字段最准,就用哪个。

步骤拆解

步骤 1:先定清楚要提取哪些字段

想让输出稳定,第一件事是把"要什么"写死。发票场景常用这几个字段:

FIELDS = ["发票号码", "开票日期", "金额", "销售方", "购买方"]

字段越明确,模型输出越可控。

步骤 2:配客户端 + 写识别函数

和普通文本调用一样,只改 base_url 和 api_key。识别的关键是把 content 换成"文字指令 + 图片",并要求只输出 JSON。

import base64, json
from openai import OpenAI

client = OpenAI(
    base_url="https://hk.code0.ai/v1",
    api_key="sk-你的Key",  # 在 console.code0.ai 获取
)

def to_data_url(path: str) -> str:
    with open(path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    ext = "jpeg" if path.lower().endswith((".jpg", ".jpeg")) else "png"
    return f"data:image/{ext};base64,{b64}"

def read_invoice(path: str, model: str = "claude-opus-4-8") -> dict:
    prompt = (
        f"这是一张发票,请提取以下字段:{ '、'.join(FIELDS) }。"
        "只输出 JSON,键用中文字段名,找不到的值填空字符串,不要任何解释。"
    )
    resp = client.chat.completions.create(
        model=model,  # 换成 gpt-5.4 / gemini-3-pro 只改这一行
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": {"url": to_data_url(path)}},
            ],
        }],
    )
    return json.loads(resp.choices[0].message.content)

步骤 3:批量跑一整个文件夹

import os

def batch(folder: str) -> list[dict]:
    rows = []
    for name in sorted(os.listdir(folder)):
        if not name.lower().endswith((".jpg", ".jpeg", ".png")):
            continue
        path = os.path.join(folder, name)
        try:
            data = read_invoice(path)
            data["文件名"] = name
            rows.append(data)
            print(f"✅ {name} → {data.get('发票号码', '')}")
        except Exception as e:
            print(f"⚠️ {name} 识别失败:{e}")
            rows.append({"文件名": name, "识别失败": str(e)})
    return rows

步骤 4:加一层校验,脏数据挑出来

识别结果直接入账有风险,加个最简单的规则校验:关键字段为空、金额不是数字的,标记出来人工复核。

def check(row: dict) -> bool:
    if not row.get("发票号码") or not row.get("金额"):
        return False
    amount = str(row.get("金额", "")).replace("¥", "").replace(",", "").strip()
    try:
        float(amount)
    except ValueError:
        return False
    return True

步骤 5:导出 CSV

import csv

def export(rows: list[dict], out: str = "result.csv"):
    cols = FIELDS + ["文件名", "需复核"]
    with open(out, "w", newline="", encoding="utf-8-sig") as f:
        w = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore")
        w.writeheader()
        for r in rows:
            r["需复核"] = "" if check(r) else "是"
            w.writerow(r)
    print(f"已导出 {len(rows)} 条到 {out}")

utf-8-sig 是为了 Excel 打开不乱码。

常见问题

  • Q:识别不准怎么办? → 先在提示词里把字段和格式写得更死(如"金额只保留数字"),再靠第 4 步的校验把可疑结果挑出来人工复核。关键财务数据务必人工确认。
  • Q:图片太大传不动? → 拍照发票通常几 MB,base64 会让请求体变大,建议先压缩或缩放再传;具体大小上限以控制台说明为准。
  • Q:能识别 PDF 发票吗? → 先把 PDF 每页转成图片再走同样流程(可用 pdf2image 等库)。
  • Q:一次能传多张吗? → 单张更稳、更好对账;要合并识别可在 content 里放多个 image_url。
  • Q:哪些模型支持读图? → 只有带视觉能力的模型可以,可用列表以控制台为准,切换只改 model 字段。
  • Q:数据安全吗? → 建议对发票中的敏感信息按需脱敏,避免在请求里提交不必要的隐私字段;平台数据处理规则以隐私政策为准。

小结

一个发票助手的核心逻辑其实很简单:图片 → 多模态提取 → 校验 → 导出。 真正省事的地方在于,用 Code0 一个 Key 就能把 Claude、GPT、Gemini 都试一遍,挑对你票据识别最准的模型,代码一行不用改。 同样的套路还能改成合同抽取、名片录入、表单数字化。想按任务难度和成本把多个模型组合调用,可以接着看 一套代码切换所有模型:Code0 多模型路由实战。