RPA进阶实战:从零打造智能票据处理机器人——OCR识别、Excel自动填报与邮件通知全流程

引言:当报销流程遇见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 + email Python标准库,稳定可靠,支持附件
文件监控 watchdog + 定时轮询(简化版) 生产环境可用watchdog,本文采用轮询降低复杂度
配置管理 dotenv + 环境变量 敏感信息不硬编码
日志 logging 标准库,便于调试和审计
图像预处理 OpenCV (cv2) + PIL 辅助处理倾斜、噪点等(如需)

1.3 环境准备

bash 复制代码
# 安装依赖
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识别发票

2.2.1 初始化OCR引擎
python 复制代码
from paddleocr import PaddleOCR

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

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

python 复制代码
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
    }
2.2.3 完整的OCR调用函数
python 复制代码
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。

bash 复制代码
pip install pdf2image
# 还需要安装poppler(Windows需下载,Linux apt install poppler-utils)
python 复制代码
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

python 复制代码
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发送带附件的邮件

python 复制代码
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文件:

ini 复制代码
SMTP_SERVER=smtp.qq.com
SMTP_PORT=465
SENDER_EMAIL=rpa@company.com
SENDER_PASSWORD=your_authorization_code
AUDITOR_EMAIL=auditor@company.com

在代码中加载:

python 复制代码
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

python 复制代码
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重试:对于识别结果置信度低的字段,可以尝试对图片进行预处理(灰度、二值化、降噪)后再次识别。

python 复制代码
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库。

python 复制代码
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示例:

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和正则规则可进一步提升)。

相关推荐
QYR_Jodie2 小时前
从智能制造升级与机器人普及驱动到高增扩容:全球机器人关节电磁制动器2025年2.12亿,2032年达4.30亿,2026-2032年CAGR11.1%
机器人·制造·市场报告
xiaoduo AI2 小时前
客服机器人自定义报表支持定时发送吗?智能 Agent + 邮件推送,能否自动生成运营日报?
大数据·人工智能·机器人
kyle~3 小时前
导航---LIO(激光雷达-惯性里程计)算法
c++·算法·机器人·ros2·导航
才兄说4 小时前
机器人二次开发机器狗巡检?低电量自动返充
人工智能·机器人
叫我黎大侠4 小时前
.NET 实战:调用千问视觉模型实现 OCR(车票识别完整教程)
阿里云·ai·c#·ocr·asp.net·.net·.netcore
石榴树下的七彩鱼4 小时前
OCR 识别接口哪个好?2026 年主流 OCR API 对比评测(附免费在线体验)
图像处理·人工智能·后端·计算机视觉·ocr·api·文字识别
AI人工智能+4 小时前
表格识别技术通过深度学习与计算机视觉,实现复杂表格的自动化解析与结构化输出
深度学习·计算机视觉·ocr·表格识别
Deepoch4 小时前
边缘语义智能:Deepoc开发板提升工业巡检机器人自主作业水平
人工智能·科技·机器人·具身模型·deepoc
永霖光电_UVLED4 小时前
EPC发布用于机器人和轻型电动车的5kW氮化镓三相逆变器
机器人