使用Python从零打造智能票据处理机器人

 更新时间:2026年04月20日 08:49:47   作者:MarkHD  
在企业日常运营中,发票报销是一个典型的高频、低效、易出错场景,本文将带领大家使用Python从零开始打造一个智能票据处理机器人,希望对大家有所帮助

引言:当报销流程遇见RPA

在企业日常运营中,发票报销是一个典型的高频、低效、易出错场景。财务人员每天要面对大量纸质或电子发票:人工录入金额、日期、供应商到Excel表格,再逐一发送给审核人。这个过程不仅耗时,而且容易因为手误导致数据错误。

那么,能否让一个“机器人”自动完成这些工作?答案是肯定的。本文将带领读者从零开始,打造一个智能票据处理机器人。它能够:

  • 自动扫描指定文件夹中的发票图片(JPG/PNG/PDF);
  • 使用OCR技术提取发票的关键信息(金额、日期、供应商名称);
  • 将提取的数据自动填写到Excel报销单模板中;
  • 最后通过邮件将填写好的Excel文件发送给指定的审核人。

本文不仅会给出完整的代码实现,还会深入讲解OCR选型与优化Excel自动化操作邮件发送以及异常处理与日志等RPA核心能力,帮助读者构建一套健壮、可维护的自动化解决方案。

一、项目概述与技术选型

1.1 业务流程设计

智能票据处理机器人的工作流如下:

[监控文件夹] → [发现新发票图片] → [OCR提取关键字段] → [写入Excel报销单] → [保存Excel文件] → [发送邮件给审核人] → [归档或移动原文件]

这是一个典型的事件驱动型RPA,可以通过定时任务(如每5分钟扫描一次)或文件系统监控(watchdog)来触发。

1.2 技术栈选择

功能模块技术方案理由
OCR识别PaddleOCR(轻量级中文模型)中文发票识别准确率高,支持印刷体,无需复杂预处理
Excel操作openpyxl支持.xlsx格式,功能强大,可读写单元格、样式、公式
邮件发送smtplib + emailPython标准库,稳定可靠,支持附件
文件监控watchdog + 定时轮询(简化版)生产环境可用watchdog,本文采用轮询降低复杂度
配置管理dotenv + 环境变量敏感信息不硬编码
日志logging标准库,便于调试和审计
图像预处理OpenCV (cv2) + PIL辅助处理倾斜、噪点等(如需)

1.3 环境准备

# 安装依赖
pip install paddlepaddle paddleocr openpyxl pillow opencv-python python-dotenv
# 邮件发送使用标准库,无需额外安装

二、OCR识别:从发票图片中提取关键信息

2.1 OCR引擎对比与选择

在RPA项目中,OCR的选型直接影响识别准确率和开发效率。常见方案对比:

OCR引擎优点缺点适用场景
Tesseract开源免费,支持多语言中文准确率一般,预处理复杂英文文档、简单验证码
PaddleOCR中文准确率高,轻量模型仅8.6MB依赖PaddlePaddle框架中文发票、身份证、表格
EasyOCR支持80+语言,GPU加速模型较大,速度较慢多语言混合场景
百度/阿里OCR API准确率最高(>95%),无需预处理收费、依赖网络、数据隐私生产级高要求场景

考虑到发票多为中文印刷体,且希望免费离线运行,本文选择PaddleOCR。它提供了轻量级的中文检测和识别模型,在普通CPU上也能达到实时识别速度。

2.2 使用PaddleOCR识别发票

初始化OCR引擎

from paddleocr import PaddleOCR

# 初始化OCR引擎(首次运行会自动下载模型)
ocr = PaddleOCR(
    use_angle_cls=True,   # 使用方向分类器,处理旋转文字
    lang='ch',            # 中文模型
    show_log=False        # 关闭冗余日志
)

识别并提取关键字段

发票上的关键信息通常以“键值对”形式出现,如“金额:¥100.00”、“开票日期:2025年03月15日”、“购买方名称:XX公司”。我们可以通过正则表达式从OCR识别的文本中提取。

import re

