用 Code0 做一个发票识别助手:图片批量转结构化数据
本站为独立第三方技术服务平台,提供多模型 API 聚合接入服务,与 Anthropic、OpenAI、Google 等模型提供商无任何关联、授权或合作关系。
财务报销、费用录入这类活儿,最烦的就是对着一堆发票照片手动敲号码、金额、日期——又慢又容易看错。多模态模型能"看懂图",正好干这件事。这篇带你用一套 OpenAI SDK 代码,在 Code0 上做一个能批量识别发票、提取关键字段、导出 CSV 的小助手,一百来行搞定,识别引擎还能一键在 Claude / GPT / Gemini 间切换对比。
TL;DR
- 核心就三步:把发票图片喂给多模态模型 → 强制输出 JSON → 汇总导出 CSV。
- 用 OpenAI SDK 零改造,识别用的模型只改
model字段就能换,挑识别效果最好的那个。 - 本地图片转 base64 内联,一个文件夹批量跑完。
- 加一层字段校验,识别不准的挑出来人工复核,别让脏数据进账。
前置准备
- 注册 Code0 账号:https://console.code0.ai/console/dashboard
- 获取 API Key(控制台 → API Keys → 创建),形如
sk-... - 安装依赖:
pip install openai
- 准备一个放发票图片的文件夹,比如
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 多模型路由实战。



