用PaddleOCR+Python搞定机票电子行程单自动识别,再也不用手动录入了
前言
大家好,今天给大家带来一个实用的功能更新——机票电子行程单自动识别。
相信很多财务小伙伴都有这样的痛苦经历:收到一堆机票行程单PDF,要手动录入发票号码、金额、税号、航班信息……一个单子录下来,眼睛都花了。
现在,这个问题彻底解决了!
效果展示
先看看最终效果,上传一张机票行程单PDF,系统自动识别出所有信息:
发票号码: ####
开票日期: 2026-06-09
购买方: ####
购买税号: ####
销售方: ####
合计金额: 756.42元
合计税额: 63.58元
价税合计: 820.00元
备注: 旅客:#### 航班:#### 日期:2026-06-05 行程:#### #### T1-#### T2 票号:#### 保险费:#### 民航基金:####
一行代码都不用写,直接上传PDF就行!
遇到的三个坑
在开发过程中,遇到了三个典型问题,分享给大家避坑。
坑1:税号死活提取不出来
问题现象:购买方的统一社会信用代码怎么都提取不到。
原因分析:机票行程单的税号格式和普通发票不一样,它用的是 统一社会信用代码/纳税人识别号 这种长格式。
解决方案:
# 原来的写法(匹配不到)
m = re.search(r'纳税人识别号[::]\s*(.+)', full_text)
# 改成这样(支持多种格式)
m = re.search(r'(?:统一社会信用代码|纳税人识别号)[::/\s]*([A-Za-z0-9]+)', full_text)
关键点:用 (?:...|...) 同时匹配两种表述,[::/\s]* 兼容各种分隔符。
坑2:金额全部是0
问题现象:票价、税额、合计全部提取为0。
原因分析:机票行程单的金额格式比较特殊,中间夹着税率百分比:
至: CNY 550.46 CNY 155.96 9% CNY 63.58 CNY 50.00 CNY 0.00 CNY 820.00
原来的正则表达式只考虑了连续的CNY值,没考虑到中间会有 9% 这种税率标识。
解决方案:
# 原来的正则(匹配失败)
r'至[::]?\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+).*?CNY\s*([\d.]+)'
# 改成这样(兼容税率)
r'至[::]?\s*CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+(\d+)%\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)'
关键点:在第二个CNY后面加上 \s+(\d+)% 来匹配税率。
坑3:航班号识别错误
问题现象:明明是 3U8981,系统却识别成了 HA313。
原因分析:航班号有两种格式:
原来的正则 [A-Z]{2}\d{3,4} 只能匹配字母开头的,所以它错误地匹配了后面的 SHA313(销售网点代号)。
解决方案:
# 原来的写法(只能匹配字母开头)
flight_re = r'[A-Z]{2}\d{3,4}'
# 改成这样(支持数字开头)
flight_re = r'[A-Z0-9][A-Z]\d{3,4}'
关键点:第一个字符改为 [A-Z0-9],兼容数字开头的航班号。
金额校验的学问
机票行程单还有一个特殊之处:价税合计的计算方式和普通发票不一样。
普通发票:
价税合计 = 合计金额 + 合计税额
820.00 = 756.42 + 63.58 ✓
机票行程单:
价税合计 = 票价 + 燃油附加费 + 增值税税额 + 民航发展基金 + 其他税费
820.00 = 550.46 + 155.96 + 63.58 + 50.00 + 0.00
为了让校验通过,我们需要把民航发展基金等非税费用计入"合计金额":
# 计算逻辑
base_amount = total - tax_amount # 756.42 = 820.00 - 63.58
# 这样就满足:合计金额 + 合计税额 = 价税合计
完整解析代码
下面是完整的解析函数,可以直接使用:
def_parse_air_ticket_invoice(full_text, tables_data):
"""解析航空运输电子客票行程单"""
lines = full_text.splitlines()
# 1. 发票号码
invoice_num = ''
m = re.search(r'发票号码[::]\s*(\d+)', full_text)
if m:
invoice_num = m.group(1)
# 2. 旅客姓名
passenger = ''
m = re.search(r'旅客姓名\s+有效身份证件号码.*?\n\s*(\S+)', full_text)
if m:
passenger = m.group(1).strip()
# 3. 航班号(支持数字开头)
flight_no = ''
carrier = ''
flight_re = r'[A-Z0-9][A-Z]\d{3,4}'
m = re.search(rf'([一-龥]{{2,}})\s+({flight_re})', full_text)
if m:
carrier = m.group(1)
flight_no = m.group(2)
# 4. 出发地/目的地
departure = ''
destination = ''
m = re.search(r'自[::]\s*([一-龥]+(?:\s+[一-龥]+)*\s*T?\d*)', full_text)
if m:
departure = m.group(1).strip()
m = re.search(r'至[::]\s*([一-龥]+(?:\s+[一-龥]+)*\s*T?\d*)', full_text)
if m:
destination = m.group(1).strip()
# 5. 日期/时间
flight_date = ''
m = re.search(r'(\d{4})年(\d{1,2})月(\d{1,2})日\s+(\d{1,2}[::]\d{2})', full_text)
if m:
flight_date = f"{m.group(1)}-{int(m.group(2)):02d}-{int(m.group(3)):02d}"
# 6. 金额解析(兼容税率)
ticket_price = 0.0
fuel_surcharge = 0.0
tax_amount = 0.0
civil_fund = 0.0
total = 0.0
m = re.search(
r'至[::]?\s*CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+(\d+)%\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)\s+CNY\s+([\d.]+)',
full_text)
if m:
ticket_price = float(m.group(1))
fuel_surcharge = float(m.group(2))
tax_amount = float(m.group(4))
civil_fund = float(m.group(5))
total = float(m.group(7))
# 7. 税号解析
buyer_tax_id = ''
m = re.search(r'(?:统一社会信用代码|纳税人识别号)[::/\s]*([A-Za-z0-9]+)', full_text)
if m:
buyer_tax_id = m.group(1).strip()
# 8. 计算合计金额(满足校验公式)
base_amount = (total - tax_amount) if total and tax_amount else (ticket_price + fuel_surcharge)
# 9. 构造备注
remark_parts = []
if passenger:
remark_parts.append(f'旅客:{passenger}')
if flight_no:
remark_parts.append(f'航班:{flight_no}')
if flight_date:
remark_parts.append(f'日期:{flight_date}')
if departure or destination:
remark_parts.append(f'行程:{departure}-{destination}')
return {
'发票号码': invoice_num,
'购买税号': buyer_tax_id,
'合计金额': base_amount,
'合计税额': tax_amount,
'价税合计': total,
'备注': ' '.join(remark_parts),
}
测试验证
from extractors import extract_invoice_data
from validators import validate_invoice, risk_summary
# 解析PDF
basic, items = extract_invoice_data('机票行程单.pdf')
# 验证结果
issues = validate_invoice(basic)
summary = risk_summary(issues)
print(f"风险等级: {summary['risk']}") # ok
print(f"错误数: {summary['error']}") # 0
print(f"警告数: {summary['warn']}") # 0
总结
这次功能更新主要解决了三个核心问题:
税号提取:支持 统一社会信用代码/纳税人识别号 格式
金额解析:兼容包含税率的复杂金额格式
航班识别:支持数字开头的航班号(如3U8981)
技术要点:
希望这篇文章对大家有帮助!如果有问题,欢迎留言交流。
git clone https://gitee.com/michah/paddleocr_demo2
cd paddleocr_demo2
python start.py # 一键启动安装省时省力
本文代码基于Python + FastAPI + pdfplumber实现完整项目代码已开源,欢迎Star