def extract_invoice_info(ocr_result):
    """
    从PaddleOCR识别结果中提取发票关键信息
    ocr_result格式: list of [box, (text, confidence)]
    """
    full_text = ""
    for line in ocr_result:
        text = line[1][0]
        confidence = line[1][1]
        if confidence > 0.7:  # 只保留高置信度结果
            full_text += text + " "
    
    # 提取金额(支持多种写法)
    amount_pattern = r'(?:金额|合计|总计)[::\s]*([¥¥]?(\d+(?:\.\d{1,2})?))'
    amount_match = re.search(amount_pattern, full_text)
    amount = amount_match.group(1) if amount_match else None
    
    # 提取开票日期(常见格式:YYYY年MM月DD日 或 YYYY-MM-DD)
    date_pattern = r'(?:开票日期|发票日期)[::\s]*(\d{4}[年-]\d{1,2}[月-]\d{1,2}日?)'
    date_match = re.search(date_pattern, full_text)
    invoice_date = date_match.group(1) if date_match else None
    
    # 提取供应商(销售方名称)
    seller_pattern = r'(?:销售方|出售方|供应商)[::\s]*([\u4e00-\u9fa5a-zA-Z0-9()()]+公司[\u4e00-\u9fa5]*)'
    seller_match = re.search(seller_pattern, full_text)
    seller = seller_match.group(1) if seller_match else None
    
    return {
        "amount": amount,
        "date": invoice_date,
        "supplier": seller,
        "raw_text": full_text
    }

完整的OCR调用函数

def ocr_invoice_image(image_path):
    """对单张发票图片执行OCR并返回结构化信息"""
    try:
        result = ocr.ocr(image_path, cls=True)
        if not result or not result[0]:
            raise ValueError("OCR未识别到任何文本")
        # result[0] 是图片中所有文本行
        info = extract_invoice_info(result[0])
        return info
    except Exception as e:
        print(f"OCR识别失败: {image_path}, 错误: {e}")
        return None

2.3 处理PDF格式的发票

实际场景中,供应商可能发送PDF格式的电子发票。我们可以借助pdf2image将PDF转换为图片,再调用OCR。

pip install pdf2image
# 还需要安装poppler(Windows需下载,Linux apt install poppler-utils)
from pdf2image import convert_from_path

def ocr_invoice_pdf(pdf_path):
    images = convert_from_path(pdf_path, dpi=200, first_page=1, last_page=1)
    if not images:
        return None
    # 将PIL Image转换为临时文件或直接处理
    import tempfile
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
        images[0].save(tmp.name, "PNG")
        result = ocr.ocr(tmp.name, cls=True)
        info = extract_invoice_info(result[0]) if result and result[0] else None
        return info

三、Excel自动填写:使用openpyxl操作报销单

3.1 设计Excel报销单模板

假设我们有一个名为报销单模板.xlsx的文件,结构如下:

A列(字段)B列(值)
日期
供应商
金额(元)
备注

或者更常见的列表式报销明细表(每行一条记录)。为简化,我们采用逐行追加的方式:每次处理一张发票,就在“报销明细”工作表中新增一行,填写日期、供应商、金额。

3.2 使用openpyxl读写Excel

from openpyxl import load_workbook
import os

def append_to_excel(excel_path, invoice_info):
    """
    将发票信息追加到Excel文件的报销明细表中
    假设工作表名为"报销明细",表头为:日期, 供应商, 金额, 备注
    """
    if not os.path.exists(excel_path):
        # 如果文件不存在,创建新文件并写入表头
        from openpyxl import Workbook
        wb = Workbook()
        ws = wb.active
        ws.title = "报销明细"
        ws.append(["日期", "供应商", "金额", "备注"])
    else:
        wb = load_workbook(excel_path)
        ws = wb["报销明细"]
    
    # 追加一行数据
    row = [
        invoice_info.get("date", ""),
        invoice_info.get("supplier", ""),
        invoice_info.get("amount", ""),
        f"OCR自动识别 {invoice_info.get('raw_text', '')[:20]}..."
    ]
    ws.append(row)
    
    # 保存文件
    wb.save(excel_path)
    print(f"已追加记录到 {excel_path}")

进阶技巧

  • 使用openpyxl.styles设置单元格格式(如金额保留两位小数);
  • 使用公式自动计算合计金额;
  • 保护工作表防止误修改。

四、邮件发送:将报销单发给审核人

4.1 使用smtplib发送带附件的邮件

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import os

def send_excel_by_email(receiver_email, excel_path, subject="报销单待审核", body="请查收本周报销明细"):
    """
    发送Excel文件作为附件到指定邮箱
    """
    # 邮箱配置(从环境变量读取)
    smtp_server = os.getenv("SMTP_SERVER", "smtp.qq.com")
    smtp_port = int(os.getenv("SMTP_PORT", "465"))
    sender_email = os.getenv("SENDER_EMAIL")
    sender_password = os.getenv("SENDER_PASSWORD")  # 授权码
    
    if not sender_email or not sender_password:
        raise ValueError("请在.env文件中配置发件邮箱和密码")
    
    # 构建邮件
    msg = MIMEMultipart()
    msg["From"] = sender_email
    msg["To"] = receiver_email
    msg["Subject"] = subject
    msg.attach(MIMEText(body, "plain", "utf-8"))
    
    # 添加附件
    with open(excel_path, "rb") as f:
        part = MIMEBase("application", "octet-stream")
        part.set_payload(f.read())
        encoders.encode_base64(part)
        filename = os.path.basename(excel_path)
        part.add_header("Content-Disposition", f"attachment; filename={filename}")
        msg.attach(part)
    
    # 发送
    with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, receiver_email, msg.as_string())
    print(f"邮件已发送至 {receiver_email}")

