发票识别总出错?这 5 个坑我帮你踩完了
发票识别总出错?这 5 个坑我帮你踩完了

发票识别总出错?这 5 个坑我帮你踩完了

大家好,我是正在实战各种 AI 项目的程序员晚枫。


😭 我踩过的那些坑

3 个月前

刚开始做发票自动识别项目。

信心满满:不就是调用个 API 吗?能有多难?

现实打脸

  • 密钥失效,代码跑不起来
  • 图片模糊,识别一塌糊涂
  • 网络超时,程序直接崩溃
  • Excel 格式不对,财务姐姐骂我
  • 密钥泄露,腾讯云账号被盗用

那段时间

  • 每天都在修 bug
  • 财务每天都在抱怨
  • 老板每天都在问进度
  • 我每天都在怀疑人生

但现在

所有坑都填平了,项目稳定运行 3 个月。

今天:把这 5 个坑分享给你,帮你避雷。


🕳️ 坑 1:密钥泄露(最危险)

问题描述

我做的

1
2
3
4
5
6
7
8
9
10
# ❌ 错误示范
SECRET_ID = 'AKIDxxxxxxxx'
SECRET_KEY = 'xxxxxxxxxxxxxxxx'

poocr.ocr2excel.VatInvoiceOCR2Excel(
input_path='./invoices',
output_path='./output',
id=SECRET_ID,
key=SECRET_KEY
)

然后:我把代码传到了 GitHub。

后果

  • 第 2 天,收到腾讯云短信:您的账户产生异常消费
  • 查账单:被盗用,产生了 2000 多元费用
  • 找客服:密钥已泄露,只能冻结账户

损失

  • 金钱:2000 多元(腾讯云赔付了大部分)
  • 时间:折腾 3 天
  • 心情:崩溃

正确做法

用环境变量

1
2
3
4
5
6
7
8
9
10
11
12
# ✅ 正确示范
import os

SECRET_ID = os.getenv('TENCENT_SECRET_ID')
SECRET_KEY = os.getenv('TENCENT_SECRET_KEY')

poocr.ocr2excel.VatInvoiceOCR2Excel(
input_path='./invoices',
output_path='./output',
id=SECRET_ID,
key=SECRET_KEY
)

配置环境变量(Windows):

1
2
setx TENCENT_SECRET_ID "你的 SecretId"
setx TENCENT_SECRET_KEY "你的 SecretKey"

或者用配置文件

1
2
3
4
5
6
# config.py
SECRET_ID = 'AKIDxxxxxxxx'
SECRET_KEY = 'xxxxxxxxxxxxxxxx'

# .gitignore
config.py

记住

  • 密钥不要硬编码
  • 不要上传到 GitHub
  • 定期更换密钥
  • 发现泄露立即冻结

🕳️ 坑 2:图片质量差(最常见)

问题描述

财务姐姐交来的发票

  • 手机拍的,手抖,模糊
  • 光线暗,看不清
  • 有反光,字被挡住
  • 折叠的,有褶皱
  • 斜着拍的,变形

识别结果

  • 发票号码识别错误
  • 金额识别错误
  • 日期识别错误
  • 干脆识别失败

财务姐姐说:"你这什么破工具,还不如我手录!"

正确做法

制定发票提交规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
📋 发票提交规范

1. 优先使用电子版 PDF 发票
2. 拍照发票要求:
- 光线充足
- 发票平整
- 无反光
- 正对拍摄
- 四角完整
3. 推荐使用扫描 APP:
- 扫描全能王
- 微软 Lens
- Adobe Scan
4. 不要折叠、不要揉搓

添加质量检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2

def check_image_quality(image_path):
"""检查图片质量"""
img = cv2.imread(image_path)

# 检查亮度
brightness = np.mean(img)
if brightness < 50:
return False, "图片太暗"

# 检查清晰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
variance = cv2.Laplacian(gray, cv2.CV_64F).var()
if variance < 100:
return False, "图片模糊"

return True, "合格"

# 使用前检查
is_ok, msg = check_image_quality('invoice.jpg')
if not is_ok:
print(f"❌ {msg},请重新拍照")

效果

  • 识别准确率从 95% → 98%+
  • 财务姐姐不骂了
  • 我也不崩溃了

🕳️ 坑 3:网络超时(最烦人)

问题描述

公司内网环境

  • 访问外网慢
  • 偶尔断网
  • 防火墙限制

程序表现

1
2
3
4
5
正在识别发票...
[超时] 请求失败
[超时] 请求失败
[超时] 请求失败
程序崩溃

财务姐姐:"怎么又失败了?"

:"网络问题……"

财务姐姐:"我不管,今天必须弄好!"

正确做法

添加重试机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import time
from requests.exceptions import Timeout, ConnectionError

def recognize_with_retry(input_path, output_path, max_retries=3):
"""带重试的识别函数"""
for i in range(max_retries):
try:
poocr.ocr2excel.VatInvoiceOCR2Excel(
input_path=input_path,
output_path=output_path,
id=SECRET_ID,
key=SECRET_KEY
)
print("✅ 识别成功")
return True
except (Timeout, ConnectionError) as e:
print(f"⚠️ 第{i+1}次失败:{e}")
if i < max_retries - 1:
print("⏳ 5 秒后重试...")
time.sleep(5)
else:
print("❌ 重试失败,请检查网络")
return False

# 使用
recognize_with_retry('./invoices', './output')

批量处理时分小批次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ❌ 一次处理 1000 张
poocr.ocr2excel.VatInvoiceOCR2Excel(
input_path='./1000_invoices',
...
)

