前言
前一篇文章讲了Python正则表达式的基本语法及主要的几个函数,本文以格式化的电子发票数据的抽取为例,展示正则表达式的便利。
一、电子发票数据提取
注:发票原样截图不让发,所以删除了。
1.提取电子pdf发票的数据
使用python的第三方包pdfplmber来提取数据,代码如下
import pdfplumber
with pdfplumber.open("./file/滴滴电子发票A.pdf") as pdf:
text = ""
for page in pdf.pages:
text += page.extract_text() # 提取文本内容
print(text)
输出文本:
2.确定提取要素
假设我们需要把主要的要素都提取出来,包括
‘发票类型’, ‘发票代码’, ‘发票号码’, ‘开票日期’, ‘校验码’, ‘机器编号’, ‘购买方名称’, ‘购买方纳税人识别号’, ‘购买方地址与电话’, ‘购买方开户行及账号’, ‘金额合计’, ‘税额合计’, ‘价税合计’, ‘销售方名称’, ‘销售方地址与电话’, ‘销售方开户行及账号’, ‘收款人’, ‘复核人’, '开票人’等。
3.完整代码
import pdfplumber
import wcwidth
import re
import csv
"""替换函数,插入年、月、日"""
def insert_date(match):
year = match.group(1)
month = match.group(2)
day = match.group(3)
return f"{year}年{month}月{day}日"
"""发票数据和发票标题在同一个格式中"""
def extract_invoice_from_pdf_together(pdf_path):
with pdfplumber.open(pdf_path) as pdf:
text = ""
for page in pdf.pages:
text += page.extract_text() # 提取文本内容
#print(text)
# 使用正则表达式提取关键字段(示例规则,需根据实际发票调整)
invoice_data = {
"发票类型":re.search(r'(.*)\s发票代码[::]',text).group(1),
"发票代码":re.search(r'发票代码[::](\d+)\s*\n',text).group(1),
"发票号码": re.search(r'发票号码[::]\s*(\w+)', text).group(1),
"开票日期": re.search(r'开票日期[::]\s*(\d{4}年\d{2}月\d{2}日)', text).group(1),
"校验码":re.search(r'校\s*验\s*码[::](\d+\s\d+\s\d+\s\d+)\s*\n',text).group(1),
"机器编号":re.search(r'机器编号[::]\s+(\d+)\s*\n',text).group(1),
"购买方名称":re.findall(r'名\s+称[::]\s*([\u4e00-\u9fff]+)',text)[0],
"购买方纳税人识别号":re.findall(r'纳税人识别号[::]\s*(\w+)\s+',text)[0],
"购买方地址与电话": re.findall(r'地\s*址[、\s]电\s*话[::]([\u4e00-\u9fff\w\-]*)',text)[0],
"购买方开户行及账号":re.findall(r'开户行及账号[::]([\u4e00-\u9fff]*\d*)',text)[0],
"金额合计":re.search(r'合\s+计\s*([¥¥]\d+\.\d{2})\s+([¥¥]\d+\.\d{2})',text).group(1),
"税额合计":re.search(r'合\s+计\s*([¥¥]\d+\.\d{2})\s+([¥¥]\d+\.\d{2})',text).group(2),
"价税合计": re.search(r'(小写)\s*([¥¥]\d+\.\d{2})', text).group(1),
"销售方名称": re.findall(r'名\s+称[::]\s*([\u4e00-\u9fff]+)',text)[1],
"销售方地址与电话": re.findall(r'地\s*址[、\s]电\s*话[::]([\u4e00-\u9fff\w\-]*)',text)[1],
"销售方开户行及账号":re.findall(r'开户行及账号[::]([\u4e00-\u9fff]*\d*)',text)[1],
"收款人":re.search(r'收\s*款\s*人[::]\s*([\u4e00-\u9fff]+)',text).group(1),
"复核人":re.search(r'复\s*核[::]\s*([\u4e00-\u9fff]+)',text).group(1),
"开票人":re.search(r'开\s*票\s*人[::]\s*([\u4e00-\u9fff]+)',text).group(1)
}
return invoice_data #返回提取后的发票数据
'''发票数据和发票标题不在同一个格式中'''
def extract_invoice_from_pdf_split(pdf_path):
with pdfplumber.open(pdf_path) as pdf:
text = ""
for page in pdf.pages:
text += page.extract_text() # 提取文本内容
# 使用正则表达式提取关键字段(示例规则,需根据实际发票调整)
date_txt = re.search("国家税务总局 章\s*(\d{4} \d{2} \d{2})",text).group(1)
pattern = r"(\d{4}) (\d{2}) (\d{2})"
print(text)
invoice_data = {
"金额合计":re.search(r'([¥¥]\d+\.\d{2})\s+[¥¥]\d+\.\d{2}',text).group(1),
"税额合计":re.search(r'[¥¥]\d+\.\d{2}\s+([¥¥]\d+\.\d{2})',text).group(1),
"价税合计(大写)":re.search(r'[¥¥]\d+\.\d{2}\s+[¥¥]\d+\.\d{2}\s*\n([\u4e00-\u9fff]*)\s+([¥¥]\d+\.\d{2})',text).group(1),
"价税合计(小写)":re.search(r'[¥¥]\d+\.\d{2}\s+[¥¥]\d+\.\d{2}\s*\n([\u4e00-\u9fff]*)\s+([¥¥]\d+\.\d{2})',text).group(2),
"发票号码": re.search(r'发票监\s*(\w+)', text).group(1),
"开票日期": re.sub(pattern,insert_date, date_txt)
}
return invoice_data #返回提取后的发票数据
"""根据输入文件路径,输出文件路径,电子发票类型差异调用不同的发票解析函数"""
def extract_invoice(infile_path,outfile_path,type):
if type == "together" :
# 使用示例
data = extract_invoice_from_pdf_together(infile_path)
with open(outfile_path,mode='w',newline="",encoding='utf-8') as csvfile:
csv_writer = csv.writer(csvfile)
csv_writer.writerow(data.keys())
csv_writer.writerow(data.values())
elif type == 'split':
data = extract_invoice_from_pdf_split(infile_path)
print(data)
extract_invoice("./file/滴滴电子发票A.pdf","./file/滴滴电子发票A.csv","together")
#extract_invoice("./file/invoice.pdf","./file/invoice.csv","split")
4.发票提取结果展示
总结
在实际的使用过程中,可能需要根据发票模板的不同,设计不同python代码模板,并在使用过程中不断优化和完善,最终达到能够完美解析各类电子发票的目标。另外,在企业的发票自动化处理过程中,应该存在识别后自动把结构化数据插入数据库的诉求,那么需要进一步加入数据库连接,动态sql组装等功能。