4.2 邮件配置管理(.env文件)

创建.env文件:

SMTP_SERVER=smtp.qq.com
SMTP_PORT=465
SENDER_EMAIL=rpa@company.com
SENDER_PASSWORD=your_authorization_code
AUDITOR_EMAIL=auditor@company.com

在代码中加载:

from dotenv import load_dotenv
load_dotenv()

五、整合实战:完整的票据处理机器人

5.1 项目结构

invoice_robot/
├── main.py              # 主程序入口
├── ocr_engine.py        # OCR识别模块
├── excel_handler.py     # Excel操作模块
├── mail_sender.py       # 邮件发送模块
├── config.py            # 配置加载
├── invoices/            # 待处理的发票图片文件夹
├── processed/           # 已处理的备份文件夹
├── output/              # 生成的Excel文件存放路径
├── .env                 # 环境变量
└── requirements.txt

5.2 主程序实现(main.py)

import os
import time
import shutil
from pathlib import Path
import logging
from dotenv import load_dotenv

from ocr_engine import ocr_invoice_image, ocr_invoice_pdf
from excel_handler import append_to_excel
from mail_sender import send_excel_by_email

# 加载配置
load_dotenv()
INPUT_DIR = "invoices"
PROCESSED_DIR = "processed"
OUTPUT_EXCEL = "output/报销明细.xlsx"
AUDITOR_EMAIL = os.getenv("AUDITOR_EMAIL")
SCAN_INTERVAL = 60  # 扫描间隔(秒)

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler("invoice_robot.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

def process_single_file(file_path):
    """处理单个发票文件"""
    logger.info(f"开始处理文件: {file_path}")
    ext = file_path.suffix.lower()
    
    # 1. OCR识别
    if ext in ['.jpg', '.jpeg', '.png']:
        info = ocr_invoice_image(str(file_path))
    elif ext == '.pdf':
        info = ocr_invoice_pdf(str(file_path))
    else:
        logger.warning(f"不支持的文件类型: {ext}")
        return False
    
    if not info:
        logger.error(f"OCR识别失败: {file_path}")
        return False
    
    logger.info(f"识别结果: 金额={info['amount']}, 日期={info['date']}, 供应商={info['supplier']}")
    
    # 2. 写入Excel
    try:
        append_to_excel(OUTPUT_EXCEL, info)
    except Exception as e:
        logger.error(f"Excel写入失败: {e}")
        return False
    
    # 3. 移动文件到已处理文件夹
    processed_path = Path(PROCESSED_DIR) / file_path.name
    shutil.move(str(file_path), str(processed_path))
    logger.info(f"文件已归档: {processed_path}")
    
    return True

def scan_and_process():
    """扫描文件夹,处理所有待处理文件"""
    input_path = Path(INPUT_DIR)
    if not input_path.exists():
        input_path.mkdir()
    
    files = list(input_path.glob("*.*"))
    if not files:
        logger.info("暂无待处理文件")
        return
    
    for file_path in files:
        if file_path.suffix.lower() in ['.jpg', '.jpeg', '.png', '.pdf']:
            process_single_file(file_path)
        else:
            logger.info(f"跳过非图片/PDF文件: {file_path.name}")

def main():
    logger.info("智能票据处理机器人启动")
    logger.info(f"监控文件夹: {INPUT_DIR}, 扫描间隔: {SCAN_INTERVAL}秒")
    
    # 确保输出目录存在
    Path("output").mkdir(exist_ok=True)
    Path(PROCESSED_DIR).mkdir(exist_ok=True)
    
    # 持续运行模式(也可改为单次执行后退出)
    try:
        while True:
            scan_and_process()
            time.sleep(SCAN_INTERVAL)
    except KeyboardInterrupt:
        logger.info("机器人已停止")

if __name__ == "__main__":
    main()

5.3 运行与测试

  1. 将若干张发票图片(或PDF)放入invoices文件夹。
  2. 运行python main.py
  3. 观察控制台日志,机器人会自动识别、填写Excel并发送邮件。
  4. 检查output/报销明细.xlsx文件,确认数据已追加。
  5. 审核人邮箱会收到包含附件的邮件。

六、健壮性增强与最佳实践

6.1 异常处理与重试机制

在实际生产中,OCR识别可能因图片质量差而失败,网络波动可能导致邮件发送失败。我们需要增加重试和降级逻辑。

OCR重试:对于识别结果置信度低的字段,可以尝试对图片进行预处理(灰度、二值化、降噪)后再次识别。

import cv2

def preprocess_image(image_path):
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    # 自适应阈值二值化
    img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
    # 去噪
    img = cv2.medianBlur(img, 3)
    return img

邮件发送重试:使用tenacity库。

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def send_excel_with_retry(*args, **kwargs):
    send_excel_by_email(*args, **kwargs)

6.2 日志与监控

  • 记录每张发票的处理时间、识别结果、成功/失败状态。
  • 可将日志输出到ELK或Splunk进行集中监控。
  • 当连续失败超过阈值时,发送告警邮件给管理员。

6.3 部署方式

  • 定时任务:使用cron(Linux)或任务计划程序(Windows)每隔10分钟运行一次python main.py(单次扫描后退出,而非无限循环)。
  • Docker容器化:方便部署在任何环境。

Dockerfile示例:

FROM python:3.9-slim
RUN apt-get update && apt-get install -y poppler-utils
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]

