codex-app发票整理技能:批量提取中文电子发票信息并自动生成Excel报表

使用 Python + pdftotext 实现发票PDF批量处理,自动提取开票日期、小写金额等关键信息,按模板生成费用报销Excel报表。

背景

在企业财务报销场景中,员工常需提交大量PDF格式的中文电子发票。手动提取每张发票的开票日期、小写金额、销售方等信息并填入Excel模板,耗时且易出错。本文介绍一种自动化解决方案,实现从PDF发票到Excel报表的全流程自动化。

技术栈

  • pdftotext: Poppler 工具集,用于从PDF提取文本
  • Python 3: 数据处理和Excel生成
  • openpyxl: Excel文件读写库
  • 正则表达式: 解析中文发票文本结构

核心思路

  1. 文本提取 : 使用 pdftotext 将PDF发票转换为纯文本
  2. 模式匹配: 通过正则表达式定位关键字段(开票日期、金额、销售方等)
  3. 模板映射: 将提取数据映射到预定义的Excel模板列
  4. 批量输出: 生成结构化Excel报表

前置条件

bash 复制代码
# 安装 Poppler (macOS)
brew install poppler

# 安装 Python 依赖
pip install openpyxl

实现步骤

步骤1:列出发票文件

python 复制代码
import os

pdf_dir = "/path/to/invoices"
pdf_files = sorted([f for f in os.listdir(pdf_dir) if f.endswith('.pdf')])

步骤2:提取PDF文本

python 复制代码
import subprocess

def extract_pdf_text(pdf_path: str) -> str:
    """使用 pdftotext 从 PDF 提取文本"""
    result = subprocess.run(
        ['pdftotext', pdf_path, '-'],
        capture_output=True, text=True, timeout=30
    )
    return result.stdout

步骤3:解析关键字段

中文电子发票具有固定文本结构,可通过正则表达式定位:

python 复制代码
import re

def parse_invoice(text: str, filename: str) -> dict:
    """解析发票文本,提取关键字段"""
    invoice = {
        'file': os.path.basename(filename),
        'date': '',
        'amount': 0.0,
        'seller': '',
        'item': '',
        'number': ''
    }

    # 提取开票日期
    date_match = re.search(r'开票日期[::]\s*(\d{4})年(\d{2})月(\d{2})日', text)
    if date_match:
        invoice['date'] = f"{date_match.group(1)}年{date_match.group(2)}月{date_match.group(3)}日"

    # 提取小写金额
    amount_match = re.search(r'(小写)\s*¥\s*([\d.]+)', text)
    if amount_match:
        invoice['amount'] = float(amount_match.group(1))

    # 提取销售方名称
    seller_match = re.search(r'销售方\s*名称[::]\s*([^\n]+)', text)
    if seller_match:
        invoice['seller'] = seller_match.group(1).strip()

    # 提取项目名称
    item_match = re.search(r'项目名称\s*\n\s*\*[^*]+\*([^\n]+)', text)
    if item_match:
        invoice['item'] = item_match.group(1).strip()

    # 提取发票号码
    number_match = re.search(r'发票号码[::]\s*([\d]+)', text)
    if number_match:
        invoice['number'] = number_match.group(1)

    return invoice

步骤4:生成Excel报表

python 复制代码
import openpyxl

def generate_excel(invoices: list, template_path: str, output_path: str):
    """从提取的发票数据生成 Excel 文件"""
    wb = openpyxl.load_workbook(template_path)
    ws = wb.active

    # 从模板第2行获取固定值
    fixed_values = {}
    for col_num in range(1, ws.max_column + 1):
        cell = ws.cell(row=2, column=col_num)
        fixed_values[col_num] = cell.value

    # 从第7行开始写入新数据(假设模板已有6行数据)
    start_row = 7

    for i, inv in enumerate(invoices):
        row = start_row + i
        ws.cell(row=row, column=1).value = i + 1  # 序号
        ws.cell(row=row, column=2).value = fixed_values.get(2)  # 审批编号
        ws.cell(row=row, column=3).value = fixed_values.get(3)  # 公司名称
        ws.cell(row=row, column=4).value = fixed_values.get(4)  # 报销人
        ws.cell(row=row, column=5).value = f'业务招待费-{inv["item"]}'  # 费用明细
        ws.cell(row=row, column=6).value = inv['amount']  # 报销金额
        ws.cell(row=row, column=9).value = inv['seller']  # 招待对象
        ws.cell(row=row, column=13).value = inv['date']  # 备注 (开票日期)

    wb.save(output_path)
    print(f"已生成: {output_path}")
    print(f"记录数: {len(invoices)}")

完整脚本

python 复制代码
#!/usr/bin/env python3
"""批量提取发票信息并生成Excel报表"""

import os
import sys
import re
import subprocess
import openpyxl
from pathlib import Path


def extract_pdf_text(pdf_path: str) -> str:
    """从 PDF 提取文本"""
    try:
        result = subprocess.run(
            ['pdftotext', pdf_path, '-'],
            capture_output=True, text=True, timeout=30
        )
        return result.stdout
    except Exception as e:
        print(f"提取失败 {pdf_path}: {e}")
        return ""


