Python实现pdf电子发票信息提取到excel表格

2025-05-28 15:50

本文主要是介绍Python实现pdf电子发票信息提取到excel表格,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Python实现pdf电子发票信息提取到excel表格》这篇文章主要为大家详细介绍了如何使用Python实现pdf电子发票信息提取并保存到excel表格,文中的示例代码讲解详细,感兴趣的小伙伴可以跟...

应用场景

电子发票信息提取系统主要应用于以下场景:

企业财务部门:需要处理大量电子发票,提取关键信息(如发票代码、号码、金额等)并录入财务系统。

会计事务所:在进行审计或账务处理时,需要从大量发票中提取信息进行分析。

报销管理:员工提交电子发票进行报销时,系统自动提取信息,减少人工录入错误。

档案管理:对电子发票进行分类、归档和检索时,提取的信息可以作为索引。

数据分析:从大量发票中提取数据,进行企业支出分析、税 务筹划等。

界面设计

系统采用图形化界面设计,主要包含以下几个部分:

Python实现pdf电子发票信息提取到excel表格

文件选择区域:提供 "选择文件" 和 "选择文件夹" 按钮,方便用户批量选择电子发票文件。

文件列表区域:显示已选择的文件列表,支持多选操作。

处理选项区域:用户可以指定输出 Excel 文件的路径和名称。

进度显示区域:包含进度条和状态文本,实时显示处理进度。

操作按钮区域:提供 "开始处理"、"清空列表" 和 "退出" 等功能按钮。

界面设计简洁明了,符合用户操作习惯,同时提供了必要的提示和反馈信息。

Python实现pdf电子发票信息提取到excel表格

详细代码步骤

import os
import re
import fitz  # PyMuPDF
import pandas as pd
from pdf2image import convert_from_path
import pytesseract
from PIL import Image
import XML.etree.ElementTree as ET
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
import logging
from datetime import datetime
 
# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='invoice_extractor.log'
)
logger = logging.getLogger(__name__)
 
class InvoiceExtractor:
    def __init__(self):
        # 初始化配置
        self.config = {
            'pdf': {
                'invoicphpe_code_pattern': r'发票代码:(\d+)',
                'invoice_number_pattern': r'发票号码:(\d+)',
                'date_pattern': r'日期:(\d{4}年\d{1,2}月\d{1,2}日)',
                'amount_pattern': r'金额:¥(\d+\.\d{2})',
                'tax_pattern': r'税额:¥(\d+\.\d{2})',
                'total_pattern': r'价税合计:¥(\d+\.\d{2})'
            },
            'ofd': {
                'invoice_code_xpath': './/TextObject[starts-with(text(), "发票代码")]/following-sibling::TextObject[1]',
                'invoice_number_xpath': './/TextObject[starts-with(text(), "发票号码")]/following-sibling::TextObject[1]',
                'date_xpath': './/TextObject[starts-with(text(), "日期")]/following-sibling::TextObject[1]',
                'amount_xpath': './/TextObject[starts-with(text(), "金额")]/following-sibling::TextObject[1]',
                'tax_xpath': './/TextObject[starts-with(text(), "税额")]/following-sibling::TextObject[1]',
                'total_xpath': './/TextObject[starts-with(text(), "价税合计")]/following-sibling::TextObject[1]'
            }
        }
    
    def extract_pdf_info(self, pdf_path):
        """提取PDF电子发票信息"""
        try:
            info = {
                '文件路径': pdf_path,
                '发票代码': '',
                '发票号码': '',
                '日期': '',
                '金额': '',
                '税额': '',
                '价税合计': ''
            }
            
            with fitz.open(pdf_path) as doc:
                text = ""
                for page in doc:
                    text += page.get_text()
            
            # 使用正则表达式提取信息
            for key, pattern in self.config['pdf'].items():
                match = re.search(pattern, text)
                if match:
                    info[key.replace('_pattern', '')] = match.group(1)
            
            # 如果无法通过文本提取,则使用OCR
            if not all(info.values()):
                images = convert_from_path(pdf_path)
                ocr_text = ""
                for image in images:
                    ocr_text += pytesseract.image_to_string(image, lang='chi_sim')
                
                for key, pattern in self.config['pdf'].items():
                    if not info[key.replace('_pattern', '')]:
                        match = re.search(pattern, ocr_text)
                        if match:
                            info[key.replace('_pattern', '')] = match.group(1)
            
            return info
        except Exception as e:
            logger.error(f"提取PDF {pdf_path} 信息失败: {str(e)}")
            return None
    
    def extract_ofd_info(self, ofd_path):
        """提取OFD电子发票信息"""
        try:
            info = {
                '文件路径': ofd_path,
                '发票代码': '',
                '发票号码': '',
                '日期': '',
                '金额': '',
                '税额': '',
                '价税合计': ''
            }
            
            # OFD文件实际上是一个ZIP压缩包
            # 这里简化处理,假设我们已经将OFD解压并获取到了XML文件
            # 实际应用中需要处理OFD文件的解压缩和解析
            # 以下代码仅为示例
            
            # 假设我们已经获取到了OFD的XML内容
            # tree = ET.parse(ofd_xml_path)
            # root = tree.getroot()
            
            # for key, xpath in self.config['ofd'].items():
            #     element = root.find(xpath)
            #     if element is not None:
            #         info[key.replace('_xpath', '')] = element.text
            
            # 由于OFD格式的复杂性,这里使用OCR作为替代方案
            images = convert_from_path(ofd_path)
            ocr_text = ""js
            for image in images:
                ocr_text += pytesseract.image_to_string(image, lang='chi_sim')
            
            for key, pattern in self.config['pdf'].items():
                if key in info:
                    match = re.search(pattern, ocr_text)
                    if match:
                        info[key.replace('_pattern', '')] = match.group(1)
            
            return info
        except Exception as e:
            logger.error(f"提取OFD {ofd_path} 信息失败: {str(e)}")
            return None
    
    def BATch_process_files(self, files, output_path):
        """批量处理文件并导出到Excel"""
        results = []
        total = len(files)
        processed = 0
        
        for file_path in files:
            try:
                file_ext = os.path.splitext(file_path)[1].lower()
                
                if file_ext == '.pdf':
                    info = self.extract_pdf_info(file_path)
                elif file_ext == '.ofd':
                    info = self.extract_ofd_info(file_path)
                else:
                    logger.warning(f"不支持的文件类型: {file_path}")
                    continue
                
                if info:
                    results.append(info)
            except Exception as e:
                logger.error(f"处理文件 {file_path} 时出错: {str(e)}")
            
            processed += 1
            yield processed, total
        
        # 导出到Excel
        if results:
            df = pd.DataFrame(results)
            df.to_excel(output_path, index=False)
            logger.info(f"成功导出 {len(results)} 条记录到 {output_path}")
            return True
        else:
            logger.warning("没有可导出的数据")
            return False
 
class InvoiceExtractorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("电子发票信息提取系统")
        self.root.geometry("800x600")
        
        self.extractor = InvoiceExtractor()
        self.selected_files = []
        self.is_processing = False
        
        self.create_widgets()
    
    def create_widgets(self):
        """创建GUI界面"""
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 文件选择区域
        file_frame = ttk.LabelFrame(main_frame, text="文件选择", padding="10")
        file_frame.pack(fill=tk.X, pady=5)
        
        ttk.Button(file_frame, text="选择文件", command=self.select_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_frame, text="选择文件夹", command=self.select_folder).pack(side=tk.LEFT, padx=5)
        
        self.file_count_var = tk.StringVar(value="已选择 0 个文件")
        ttk.Label(file_frame, textvariable=self.file_count_var).pack(side=tk.RIGHT, padx=5)
        
        # 文件列表区域
        list_frame = ttk.LabelFrame(main_frame, text="文件列表", padding="10")
        list_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        # 创建滚动条
        scrollbar = ttk.Scrollbar(list_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 创建文件列表
        self.file_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set, selectmode=tk.EXTENDED)
        self.file_listbox.pack(fill=tk.BOTH, expand=True)
        scrollbar.config(command=self.file_listbox.yview)
        
        # 处理选项区域
        options_frame = ttk.LabelFrame(main_frame, text="处理选项", padding="10")
        options_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(options_frame, text="输出文件:").pack(side=tk.LEFT, padx=5)
        
        default_output = os.path.join(os.getcwd(), f"发票信息_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")
        self.output_path_var = tk.StringVar(value=default_output)
        
        output_entry = ttk.Entry(options_frame, textvariable=self.output_path_var, width=50)
        output_entry.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(options_frame, text="浏览", command=self.browse_output).pack(side=tk.LEFT, padx=5)
        
        # 进度条区域
        progress_frame = ttk.Frame(main_frame, padding="10")
        progress_frame.pack(fill=tk.X, pady=5)
        
        self.progress_var = tk.DoubleVar(value=0)
        self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.pack(fill=tk.X)
        
        self.status_var = tk.StringVar(value="就绪")
        ttk.Label(progress_frame, textvariable=self.status_var).pack(anchor=tk.W)
        
        # 按钮区域
        button_frame = ttk.Frame(main_frame, padding="10")
        button_frame.pack(fill=tk.X, pady=5)
        
        self.process_button = ttk.Button(button_frame, text="开始处理", command=self.start_processing)
        self.process_button.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(button_frame, text="清空列表", command=self.clear_file_list).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="退出", command=self.root.quit).pack(side=tk.RIGHT, padx=5)
    
    def select_files(self):
        """选择多个文件"""
        if self.is_processing:
            return
        
        files = filedialog.askopenfilenames(
            title="python实现pdf电子发票信息提取到excel表格",
            filetypes=[("PDF文件", "*.pdf"), ("OFD文件", "*.ofd"), ("所有文件", "*.*")]
        )
        
        if files:
            self.selected_files = list(files)
            self.update_file_list()
    
    def select_folder(self):
        """选择文件夹"""
        if self.is_processing:
            return
        
        folder = filedialog.askdirectory(title="选择包含电子发票的文件夹")
        
        if folder:
            pdf_files = [os.path.join(folder, f) for f in os.listdir(folder) if f.lower().endswith('.pdf')]
            ofd_files = [os.path.join(folder, f) for f in os.listdir(folder) if f.lower().endswith('.ofd')]
            self.selected_files = pdf_files + ofd_files
            self.update_file_list()
    
    def update_file_list(self):
        """更新文件列表显示"""
        self.file_listbox.delete(0, tk.END)
        for file_path in self.selected_files:
            self.file_listbox.insert(tk.END, os.path.basename(file_path))
        self.file_count_var.set(f"已选择 {len(self.selected_files)} 个文件")
    
    def browse_output(self):
        """浏览输出文件位置"""
        if selfphp.is_processing:
            return
        
        output_file = filedialog.asksaveasfilename(
            title="Python实现pdf电子发票信息提取到excel表格",
            defaultextension=".xlsx",
            filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
        )
        
        if output_file:
            self.output_path_var.set(output_file)
    
    def clear_file_list(self):
        """清空文件列表"""
        if self.is_processing:
            return
        
        self.selected_files = []
        self.update_file_list()
    
    def start_processing(self):
        """开始处理文件"China编程""
        if self.is_processing or not self.selected_files:
            return
        
        output_path = self.output_path_var.get()
        if not output_path:
            messagebox.showerror("错误", "请指定输出文件")
            return
        
        # 确认是否覆盖现有文件
        if os.path.exists(output_path):
            if not messagebox.askyesno("确认", "输出文件已存在,是否覆盖?"):
                return
        
        self.is_processing = True
        self.process_button.config(state=tk.DISABLED)
        self.status_var.set("正在处理...")
        
        # 在单独的线程中处理文件
        threading.Thread(target=self.process_files_thread, daemon=True).start()
    
    def process_files_thread(self):
        """文件处理线程"""
        try:
            output_path = self.output_path_var.get()
            progress = 0
            total = len(self.selected_files)
            
            for processed, total in self.extractor.batch_process_files(self.selected_files, output_path):
                progress = (processed / total) * 100
                self.progress_var.set(progress)
                self.status_var.set(f"正在处理 {processed}/{total}")
                self.root.update_idletasks()
            
            self.progress_var.set(100)
            self.status_var.set("处理完成")
            messagebox.showinfo("成功", f"已成功处理 {total} 个文件\n结果已保存至: {output_path}")
        except Exception as e:
            logger.error(f"处理过程中出错: {str(e)}")
            self.status_var.set("处理出错")
            messagebox.showerror("错误", f"处理过程中出错: {str(e)}")
        finally:
            self.is_processing = False
            self.process_button.config(state=tk.NORMAL)
 
def main():
    """主函数"""
    try:
        # 检查依赖库
        import PyMuPDF
        import pandas
        import pdf2image
        import pytesseract
        from PIL import Image
        
        # 创建GUI
        root = tk.Tk()
        app = InvoiceExtractorGUI(root)
        root.mainloop()
    except ImportError as e:
        print(f"缺少必要的库: {str(e)}")
        print("请安装所有依赖库: pip install PyMuPDF pandas pdf2image pytesseract pillow")
    except Exception as e:
        print(f"程序启动出错: {str(e)}")
        logger.error(f"程序启动出错: {str(e)}")
 
if __name__ == "__main__":
    main()    

系统实现主要包含以下几个核心模块:

配置管理:设置 PDF 和 OFD 文件的信息提取规则,包括正则表达式模式和 OFD 的 XPath 表达式。

PDF 信息提取:使用 PyMuPDF 库读取 PDF 文本内容,通过正则表达式提取关键信息;如果文本提取失败,则使用 OCR 技术进行图像识别。

OChina编程FD 信息提取:OFD 文件结构复杂,本系统采用 OCR 技术作为主要提取方法,将 OFD 转换为图像后使用 pytesseract 进行文字识别。

批量处理:支持批量处理多个文件,并提供进度反馈。

数据导出:将提取的信息整理成 DataFrame,并导出为 Excel 文件。

图形界面:使用 tkinter 构建直观易用的图形界面,支持文件选择、处理选项设置和进度显示。

总结优化

该系统提供了一个基础的电子发票信息提取解决方案,具有以下优点:

  • 通用性:支持 PDF 和 OFD 两种主流电子发票格式。
  • 可扩展性:配置文件分离,易于添加新的发票格式或修改提取规则。
  • 用户友好:图形界面操作简单,适合非技术人员使用。
  • 日志记录:完整的日志记录,便于问题排查和系统优化。

然而,系统仍有以下可以优化的地方:

  • OFD 解析:当前使用 OCR 处理 OFD 文件效率较低,可以研究更高效的 OFD 解析库。
  • 提取规则优化:针对不同类型的发票,可能需要定制化的提取规则,可考虑添加规则配置界面。
  • 性能优化:对于大量文件的处理,可以引入多线程或异步处理提高效率。
  • 数据验证:增加提取信息的验证机制,提高数据准确性。
  • 用户体验:添加更多交互反馈,如文件预览、处理结果预览等功能。

通过不断优化和扩展,该系统可以满足更多场景的需求,提高电子发票信息处理的效率和准确性。

到此这篇关于Python实现pdf电子发票信息提取到excel表格的文章就介绍到这了,更多相关Python提取pdf信息保存到excel内容请搜索编程China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!

这篇关于Python实现pdf电子发票信息提取到excel表格的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154828

相关文章

SpringBoot实现文件记录日志及日志文件自动归档和压缩

《SpringBoot实现文件记录日志及日志文件自动归档和压缩》Logback是Java日志框架,通过Logger收集日志并经Appender输出至控制台、文件等,SpringBoot配置logbac... 目录1、什么是Logback2、SpringBoot实现文件记录日志,日志文件自动归档和压缩2.1、

基于Python实现智能天气提醒助手

《基于Python实现智能天气提醒助手》这篇文章主要来和大家分享一个实用的Python天气提醒助手开发方案,这个工具可以方便地集成到青龙面板或其他调度框架中使用,有需要的小伙伴可以参考一下... 目录项目概述核心功能技术实现1. 天气API集成2. AI建议生成3. 消息推送环境配置使用方法完整代码项目特点

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

《spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔)》:本文主要介绍spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔),本文通过实例图... 目录需求背景需求拆解设计流程及作用域逻辑处理代码逻辑需求背景公司要求,通过公司网络代理访问的请求需要做请

使用Python获取JS加载的数据的多种实现方法

《使用Python获取JS加载的数据的多种实现方法》在当今的互联网时代,网页数据的动态加载已经成为一种常见的技术手段,许多现代网站通过JavaScript(JS)动态加载内容,这使得传统的静态网页爬取... 目录引言一、动态 网页与js加载数据的原理二、python爬取JS加载数据的方法(一)分析网络请求1

Spring Security介绍及配置实现代码

《SpringSecurity介绍及配置实现代码》SpringSecurity是一个功能强大的Java安全框架,它提供了全面的安全认证(Authentication)和授权(Authorizatio... 目录简介Spring Security配置配置实现代码简介Spring Security是一个功能强

SpringCloud使用Nacos 配置中心实现配置自动刷新功能使用

《SpringCloud使用Nacos配置中心实现配置自动刷新功能使用》SpringCloud项目中使用Nacos作为配置中心可以方便开发及运维人员随时查看配置信息,及配置共享,并且Nacos支持配... 目录前言一、Nacos中集中配置方式?二、使用步骤1.使用$Value 注解2.使用@Configur

Python中合并列表(list)的六种方法小结

《Python中合并列表(list)的六种方法小结》本文主要介绍了Python中合并列表(list)的六种方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋... 目录一、直接用 + 合并列表二、用 extend() js方法三、用 zip() 函数交叉合并四、用

如何基于Python开发一个微信自动化工具

《如何基于Python开发一个微信自动化工具》在当今数字化办公场景中,自动化工具已成为提升工作效率的利器,本文将深入剖析一个基于Python的微信自动化工具开发全过程,有需要的小伙伴可以了解下... 目录概述功能全景1. 核心功能模块2. 特色功能效果展示1. 主界面概览2. 定时任务配置3. 操作日志演示

python多线程并发测试过程

《python多线程并发测试过程》:本文主要介绍python多线程并发测试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、并发与并行?二、同步与异步的概念?三、线程与进程的区别?需求1:多线程执行不同任务需求2:多线程执行相同任务总结一、并发与并行?1、

Python处理大量Excel文件的十个技巧分享

《Python处理大量Excel文件的十个技巧分享》每天被大量Excel文件折磨的你看过来!这是一份Python程序员整理的实用技巧,不说废话,直接上干货,文章通过代码示例讲解的非常详细,需要的朋友可... 目录一、批量读取多个Excel文件二、选择性读取工作表和列三、自动调整格式和样式四、智能数据清洗五、