6.4 扩展思路

  1. 支持更多发票类型:火车票、出租车票、增值税专用发票等,只需调整正则表达式或使用更智能的字段定位(如基于坐标模板)。
  2. 接入API:对于识别难度高的发票,可调用百度OCR API作为备选。
  3. Web界面:使用Flask或FastAPI提供上传界面,用户可手动上传发票并实时查看识别结果。
  4. 多审核人轮询:根据报销金额或部门自动选择不同的审核人邮箱。

七、总结与展望

本文从零开始,完整实现了一个智能票据处理机器人,涵盖了RPA项目中的三大核心增强能力:

  • OCR识别:使用PaddleOCR高效提取发票中的金额、日期、供应商等关键信息;
  • Excel自动化:通过openpyxl动态填写报销明细表;
  • 邮件通知:利用smtplib自动发送带附件的邮件给审核人。

这个机器人能够显著提升财务报销流程的效率,将人工处理一张发票的3-5分钟缩短到10秒以内,且准确率可达90%以上(通过持续优化OCR和正则规则可进一步提升)。

以上就是使用Python从零打造智能票据处理机器人的详细内容,更多关于Python智能票据处理的资料请关注脚本之家其它相关文章!

相关文章

  • Python random模块的使用示例

    Python random模块的使用示例

    这篇文章主要介绍了Python random模块的使用示例,帮助大家更好的理解和使用python生成随机数,感兴趣的朋友可以了解下
    2020-10-10
  • python基础之内置函数

    python基础之内置函数

    这篇文章主要介绍了python内置函数,实例分析了Python中返回一个返回值与多个返回值的方法,需要的朋友可以参考下
    2021-10-10
  • Python中的三目(元)运算符详解

    Python中的三目(元)运算符详解

    这篇文章主要介绍了python 三元运算符使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-09-09
  • Python新手避坑指南之KeyError报错的解决方案

    Python新手避坑指南之KeyError报错的解决方案

    KeyError是Python中字典(dict)操作时最常见的异常之一,当尝试访问字典中不存在的键(key)时,Python会抛出这个错误,下面小编就和大家详细介绍一下具体解决方法吧
    2026-03-03
  • Python实现GUI学生管理系统的示例代码

    Python实现GUI学生管理系统的示例代码

    这篇文章主要为大家介绍了如何留Python语言实现简易的GUI学生管理系统,文中的示例代码讲解详细,对我们学习Python有一定帮助,需要的可以参考下
    2022-06-06
  • python requests库爬取豆瓣电视剧数据并保存到本地详解

    python requests库爬取豆瓣电视剧数据并保存到本地详解

    这篇文章主要介绍了python requests库爬取豆瓣电视剧数据并保存到本地详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • python实现简单的名片管理系统

    python实现简单的名片管理系统

    这篇文章主要为大家详细介绍了python实现名片管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • python urllib urlopen()对象方法/代理的补充说明

    python urllib urlopen()对象方法/代理的补充说明

    这篇文章主要介绍了python urllib urlopen()对象方法/代理的补充说明的相关资料,需要的朋友可以参考下
    2017-06-06
  • python脚本将mysql数据写入doris过程

    python脚本将mysql数据写入doris过程

    文章描述了在将MySQL数据写入Doris时遇到的权限和数据质量问题,包括使用Python脚本和Streamload方式的异常,以及通过Flink-CDC进行数据同步时遇到的字符集和主键问题,作者通过分析和调整,最终解决了数据写入问题,并对比了Doris和MySQL在性能上的差异
    2026-02-02
  • python panda库从基础到高级操作分析

    python panda库从基础到高级操作分析

    本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据优化技巧,强调其高效性、灵活性和与科学计算库的集成能力,感兴趣的朋友跟随小编一起看看吧
    2025-08-08

最新评论