每到月底,财务和行政的桌角总会准时出现一摞报销发票:专票、普票、电子发票打印件,有的还压着红章。逐张打开、逐个字段敲进 Excel,一晚上过去,价税合计栏还差三十行。这件事完全可以交给脚本:把整个文件夹的发票批量喂给 OCR,跑完直接得到一张汇总表。本文给两条可复现的完整路线,从装依赖到导出 xlsx 一步不落。
先把分工说清楚:站内那篇 DeepSeek-OCR 部署教程讲的是「把 OCR 模型跑起来」,属于工具导向;本篇讲的是「把一摞发票变成一张 Excel」,属于任务导向,部署细节不再重复展开。
两条路线怎么选
主流做法分两条:云 API 路线和本地开源路线。一张表看清差异:
| 维度 | 路线 A:云 API | 路线 B:本地 PaddleOCR |
|---|---|---|
| 字段结构化 | 接口直接返回发票号码、税额等命名字段 | 开源版没有发票专用模型,字段映射须自写规则 |
| 数据出域 | 图片上传到服务商服务器 | 数据不出本机 |
| 费用 | 百度智能云有按月发放的免费额度 | 纯本地零成本,CPU 可跑 |
| 工程量 | 装好依赖即可调用 | 装环境加写正则规则 |
| 适合场景 | 每月几十到上千张、追求省事 | 票据敏感、量大、愿意维护脚本 |
一句话结论:图省事走 A,数据敏感或票量大走 B;也可以先拿 A 的免费额度把流程跑通,再迁到 B。
动手前建议先把文件归拢:新建一个「发票」文件夹,电子发票 PDF 原件直接另存进去,纸票用手机平拍(镜头贴近平面、避开灯光直射),命名尽量带上开票方和日期,后面排查漏识别时能对上号。文件夹结构没有强制要求,本文脚本示例用的就是同目录「发票」文件夹加汇总表输出的布局,照搬即可。
路线 A:云 API 六步走
以百度智能云「增值税发票识别」为例。官方文档给出的接口是 POST https://aip.baidubce.com/rest/2.0/ocr/v1/vat_invoice,入参 image、url、pdf_file、ofd_file 四选一,优先级依次降低。也就是说 PDF 发票可以直接传 pdf_file,不必先转图片;base64 编码并 urlencode 后不超过 8M,图片最短边不小于 15px、最长边不超过 4096px;多页 PDF 用 pdf_file_num 指定页码,不传默认识别第 1 页。
第一步:装依赖。 一条命令装齐四个库:
pip install pymupdf pandas openpyxl requests一个许可提醒:PyMuPDF 采用 AGPL/商业双许可——个人项目与开源(AGPL 兼容)场景免费,闭源商用需向厂商购买商业授权;发票场景若在公司内部工具里用,先确认许可边界,介意的话可换 pdf2image(GPL 组件依赖,同样要核对)或直接走云 API 的 pdf_file 直传,绕开本地渲染。
第二步:开通服务、管好密钥。 在百度智能云控制台创建 OCR 应用,拿到 API Key 和 Secret Key。密钥不要写进代码,用环境变量承载——官方 SDK 指南原话就是「不要把 AccessKey 写进项目代码」。
第三步:换 access_token。 调用识别接口前,先拿两个 Key 换 token,之后把 token 带在 URL 参数里请求:
import os, requests
def get_token():
# 密钥放环境变量,不硬编码
resp = requests.get(
"https://aip.baidubce.com/oauth/2.0/token",
params={
"grant_type": "client_credentials",
"client_id": os.environ["BAIDU_OCR_API_KEY"],
"client_secret": os.environ["BAIDU_OCR_SECRET_KEY"],
},
)
return resp.json()["access_token"]第四步:确认票种与参数。 接口默认 type=normal,覆盖增值税普票、专票、电子发票(含全电票)和区块链发票;卷票传 type=roll;开 seal_tag=true 会附带印章判断结果。注意票种边界:定额发票、通用机打发票、医疗发票不在增值税发票识别范围内,硬传会失败或字段缺失,这类票要换百度智能云的智能财务票据识别接口 multiple_invoice,一次能拆 13 种常见票据的混贴图。
第五步:收集文件、跑批量脚本。 用标准库 pathlib 或 glob 递归通配文件夹里的 PDF、JPG、PNG,零第三方依赖。PDF 用 PyMuPDF 渲染成 300dpi 的 PNG——PDF 默认按 72dpi 渲染,150dpi 够网页预览但不够 OCR 认小字,OCR 输入建议 300dpi,且 PNG 优于 JPG(避免压缩伪影干扰笔画)。逐张调接口,把 words_result 字段装进字典,失败的单记状态、不中断整批:
import os, time, base64, glob
import fitz # PyMuPDF
import pandas as pd
import requests
VAT_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/vat_invoice"
def pdf_to_png(pdf_path, dpi=300):
# PDF 渲染成 300dpi PNG,OCR 认小字更稳
doc = fitz.open(pdf_path)
pix = doc[0].get_pixmap(matrix=fitz.Matrix(dpi / 72, dpi / 72))
return pix.tobytes("png")
def recognize(token, img_bytes):
resp = requests.post(
VAT_URL,
params={"access_token": token},
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={"image": base64.b64encode(img_bytes).decode()},
)
return resp.json()
def main():
token = get_token()
rows = []
files = glob.glob("发票/*.pdf") + glob.glob("发票/*.jpg") + glob.glob("发票/*.png")
for path in files:
row = {"文件名": os.path.basename(path), "识别状态": "成功"}
try:
if path.lower().endswith(".pdf"):
img = pdf_to_png(path)
else:
img = open(path, "rb").read()
w = recognize(token, img).get("words_result", {})
row.update({
"发票号码": w.get("InvoiceNum", ""),
"开票日期": w.get("InvoiceDate", ""),
"销售方税号": w.get("SellerRegisterNum", ""),
"合计金额": w.get("TotalAmount", ""),
"合计税额": w.get("TotalTax", ""),
"价税合计": w.get("AmountInFiguers", ""),
})
except Exception as e:
row["识别状态"] = f"失败:{e}"
rows.append(row)
time.sleep(0.6) # 免费态 2 QPS,串行节流
pd.DataFrame(rows).to_excel("发票汇总.xlsx", index=False)
if __name__ == "__main__":
main()顺带一提,脚本里统一先转 PNG 是为了让输入可控、便于本地留档复查;如果你追求最少步骤,接口本身支持 pdf_file 直传,跳过转图那步也能跑。
第六步:额度与限速要心里有数。 按百度智能云官方免费测试资源页(页面更新时间 2026-10-22,本文 10-30 核对):增值税发票识别个人认证 1,000 次/月、企业认证 2,000 次/月,额度按月发放,当月没用完不结转;官方原文还写明「成功调用与失败调用均消耗免费测试资源」——传一张废图照样扣次,别拿模糊测试图刷量。QPS 以官方 QA 口径为准:未开通付费 2 QPS,开通付费后提升至 10 QPS;社区流传的 5 QPS、3 QPS 说法与官方口径不一致,本文不采。2 QPS 意味着脚本必须串行加 time.sleep 节流,或对限流错误做指数退避重试,上面脚本的 sleep(0.6) 就是干这个的。
其他云厂商可作对照:腾讯云混贴票据识别接口单图支持 14 种标准报销发票混贴,默认请求频率 5 次/秒;阿里云 RecognizeInvoice 支持增值税专票、普票、电子发票,格式覆盖最广(含 OFD、TIFF),官方自述总体识别准确率可达 98%——注意这是厂商自报口径。腾讯云免费额度各处说法不一,动手前以控制台资源包列表实际显示为准。
路线 B:本地 PaddleOCR
选它的理由:PaddleOCR 是目前 GitHub 上关注度最高的开源 OCR 项目之一(截至 2026-10-30 实测 91,929 星),Apache-2.0 许可。最新 release v3.7.0 发布了 PP-OCRv6 系列,分 tiny、small、medium 三档,CPU 就能跑——官方 release 说明给了 OpenVINO CPU 推理加速 5.2 倍的实证,发票批量场景用 CPU 完全可行,只是吞吐低于 GPU;不上显卡就没有显存压力。
pip install paddlepaddle paddleocr环境注意:仓库提交记录里提到 PaddlePaddle 3.3.0 的 CPU 原生推理对 paddleocr 模型存在 bug,官方曾给出版本回退建议。装环境时把 PaddlePaddle 版本锁住是常见做法,遇到推理异常先查版本兼容,别急着怀疑代码。
关键认知,也是本文最想帮你避开的坑:开源版 PaddleOCR 没有增值税发票专用模型。 正确用法是 PP-OCRv6 做通用文字检测识别,拿到一串文本块之后自己写正则规则映射字段。这和云 API 直接返回 InvoiceNum 命名字段的工程量差异,正是两条路线选型的核心论据。需要表格或版面还原时,v3.6.0 起的 PaddleOCR-VL 配合 PP-StructureV3 管道支持表格识别与印章识别开关,可以叠加使用。
import re, glob
import pandas as pd
from paddleocr import PaddleOCR
ocr = PaddleOCR(ocr_version="PP-OCRv6", device="cpu") # CPU 即可跑
def extract_fields(texts):
# 从文本块里按规则捞字段,开源路线的核心工作量在这里
row = {}
for t in texts:
if re.fullmatch(r"\d{8}", t): # 8 位发票号码
row["发票号码"] = t
m = re.search(r"(\d{4}年\d{1,2}月\d{1,2}日)", t)
if m:
row["开票日期"] = m.group(1)
m = re.search(r"(?:价税合计|合\s*计).*?([\d,]+\.\d{2})", t)
if m:
row.setdefault("价税合计", m.group(1))
return row
rows = []
for path in glob.glob("发票/*.png") + glob.glob("发票/*.jpg"):
result = ocr.predict(path)
texts = result[0]["rec_texts"] # 通用文本块列表
rows.append({"文件名": path, **extract_fields(texts)})
pd.DataFrame(rows).to_excel("发票汇总_本地.xlsx", index=False)示例正则只覆盖三类最典型字段。真实票面远比示例复杂:全电票版式、卷票、带折痕的扫描件都会让规则漏抓,所以本地路线的产出务必保留一列人工复核状态。
字段映射与 Excel 导出
云 API 返回的是英文字段名,落表前要做一次映射。百度智能云官方文档里发票识别的关键字段对照如下:
| 返回字段 | 含义 | 建议列名 |
|---|---|---|
| InvoiceNum | 发票号码 | 发票号码 |
| InvoiceDate | 开票日期 | 开票日期 |
| SellerRegisterNum | 销售方纳税人识别号 | 销售方税号 |
| TotalAmount | 合计金额 | 合计金额 |
| TotalTax | 合计税额 | 合计税额 |
| AmountInFiguers | 价税合计(小写) | 价税合计 |
| CommodityTaxRate | 税率(在商品明细数组内) | 税率 |
商品明细数组里的 CommodityName、CommodityPrice 等字段可按需展开成子表,这里不铺开。导出环节用 pandas 出数、openpyxl 做样式:pandas 的 to_excel 写 .xlsx 底层用的就是 openpyxl 引擎;当前 pandas 稳定版 3.0.6(2026-09-18 发布)要求 Python 3.11 以上。给汇总表加一点办公体面:
from openpyxl import load_workbook
from openpyxl.styles import Font
wb = load_workbook("发票汇总.xlsx")
ws = wb.active
for cell in ws[1]:
cell.font = Font(bold=True) # 表头加粗
ws.freeze_panes = "A2" # 冻结首行
for col in "FGH": # 金额三列套千分位
for cell in ws[col][1:]:
cell.number_format = "#,##0.00"
wb.save("发票汇总.xlsx")两个库的分工口诀:按「表格」思考用 pandas,按「单元格和样式」思考用 openpyxl。注意 openpyxl 不计算公式——如果表里写了公式,用 load_workbook(data_only=True) 读结果前,须先在 Excel 或 LibreOffice 里重算保存过。
大模型路线:作补充,不当主力
GLM-OCR(智谱开源,0.9B 参数)支持 text、formula、table、custom 四种模式,custom 模式用自定义 JSON schema 提示词做结构化抽取,社区有开发者用它对收据抽出发票号、日期、明细与合计的实测示例。但口径要说全:这是官方能力宣称加第三方通用实测,目前没有发票专项公开基准,实测者的结论也是「结构化抽取靠 prompt 明确约束后稳定性才大幅提升」。0.9B 参数对显存要求很低,票量不大、想用一行 prompt 代替一套正则时可以试;要稳定产线,前两条路线更可靠。资源清单见站内 GLM-OCR 篇(文末延伸阅读)。
常见报错与对策
识别失败和识别不准要分开排查:前者是接口报错或返回空,多为图片质量问题;后者是有结果但字段错位,多为版式与语义问题。排查顺序四步走:图片质量、版式与语言、引擎参数、业务兜底。
采集端的高频原因:覆膜票面灯光直射反光过曝、倾斜拍摄致字段畸变、折痕污渍褪色、红色发票章压住税号或金额栏、截图滤镜过度压缩丢细节。对策是柔光平拍、抚平折痕、印章遮挡时微调角度、优先用电子发票 PDF 原件而非翻拍截图。量化看有多伤:百度智能云一篇技术文的实验口径称 15 度倾斜使字符识别准确率下降 23%,另有案例印章覆盖导致 35% 的金额字段识别错误——注意这是该文实验与案例数据,不是行业通识。
接口侧可对照官方错误码表(以腾讯云文档为例,各家逻辑相似):ImageBlur 图片模糊就重拍或预处理;ImageDecodeFailed 解码失败就核对文件确为 PNG/JPG/PDF 且未损坏;文件超限就压缩或拆分。票种不匹配(定额、机打、医疗发票硬传增值税接口)就换混贴票据接口。
大模型路线有三类典型错误也值得知道(腾讯云官方公众号口径):字段串行(购销方名称与税号错位)、金额串行(不含税金额、税额、价税合计读串)、明细截断(多行明细只出前几行);专业 OCR 按字段坐标返回,不发生串行问题。
合规边界,一句话说清
发票含税号、购销方名称等经营信息,走云 API 前确认服务商数据协议(百度智能云协议明确用户业务数据归用户所有);验真伪只认国家税务总局全国增值税发票查验平台——每票每天最多查验 5 次、可查最近 5 年内开具的发票、当日开具当日可查验;AI 审查结果作辅助,不替代专业意见。
延伸阅读
- DeepSeek-OCR 本地部署 SOP:那篇讲把 OCR 模型跑起来,本篇讲把一摞发票变成一张 Excel,正好互补。
- 开源 OCR 模型横评:六款开源引擎横向对比,选引擎前先看这篇。
- GLM-OCR 开源资源:小模型结构化抽取路线的完整资源清单。
常见问题
Q1: 发票 OCR 识别失败是什么原因?
最常见的是图片质量:反光、模糊、倾斜、印章遮挡关键字段、过度压缩。其次是票种不匹配——定额发票、通用机打发票不在增值税发票识别接口范围内,要换混贴票据接口。云 API 免费态还有 QPS 限制,并发过高会被限流。
Q2: 批量识别发票怎么录入 Excel?
三段式流程:用 pathlib 或 glob 收集文件夹里的 PDF/JPG/PNG;PDF 用 PyMuPDF 渲染成 300dpi 图片;逐张调 OCR 接口或本地模型,字段装进字典,最后用 pandas 的 to_excel 一次性导出 xlsx。本文路线 A 的主脚本就是完整可跑的示例。
Q3: PDF 电子发票能直接识别吗,要先转图片吗?
百度智能云接口支持 pdf_file 直传,不必先转图片;但把 PDF 统一渲染成 300dpi PNG 再传,输入更可控,也方便本地留档复查。扫描件同理,优先用 PDF 原件而不是二次截图。
Q4: 开源 PaddleOCR 能直接识别出发票字段吗?
不能直接出字段。开源版没有增值税发票专用模型,只能拿到通用文字块,需要自己写正则映射发票号码、日期、金额等字段。追求开箱即用的命名字段,云 API 路线工程量小得多。
Q5: 批量识别出来的发票怎么验真伪?
OCR 只解决「读出来」,真伪要另查。官方唯一在线渠道是全国增值税发票查验平台,注意每票每天最多查验 5 次、可查最近 5 年内开具的发票;云厂商也有发票验真接口可作批量抽查,结果同样以税务平台为准。