多页不规则PDF发票数据提取至Excel的Python技术求助
解决不规则PDF发票数据提取问题
核心问题分析
原代码的主要问题包括:
- 正则表达式分组错位,导致提取字段混乱
- 正则规则过于严格,无法适配格式波动的发票行
- 缺失值未按要求填充为0
- 输出列名与目标要求不匹配
修正后的代码实现
import re import pdfplumber import pandas as pd from collections import namedtuple # 匹配目标列名的namedtuple,与需求完全对应 Inv = namedtuple('Inv', [ 'area_name', 'Date', 'Tran', 'Inv #', 'Product', 'Description', 'Packing', 'Rate', 'Qty', 'Bonus', 'Gross', 'Discount', 'Sales', 'Tax', 'Net Amt' ]) # 预定义区域名称正则,保留精确匹配 area_pattern = re.compile( r'\b(Chichawatni|Harrapa|Kasowal|Mianchannu|Malka Hans|Gogera|Qabula|Arif Wala|Sahiwal|90 Morh|S1 High Street|S2 Mission Chowk|S3 DHQ Hospital|S4 Farid Town|S5 Jahaz Ground|S7 Karbala Road|S7 Mazdoor Puli|S8 TBZ Colony)\b', re.IGNORECASE ) # 优化后的发票行正则:适配可选字段、多空格、格式波动 line_pattern = re.compile( r'(\d{2}-[A-Za-z]{3}-\d{4})' # Date r'\s+(Sale|Return)?' # Tran(可选) r'\s*(\S+)' # Inv # r'\s+([A-Za-z0-9\-\s]+?)' # Product(非贪婪匹配避免溢出) r'\s+([A-Za-z0-9\-\s]+?)' # Description r'\s+(\d+[A-Za-z]+)' # Packing r'\s+([\d\.]+)' # Rate r'\s+(\d+)' # Qty r'\s+([\d\.]+)' # Bonus r'\s+([\d\.]+)' # Gross r'\s+([\d\.]+)' # Discount r'\s+([\d\.]+)' # Sales r'\s+([\d\.]+)' # Tax r'\s+([\d\.]+)' # Net Amt , re.IGNORECASE ) lines = [] with pdfplumber.open('AK PHARMA (1).pdf') as pdf: for page in pdf.pages: text = page.extract_text() if not text: continue current_area = None for line in text.split('\n'): line = line.strip() if not line: continue # 匹配区域名称 area_match = area_pattern.search(line) if area_match: current_area = area_match.group(0).strip() continue # 匹配发票数据行 line_match = line_pattern.match(line) if line_match and current_area: # 提取所有分组,缺失字段填充为0 groups = list(line_match.groups()) # 处理可选的Tran字段 if groups[1] is None: groups[1] = '0' # 数值字段兜底处理 for i in range(3, len(groups)): if groups[i] is None: groups[i] = '0' # 构造Inv对象 inv_entry = Inv(current_area, *groups) lines.append(inv_entry) # 转换为DataFrame并处理缺失值 df = pd.DataFrame(lines) # 数值列转换为float并填充缺失值 numeric_cols = ['Rate', 'Qty', 'Bonus', 'Gross', 'Discount', 'Sales', 'Tax', 'Net Amt'] df[numeric_cols] = df[numeric_cols].apply(pd.to_numeric, errors='coerce').fillna(0) # Tran字段缺失填充0 df['Tran'] = df['Tran'].fillna('0') # 输出为Excel文件 df.to_excel('AK_Pharma_Invoices.xlsx', index=False) print("数据提取完成,已保存为AK_Pharma_Invoices.xlsx")
关键优化点说明
- 正则修正:调整分组顺序,确保每个字段对应正确位置;使用非贪婪匹配解决字段溢出问题;标记
Tran为可选字段,适配无交易类型的行。 - 缺失值处理:通过
fillna(0)统一填充所有缺失的数值和文本字段,符合需求。 - 列名对齐:完全匹配目标列名,避免后续映射麻烦。
- 鲁棒性提升:增加空文本、空行的跳过逻辑,避免无效循环。
内容的提问来源于stack exchange,提问作者Hannan Wali
相关产品推荐
相关产品推荐