# ✅ 分 10 批,每批 100 张
for i in range(10):
batch_path = f'./batch_{i}'
poocr.ocr2excel.VatInvoiceOCR2Excel(
input_path=batch_path,
...
)
print(f"✅ 第{i+1}批完成")

添加超时设置

1
2
3
4
# 在 poocr 库配置中设置超时
import poocr

poocr.config.timeout = 30 # 30 秒超时

效果

  • 程序不再崩溃
  • 失败自动重试
  • 财务姐姐满意了

🕳️ 坑 4:Excel 格式不对(最尴尬)

问题描述

我生成的 Excel

发票代码发票号码金额税额日期
0110021001131234567810001302026-03-15

财务姐姐要的

发票代码发票号码开票日期不含税金额税额价税合计备注
011002100113123456782026 年 03 月 15 日1,000.00130.001,130.00

财务姐姐:"这格式不对啊,我还要手动调整?"

:"……我改。"

正确做法

自定义输出模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import pandas as pd
from datetime import datetime

def format_invoice_excel(raw_data, output_path):
"""格式化 Excel 输出"""
# 读取原始数据
df = pd.read_excel(raw_data)

# 重命名列
df.rename(columns={
'金额': '不含税金额',
'日期': '开票日期'
}, inplace=True)

# 添加价税合计
df['价税合计'] = df['不含税金额'] + df['税额']

# 格式化日期
df['开票日期'] = df['开票日期'].apply(
lambda x: datetime.strftime(x, '%Y年%m月%d日')
)

# 格式化金额(保留 2 位小数)
for col in ['不含税金额', '税额', '价税合计']:
df[col] = df[col].apply(lambda x: f"{x:.2f}")

# 添加备注列
df['备注'] = ''

# 调整列顺序
df = df[['发票代码', '发票号码', '开票日期', '不含税金额',
'税额', '价税合计', '备注']]

# 保存
df.to_excel(output_path, index=False)
print(f"✅ 格式化完成:{output_path}")

# 使用
format_invoice_excel('./raw_data.xlsx', './output/发票识别结果.xlsx')

提前沟通格式

1
2
3
4
5
6
7
8
# 项目开始前,和财务确认格式
format_requirements = """
请确认 Excel 格式要求:
1. 列名:发票代码、发票号码、开票日期、不含税金额、税额、价税合计、备注
2. 日期格式:YYYY 年 MM 月 DD 日
3. 金额格式:保留 2 位小数,带千分位
4. 其他要求:_______
"""

效果

  • Excel 格式符合要求
  • 财务姐姐直接能用
  • 我不尴尬了

🕳️ 坑 5:异常发票处理(最容易忽略)

问题描述

98% 的发票:识别成功,没问题。

2% 的发票

  • 太模糊,识别失败
  • 发票类型特殊,不支持
  • 发票损坏,无法读取
  • 重复发票,需要标记

我之前没处理

  • 失败的发票没记录
  • 不知道哪些需要手动处理
  • 财务姐姐问:"这 5 张呢?"
  • 我:"……我找找。"

正确做法

添加异常记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import json
from datetime import datetime

def process_invoices_with_log(input_path, output_path):
"""处理发票并记录异常"""
failed_invoices = []

# 遍历所有发票
for invoice_file in os.listdir(input_path):
try:
poocr.ocr2excel.VatInvoiceOCR2Excel(
input_path=os.path.join(input_path, invoice_file),
output_path=output_path,
id=SECRET_ID,
key=SECRET_KEY
)
print(f"✅ {invoice_file} 识别成功")
except Exception as e:
print(f"❌ {invoice_file} 识别失败:{e}")
failed_invoices.append({
'filename': invoice_file,
'error': str(e),
'time': datetime.now().isoformat()
})

# 记录异常发票
if failed_invoices:
with open('./failed_invoices.json', 'w', encoding='utf-8') as f:
json.dump(failed_invoices, f, ensure_ascii=False, indent=2)
print(f"⚠️ 有{len(failed_invoices)}张发票识别失败,详见 failed_invoices.json")
else:
print("✅ 所有发票识别成功")

# 使用
process_invoices_with_log('./invoices', './output')

生成异常报告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def generate_failed_report(failed_list, output_path):
"""生成异常发票报告"""
report = "# 发票识别失败报告\n\n"
report += f"生成时间:{datetime.now()}\n\n"
report += f"失败数量:{len(failed_list)}\n\n"
report += "## 失败清单\n\n"
report += "| 文件名 | 错误原因 | 建议 |\n"
report += "|--------|----------|------|\n"

for item in failed_list:
suggestion = "重新拍照" if "模糊" in item['error'] else "手动处理"
report += f"| {item['filename']} | {item['error']} | {suggestion} |\n"

with open(output_path, 'w', encoding='utf-8') as f:
f.write(report)

print(f"📋 异常报告已生成:{output_path}")

效果

  • 异常发票有记录
  • 财务姐姐知道哪些需要手动处理
  • 责任清晰,不扯皮

📊 避坑检查清单

部署前检查

  • 密钥用环境变量配置
  • 密钥未上传到代码仓库
  • 制定了发票提交规范
  • 添加了重试机制
  • 批量处理分小批次
  • Excel 格式和财务确认过
  • 异常发票有记录
  • 有失败报告生成

全部打勾,再上线。


💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询


🎓 推荐课程


坑,我帮你踩完了。

路,我给你铺平了。

现在,轮到你了。

行动起来,让发票识别不再痛苦。 💪


P.S. 财务姐姐上周给我送了杯奶茶,说:"小枫啊,现在这工具真好用,不骂你了。"

我:"……谢谢姐姐。"

这杯奶茶,真甜。

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!