def parse_invoice(text: str, filename: str) -> dict:
    """解析发票文本,提取关键字段"""
    invoice = {
        'file': os.path.basename(filename),
        'date': '',
        'amount': 0.0,
        'seller': '',
        'item': '',
        'number': ''
    }

    # 提取开票日期
    date_match = re.search(r'开票日期[::]\s*(\d{4})年(\d{2})月(\d{2})日', text)
    if date_match:
        invoice['date'] = f"{date_match.group(1)}年{date_match.group(2)}月{date_match.group(3)}日"

    # 提取小写金额
    amount_match = re.search(r'(小写)\s*¥\s*([\d.]+)', text)
    if amount_match:
        invoice['amount'] = float(amount_match.group(1))

    # 提取销售方名称
    seller_match = re.search(r'销售方\s*名称[::]\s*([^\n]+)', text)
    if seller_match:
        invoice['seller'] = seller_match.group(1).strip()

    # 提取项目名称
    item_match = re.search(r'项目名称\s*\n\s*\*[^*]+\*([^\n]+)', text)
    if item_match:
        invoice['item'] = item_match.group(1).strip()

    # 提取发票号码
    number_match = re.search(r'发票号码[::]\s*([\d]+)', text)
    if number_match:
        invoice['number'] = number_match.group(1)

    return invoice


def generate_excel(invoices: list, template_path: str, output_path: str):
    """生成 Excel 报表"""
    wb = openpyxl.load_workbook(template_path)
    ws = wb.active

    # 获取固定值
    fixed_values = {}
    for col_num in range(1, ws.max_column + 1):
        cell = ws.cell(row=2, column=col_num)
        fixed_values[col_num] = cell.value

    start_row = 7

    for i, inv in enumerate(invoices):
        row = start_row + i
        ws.cell(row=row, column=1).value = i + 1
        ws.cell(row=row, column=2).value = fixed_values.get(2)
        ws.cell(row=row, column=3).value = fixed_values.get(3)
        ws.cell(row=row, column=4).value = fixed_values.get(4)
        ws.cell(row=row, column=5).value = f'业务招待费-{inv["item"]}'
        ws.cell(row=row, column=6).value = inv['amount']
        ws.cell(row=row, column=9).value = inv['seller']
        ws.cell(row=row, column=13).value = inv['date']

    wb.save(output_path)
    print(f"已生成: {output_path}")
    print(f"记录数: {len(invoices)}")


def main():
    if len(sys.argv) < 3:
        print("用法: python extract_invoices.py <pdf目录> <模板.xlsx> [输出.xlsx]")
        sys.exit(1)

    pdf_dir = sys.argv[1]
    template_path = sys.argv[2]
    output_path = sys.argv[3] if len(sys.argv) > 3 else 'output.xlsx'

    # 查找所有 PDF 文件
    pdf_files = sorted([f for f in os.listdir(pdf_dir) if f.endswith('.pdf')])

    if not pdf_files:
        print("未找到 PDF 文件。")
        sys.exit(1)

    # 提取每张发票数据
    invoices = []
    for pdf_file in pdf_files:
        pdf_path = os.path.join(pdf_dir, pdf_file)
        print(f"处理: {pdf_file}")

        text = extract_pdf_text(pdf_path)
        if text:
            invoice = parse_invoice(text, pdf_path)
            if invoice['date'] or invoice['amount']:
                invoices.append(invoice)
            else:
                print(f"  警告: 无法从 {pdf_file} 提取日期/金额")

    if invoices:
        generate_excel(invoices, template_path, output_path)
    else:
        print("未找到有效发票。")
        sys.exit(1)


if __name__ == '__main__':
    main()

使用方法

bash 复制代码
# 基本用法
python extract_invoices.py /path/to/invoices /path/to/template.xlsx output.xlsx

# 示例
python extract_invoices.py ./invoices 模板.xlsx 5月份报销.xlsx

输出示例

序号 审批编号 公司名称 报销人 费用明细 报销金额
1 202605200002 XX技术有限公司 王XX 业务招待费-餐费 356.00
2 202605200002 XX技术有限公司 王XX 业务招待费-餐费 177.00
3 202605200002 XX技术有限公司 王XX 业务招待费-餐饮服务 316.00

注意事项

  1. PDF 格式: 仅适用于标准中文电子发票PDF,手写或扫描件需OCR预处理
  2. 文本提取 : pdftotext 对某些加密PDF可能失效,需先解密
  3. 模板匹配: 需确保Excel模板列结构与代码映射一致
  4. 错误处理: 建议添加日志记录,便于排查提取失败的文件

扩展方向

  • 支持增值税专用/普通发票自动分类
  • 添加发票验真接口(对接税务局API)
  • 支持图片格式发票(OCR预处理)
  • Web界面上传和批量处理
  • 与财务系统API对接自动提交

总结

本文介绍了使用 Python 批量提取中文电子发票信息的完整方案。通过 pdftotext + 正则表达式解析 + openpyxl 生成Excel,实现了从PDF发票到结构化报表的全流程自动化,大幅提升了财务报销工作效率。

以上过程可以直接使用codex-app帮你生成一个发票整理技能。