最好用的票据与发票 OCR 识别软件
票据与发票 OCR 软件选购指南:可验证的准确度、明细提取、导出、API 与 webhook、审计追踪,以及透明的定价——并用实时演示逐一印证。
凡是要跟纸打交道的企业,都绕不开票据和发票——而这两样东西手动录入起来都让人头疼。OCR 的卖点很直白:把单据拍下来,得到结构化数据,然后继续干别的。问题在于,大多数 OCR 工具只做到了看起来像那么回事。它们给你一个供应商名称和一个合计金额,剩下的就要你自己去信。记一笔个人开销,这没问题。但放到应付账款、费用对账,或者任何会被审计的场景里,“模型说是这样”可不是你敢拍胸脯担保的答案。
本文就是一份选购清单。它会带你梳理:到底是什么把真正好用的票据与发票 OCR 软件和一个华而不实的演示区分开来——可验证的准确度、明细行提取、干净的导出、带 Webhook 的真正 API、审计记录,以及你能预估的定价——然后展示 space-ocr 如何逐一做到,用一个实时、可核对的演示,而不是一张截图。
先看证据:一次你可以亲自核对的真实提取
在罗列任何功能之前,先看一样多数厂商不会给你看的东西:一次提取里,每一个数值都能指回它在页面上原本所在的确切位置。把鼠标悬停在下方任意字段上——票据上高亮的那个框,就是这个数值被读取的地方。

