把一堆乱七八糟的发票文件(PDF / OFD / XML)丢进一个文件夹,一条命令得到: 去重后的归档 + 报销汇总表 + 一份可以离线打开的报告。
fapiao scan ./我的发票 --archive双击 发票报告.html 即可离线打开——搜索、筛选、汇总、风险标红,全部内置,
不加载任何外部资源:
每个月底报销的人都在重复同样的事:
- 邮箱里、微信里、支付宝里下载的发票,文件名全是乱码(
dzfp_25512000000012345678_20260901.pdf) - 同一张发票下载了两次,自己根本发现不了 —— 而重复报销是财务红线
- 报销前才发现抬头写错了,被打回重开
- 手工把金额一张张填进 Excel,加错一个数字就要全部重来
现有工具的普遍做法是:把发票图片上传到云端大模型做 OCR。这意味着你的 发票(含公司名、税号、消费金额、行程)离开了你的电脑,而且识别结果还带概率误差。
但这件事根本不需要 AI。 现在的电子发票(数电票)PDF / OFD / XML 都自带文本层, 字段是确定性的,用规则解析可以做到准确、可复现、可验证,而且完全离线。
验证方法:断网,工具照样工作;打开任务管理器,全程零网络请求。
| FapiaoVault | 云端 OCR 类工具 | 纯手动整理 | |
|---|---|---|---|
| 数据是否离开本机 | 从不 | 图片会上传 | 否 |
| 需要 API Key / 联网 | 不需要 | 需要 | 不需要 |
| 识别方式 | 文本层确定性解析 | 大模型/OCR(有误差) | 人眼 |
| 需要配置邮箱授权码 | 不需要(仅可选的邮箱抓取功能) | 通常需要 | 不需要 |
| 发票查重 | 批内 + 跨期台账 | 少见 | 靠记性 |
| 抬头/税号合规校验 | 支持(含校验位) | 少见 | 靠人眼 |
| 处理速度 | 毫秒级 | 受网络与额度限制 | 按小时算 |
| 安装体积 | 几百 KB | 常需下载几百 MB 运行时 | — |
git clone https://github.com/dfhhvc/fapiao-vault.git
cd fapiao-vault
pip install -e ".[pdf,excel]"pdf:解析 PDF 发票(唯一的第三方依赖,OFD 与 XML 用标准库即可解析)excel:导出.xlsx(不装会自动降级为 CSV,功能不受影响)
双击项目根目录下的 一键整理发票.bat(首次运行会自动安装 pypdf):
把任意发票文件夹拖到它的图标上,就会扫描该文件夹;直接双击则扫描 samples 示例目录。
扫描完成后会自动打开 HTML 报告。
开启抬头校验(推荐):把 报销配置.example.txt 复制一份、重命名为 报销配置.txt
(与 bat 同目录),用记事本把第一行改成你的公司全称、第二行改成 18 位税号。
之后每次扫描都会自动核对抬头与税号,开错的发票会被单独标红提示;
不放置该文件也完全能用,只是跳过抬头校验。
# 扫描 + 生成报表
fapiao scan ./我的发票
# 扫描 + 按月份归档重命名(原文件保留,只做复制)
fapiao scan ./我的发票 --archive
# 从邮箱一键抓取发票附件(可选联网功能,抓完接 scan 即可)
# 授权码用环境变量传入,避免进入 shell 历史
$env:FAPIAO_IMAP_PASSWORD = "你的授权码" # bash: export FAPIAO_IMAP_PASSWORD=...
fapiao fetch --host imap.qq.com --user me@qq.com --dir ./我的发票 --password-env FAPIAO_IMAP_PASSWORD
fapiao scan ./我的发票 --archive
# 带上公司抬头,自动检查每张票的抬头和税号是否会报销被退回
fapiao scan ./我的发票 --company "某某科技有限公司" --tax-id "91330100MA2XXXXXX2"
# 校验一个统一社会信用代码是否合法
fapiao verify 91330100MA2XXXXXX2扫描后会生成 _报表/ 目录:
| 文件 | 用途 |
|---|---|
发票报告.html |
双击即可离线打开的可视化报告,有搜索、筛选、汇总 |
发票汇总.xlsx |
三个工作表:发票明细 / 报销汇总 / 重复发票 |
发票明细.csv |
带 UTF-8 BOM,Excel 双击不乱码 |
台账.json |
历史台账,下次扫描自动读取,用于发现跨月重复报销 |
归档结果长这样(自动按月份分目录、自动去重命名、不覆盖已有文件):
_归档/
└── 2026-09/
├── 2026-09-01_示例出行有限公司_42.50元_25117000000000000001_普通发票.pdf
├── 2026-09-03_示例酒店有限公司_123.60元_25117000000000000002_普通发票.ofd
└── 2026-09-01_示例出行有限公司_42.50元_25117000000000000001_普通发票_2.pdf ← 重复件
工具在解析后会做这些校验,并把问题写进报告:
- 跨期重复报销(最高风险):本次扫描的票号已存在于历史台账且月份不同 —— 这是财务最警惕的场景,会与批内查重分开单独标红
- 重复报销风险(高风险):同一张发票号出现在不同月份的扫描批次内
- 同一张票重复收集:同批次内票号相同,或文件内容完全一致(sha256)
- 抬头不符:购买方名称与你公司的抬头不一致
- 税号错误:购买方税号与公司税号不符,或校验位算不过(GB 32100-2015)
- 金额不勾稽:金额 + 税额 ≠ 价税合计(超过 1 分钱)
- 大小写金额不一致:中文大写与小写对不上,这是发票被涂改的信号
- 发票号码格式异常:数电票应为 20 位且无发票代码;传统票为 12 位代码 + 8 位号码
| 格式 | 支持 | 说明 |
|---|---|---|
| PDF(数电票 / 增值税电子发票) | ✅ | 读文本层,无需 OCR |
| OFD | ✅ | 纯标准库解析(OFD 本质是 ZIP + XML) |
| XML(数电票原件) | ✅ | 结构化字段直读,零误差 |
| 图片(jpg / png / 扫描件) | ❌ | 刻意不做 OCR,会被识别出来并提示你改用原始电子发票 |
工具只处理自带文本层的电子发票。绝大多数来自邮箱、微信、支付宝、电商平台的 发票下载件都属于这一类。真正的纸质扫描件不在射程内——这是刻意的边界, 因为要把它们做对就必须上 OCR,那会破坏"离线 + 确定性"这两个核心承诺。
票种覆盖:数电票全票种——普通发票 / 专用发票 / 铁路电子客票 / 航空运输电子客票行程单(后两种票面没有销售方区块,解析时不会误报), 以及增值税电子普通 / 专用发票等老版式。
发票文件
│
├─ .pdf ─→ pypdf 抽取文本层 ─┐
├─ .ofd ─→ 解 ZIP,遍历 XML 文字节点 ─┤
└─ .xml ─→ 直接读结构化字段 ──────────┤
▼
字段抽取(正则 + 版式规则)
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
合规校验 三级查重 置信度评估
(税号校验位/ (票号 → (字段完整度
金额勾稽/抬头) 文件指纹 → + 问题数量)
内容键)
│
▼
归档重命名 + HTML/Excel/CSV/JSON 报表
两个值得一提的工程细节:
- 文本层版式兼容。数电票的购买方/销售方用纵向标签(购/买/方/信/息),
PDF 抽取后这些字会与字段值粘连(
…有限公司买)。工具用"分区切分 + 出现顺序兜底" 双路径识别,两种版式都有测试样例锁定。 - 查重不误报。完全没解析出内容的文件(既无号码也无金额)不参与内容级查重, 否则一堆"解析失败"的文件会被误报成互相重复。
| 承诺 | 你自己怎么验证 |
|---|---|
| scan 不发起任何网络请求 | 断网运行,或开抓包工具全程观察 |
| fetch 只连接你自己填写的 IMAP 服务器 | 抓取模块仅用标准库 imaplib,源码可全文审阅;授权码走环境变量或交互输入,不进命令行历史、不落盘 |
| 不上传发票内容给任何第三方 | 代码里没有 requests / urllib / httpx 等网络调用 |
| 不改动你的原始文件 | 归档是复制不是移动;重名自动加序号,绝不覆盖 |
| 不写注册表、不留后台 | 纯命令行/库,跑完即退出 |
唯一会写文件的地方:你指定的输出目录(默认 _报表/ 与 _归档/)。
python -m unittest discover -s tests119 个测试,零测试依赖(只用标准库 unittest),覆盖:
- 统一社会信用代码校验位(含手工验算的样例,不循环自证)
- 中文大写金额解析(元/角/分、万、拾 的边界)
- 多种真实与对抗性文本层版式(分区完整 / 纵向标签尾部粘连 / 头部粘连 / 逐字打散)
- 报销高频票种版式:铁路电子客票 / 航空行程单(无销售方区块时不误报)
- 邮件抓取:MIME 附件提取、中文文件名解码与清洗、重复下载保护(全部本地构造,不联网)
- PDF / OFD / XML 三种格式的端到端解析(测试样例由代码动态生成)
- 三级查重、跨期重复、历史台账(含"工具写出的台账必须能被读回"的回归测试)
- 报表输出(含 HTML 自包含性与 XSS 转义)
- 归档重命名与不覆盖保证
- CLI 全命令
CI 在 Ubuntu / Windows × Python 3.9–3.13 十个组合上跑全部测试,
外加一个不装任何 extras 的 core-only 任务,守住"零硬依赖"的承诺。
生成测试样例:
python tools/make_fixtures.py samples样例中的票号、税号、公司名全部是虚构数据,仅用于测试,不对应任何真实票据。 样例 PDF 由代码用 PDF 规范内置的中文 CID 字体生成,不依赖任何第三方库。
Q:为什么不用 AI / OCR? A:因为没必要,而且代价更高。电子发票自带文本层,规则解析是确定性的——同样的输入 永远得到同样的输出,可以写测试,出错能定位到具体规则。OCR 是概率性的,还要求你把 发票图片传给第三方。只有在处理扫描件时才需要 OCR,而那不是本工具的目标场景。
Q:我的发票是图片怎么办? A:去开票平台重新下载原始 PDF/OFD/XML 原件(通常免费且即时),比 OCR 更准。 工具会明确告诉你哪些文件属于这一类。
Q:会不会把我的发票弄丢? A:归档只做复制。原始文件从头到尾不会被移动、改名或删除。
Q:能处理几百张发票吗? A:可以。解析是纯本地字符串处理,速度在毫秒级。
Q:邮箱抓取怎么用?安全吗?
A:fapiao fetch 只用 Python 标准库 imaplib 连接你自己填写的 IMAP 服务器,
授权码通过环境变量(--password-env)或交互式输入传入——不进命令行历史、
不落盘,也不会把发票数据发给任何第三方。抓完的附件就在你指定的本地目录,
之后的整理查重照旧离线。授权码获取:QQ 邮箱在网页版「设置-账户」开启 IMAP
并生成授权码;163/126 在「设置-POP3/SMTP/IMAP」开启服务获取授权密码。
fapiao-vault/
├── 一键整理发票.bat # Windows 双击/拖拽入口
├── 报销配置.example.txt # 抬头校验配置模板(复制为 报销配置.txt 生效)
├── src/fapiao_vault/
│ ├── models.py # Invoice 数据模型(金额用 Decimal)
│ ├── fields.py # 字段抽取:三种文本视图 + 版式规则
│ ├── validate.py # 税号校验位、发票号码格式、抬头合规、金额勾稽
│ ├── dedup.py # 三级查重引擎 + 历史台账
│ ├── parsers/ # pdf / ofd / xml 三个解析器 + 调度
│ ├── pipeline.py # 扫描流水线
│ ├── archive.py # 归档重命名
│ ├── report.py # HTML / Excel / CSV / JSON 报表
│ ├── mail_fetch.py # IMAP 邮箱抓取(可选,仅标准库)
│ └── cli.py # 命令行入口
├── tests/ # 119 个单元测试(标准库 unittest)
├── tools/make_fixtures.py # 合成测试样例生成器
├── samples/ # 生成的测试样例
└── docs/ # logo 与 README 演示图
-
火车票(铁路电子客票)/ 行程单的专项解析(v1.1.0 已完成,含版式回归测试) -
IMAP 邮箱一键抓取发票附件(v1.1.0 已完成,纯标准库) - 增值税专用发票、出租车票的专项解析规则
- 按公司报销制度自定义校验规则(如"单张超过 500 元需附明细")
- 跨年度台账归档与检索
- 桌面 GUI(保持零依赖、零上传)
欢迎提交 Issue 和 PR,详见贡献指南。如果遇到解析失败的发票, 请把脱敏后的票面文本(而不是原始票据)附在 Issue 里,那是最有效的反馈方式。
安全漏洞请勿公开 issue,报告方式见安全策略。