Each value with a box carries a verified on-page location — in data.cells[path], that is box + 4-point quad + evidence.match_ratio — on a 0–1000 normalized grid (0,0 top-left → 1000,1000 bottom-right), the same shape the live API returns. Hover a field to trace it back to the pixels it came from.
票据与发票 OCR 该看哪些点
票据和发票是最难啃的“简单”单据。版式因供应商而异,合计金额藏在小计和税额之间,明细行会换行折行,而一张手机拍的照片往往是歪的、还带着反光。一个能搞定干净 PDF 的工具,下一张皱巴巴的热敏小票就可能崩盘。用下面这些标准,帮你穿过营销话术看清本质。
| 关键点 | 为什么重要 | 弱工具 | 强工具 |
|---|---|---|---|
| 可验证的准确度 | 一个无法追溯的数字,反正你还得重新录入一遍 | 给出一个值,也许再加个置信度分数 | 给出每个值连同它的来源坐标——轴对齐的框,加上跟随页面倾斜的四点 |
| 给的是待查清单,不是分数 | 先查哪一条,总得有人决定 | 把阈值设计丢给你自己 | 返回需要复核的路径,附上机器可读的原因码;声明过的数值与日期还带确定性解析值 |
| 明细行 | 发票和票据是表格,不是一组扁平字段 | 抓到合计,丢掉行 | 提取可重复的明细行,每个单元格都带位置 |
| 导出 | 数据得能离开工具才有用 | 复制粘贴或锁死在查看器里 | 通过 API 提供 CSV(Excel/中日韩文本安全)和 JSON |
| API + Webhook | 真有量就意味着自动化,而不是点鼠标 | 只有界面,或一个单薄的同步接口 | 带异步任务和签名 Webhook 的 REST API |
| 审计记录 | 审核者需要看到改了什么 | 悄悄覆盖 OCR 输出 | 把原始值保留在人工修改旁边 |
| 透明定价 | 做预算最怕意外 | 凡事“请联系我们” | 公开的单图价格加免费额度 |
本文余下部分会逐行展开。
可验证的准确度,胜过一个置信度分数
置信度分数告诉你模型自我感觉很有把握。它并不告诉你 total: 2,045 到底是不是票据上实际印着的那个数字。space-ocr 回答的是一个更严苛的问题。业务数据放在 data.values 里,而通往它的每一条路径(total、line_items[0].unit_price)都对应 data.cells 中的一项:
box——一个轴对齐矩形{ xmin, ymin, xmax, ymax },位于 0–1000 归一化网格上(0,0 = 左上角,1000,1000 = 右下角)。换算成像素要用data.image,它给出实际读取时的页面宽高。quad——四个有序顶点,跟随单据的倾斜角度,且始终与box一起返回。系统不做倾斜校正,所以哪怕手机拍歪了,框也会按页面本来的样子贴上去。verified——这是一个判定:单元格带有复核原因时为false,比对跑过且没有任何标记时为true,没有可比对的对象时(例如明细行的合并框)为null。review——要么是null,要么是{ reasons },说明这个单元格为什么值得再看一眼。evidence——判定背后的佐证:text_match是逐字符比对本身的结果,match_ratio表示该值有多少字符在页面上被定位到,printed_text是这些坐标处 OCR 读到的原始字符。
待办清单是 data.review.flagged——一个 { path, reasons } 数组,其中 path 与 cells 的键用同一套写法,可以直接取到对应单元格。因为位置是跟着值一起走的,你可以渲染出这个框、引用这些坐标,或者重新核对一个被标记的字段,而无需重跑 OCR。这正是 OCR 审计记录 的基础——也是上面那个演示不是摆拍的原因。
这些坐标并不是听模型一面之词得来的。 语言模型会返回每个字段的文本——以及它用到了哪些词元(word token)的提示——但从不返回框本身。引擎随后把这段文本与视觉 OCR 在页面上实际检测到的符号逐字符匹配,于是框落在这些字符被找到的真实像素上,每个值也据此得到一个 match_ratio,表示它有多大比例被定位到了。模型给的词元提示可能有噪声(在重复出现的行之间它有时会张冠李戴),所以系统用列一致性和行一致性检查来验证这些提示,而不是盲目相信它们。重点不在于 AI 不会出错——而在于每个值都会回过头来与票据本身核对,并附上一个分数说明它匹配得有多好。
要的是明细行,不只是合计
廉价票据 OCR 最大的缺口就在表格。谁都能抓到一个总计;真正的价值在行里——每一项商品、数量、单价和折扣。space-ocr 把这些提取为可重复的行,而且每个单元格都保留自己的位置,所以一条换行或合并的明细行依然可追溯。
你通过一个 type: "array" 的字段来请求它们,其 children 描述一行的结构。关于行模型更深入的讲解,参见 从发票中提取明细行。
{
"fields": [
{ "name": "vendor", "type": "string" },
{ "name": "invoice_date", "type": "string" },
{ "name": "total", "type": "string" },
{
"name": "line_items",
"type": "array",
"children": [
{ "name": "description", "type": "string" },
{ "name": "quantity", "type": "number" },
{ "name": "unit_price", "type": "number" }
]
}
]
}只声明你需要的字段——不确定就交给 autoFields
抽取模式通过 fields 数组传入:把真正要落库的项按名称和类型列出来,明细行则声明成 type: "array" 字段,用 children 描述其中一行。如果还不清楚单据上有哪些项,改传 autoFields: true,由模型提出一份模式——把它返回的字段名固化成显式声明,就是你的生产调用。
声明并不是一个调节准确度的旋钮。required、pattern、min/max、enum、near、not_near 都不会传给模型,所以声不声明,抽出来的值都一样。多出来的是两样东西:违反规则的值会连同原因(missing、pattern_mismatch、out_of_range、near_mismatch、near_conflict)进入 data.review.flagged;而声明 number、integer、date 会在 values 旁边多出 data.normalized 这一层——同一份读数的确定性解析结果,同一张页面每次跑都得到同一个数。near 和 not_near 并不能让模型在印着两个公司名的单据上挑对那一个,它们的作用是让挑错这件事显形。
整个调用就是一次 HTTP 请求——不用 SDK,也不用对 PDF 做预处理(引擎读的是栅格图像,也就是照片和扫描件)。
# A. 生产——只声明你真正要落库的项
curl -s https://api.space-ocr.com/ocr/fields \
-H "Authorization: Bearer $SPACE_OCR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "https://example.com/invoice.jpg",
"imageType": "url",
"fields": [
{ "name": "issuer", "type": "string", "near": ["纳税人识别号", "电话"] },
{ "name": "recipient", "type": "string", "not_near": ["纳税人识别号", "电话"] },
{ "name": "invoice_no", "type": "string", "required": true,
"pattern": "^[A-Z0-9-]{4,}$" },
{ "name": "invoice_date", "type": "date" },
{ "name": "payment_method", "type": "string", "enum": ["现金", "转账", "信用卡"] },
{ "name": "total", "type": "number", "required": true, "min": 0 },
{
"name": "line_items",
"type": "array",
"children": [
{ "name": "description", "type": "string" },
{ "name": "quantity", "type": "number" },
{ "name": "unit_price", "type": "number" }
]
}
]
}'
# B. 探索——还没有模式,让接口提一份
curl -s https://api.space-ocr.com/ocr/fields \
-H "Authorization: Bearer $SPACE_OCR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "https://example.com/invoice.jpg",
"imageType": "url",
"autoFields": true
}'导出与 API:数据去往何处
如果数据被困在工具里,提取就毫无价值。space-ocr 给你两条干净的出口:
- CSV——表格导出时带 UTF-8 BOM,这样 Excel 才能正确打开日文、韩文和中文文本。数组(明细行)会展开成子行,而任何人工修正都会在输出中覆盖 OCR 原值。
- 基于 REST 的 JSON——
POST /ocr/fields处理单份单据,POST /upload把图片直接推入一张表,GET /view在服务端查询已存储的表(where、sort、select、limit),无需重跑 OCR,也不必再付一次费。
面向大批量的自动化,/upload 默认是异步的:它为每个文件返回一个任务,并在完成时通过 Webhook 通知你——每个空间一个签名(HMAC-SHA256)端点,事件如 ocr.completed 和 ocr.failed。这就是“你点鼠标的工具”和“自己跑起来的流水线”之间的区别。完整的接口面收录在 发票数据提取 API 指南 和 API 文档 里。
审计记录:机器读到了什么 vs. 人改了什么
最好的票据与发票 OCR,不只是记录它自己的输出——它还会记录修正。当你在 space-ocr 里编辑一个单元格时,你的值会与 OCR 原值分开存储,而一个 Original(原值)提示框始终显示引擎最初读到的内容。审核者可以并排看到机器值和人工覆盖值——这恰恰是审计所要的。
透明、可预估的定价
可验证的准确度和一个实诚的价格,往往出自同一种态度。space-ocr 为每张图 $0.05。有每月 100 点数的免费额度,无需信用卡;Pro 套餐 $39/月,含 1,100 点数、不限表格数量和 100 GB 存储。没有按字段计费,没有按页加价,针对已存储表的查询(GET /view)也是免费的。
如何提取一张票据或发票
- 发送图像把票据或发票 POST 到 /ocr/fields,imageType 取 'url' 或 'base64'。引擎读的是栅格图像——手机照片或扫描件。
- 声明字段用 fields 数组声明你要落库的项,明细行用带 children 的 array 字段描述其中一行。如果模式还没定下来,就改传 autoFields 让模型提一份。
- 读取结构化结果业务数据在 data.values;每个值的 box、quad、verified、review 和 evidence 在 data.cells[path];坐标所依据的页面尺寸在 data.image。
- 核对与修正逐条处理 data.review.flagged:每一项给出路径和原因(text_mismatch、missing、out_of_range 等)。点击单元格即可高亮它被读取的确切区域;修改会保存在原值旁边。
- 导出或查询下载 CSV(UTF-8 BOM,明细行已展开),或用 GET /view 配合 where、sort 和 select 查询已存储的表——无需重跑 OCR,也不额外收费。