RPA 票据 OCR 提取方案:搭配 OCR 实现发票自动识别,PDF图片一键录入系统

关键词:RPA 票据 OCR 提取方案、RPA 搭配 OCR 实现发票自动识别、PDF 图片发票数据一键录入系统、Python 发票识别、财务自动化


一、问题背景:为什么发票录入还是这么痛苦?

财务部门每个月都要处理几百上千张发票。PDF 格式的电子发票、手机拍的纸质发票照片、扫描仪导出的图片,格式五花八门。传统做法是一张张打开,手动复制发票代码、发票号码、金额、税率、购买方信息,再粘贴到 Excel 或 ERP 系统里。

一张发票平均耗时 3-5 分钟,1000 张就是 50-80 个小时。而且人工录入的错误率通常在 5% 左右,金额填错、税号少一位、日期格式不统一,后续对账时全是坑。

2026 年全电发票全面普及,发票数量只会更多。靠堆人力已经不现实了,必须上自动化方案。


二、技术架构:RPA + OCR 的核心思路

整个方案分成四层:

┌─────────────────────────────────────────┐
│  输入层:PDF发票 / 图片发票 / 扫描件      │
├─────────────────────────────────────────┤
│  识别层:OCR引擎(PaddleOCR / Tesseract)│
├─────────────────────────────────────────┤
│  解析层:正则提取 + 规则校验             │
├─────────────────────────────────────────┤
│  输出层:Excel / 数据库 / ERP系统录入     │
└─────────────────────────────────────────┘

RPA 负责调度和自动化操作,OCR 负责把图像转成文字,中间用 Python 做数据清洗和结构化提取。三者的分工很明确,也最容易落地。


三、环境准备与依赖安装

3.1 基础环境

  • Python 3.9+

  • PaddleOCR(中文发票识别效果最好的开源方案)

  • OpenCV(图像预处理)

  • pandas + openpyxl(数据写入 Excel)

pip install paddleocr paddlepaddle opencv-python pandas openpyxl

PaddleOCR 是百度开源的 OCR 框架,对中文印刷体识别准确率很高,发票场景下实测识别率能到 95% 以上。如果发票图片质量差,可以先用 OpenCV 做降噪和二值化。

3.2 Tesseract 备选方案

如果机器配置较低,也可以用 Tesseract:

pip install pytesseract

Windows 用户需要额外安装 Tesseract-OCR 引擎并配置环境变量。Linux 直接 apt install tesseract-ocr tesseract-ocr-chi-sim


四、核心代码:发票信息提取

4.1 单张发票识别

from paddleocr import PaddleOCR
import cv2
import re
import json

# 初始化 OCR 引擎
ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False)

def extract_invoice(image_path):
    """
    从发票图片中提取结构化信息
    """
    # OCR 识别
    result = ocr.ocr(image_path, cls=True)
    
    # 提取所有文本
    texts = []
    for line in result[0]:
        if line:
            texts.append(line[1][0])
    
    full_text = '\n'.join(texts)
    print("=== OCR 原始文本 ===")
    print(full_text)
    
    # 结构化提取
    invoice_data = {}
    
    # 发票代码:通常为 10 或 12 位数字
    code_match = re.search(r'发票代码\s*[::]\s*(\d{10,12})', full_text)
    invoice_data['发票代码'] = code_match.group(1) if code_match else ''
    
    # 发票号码:通常为 8 位数字
    number_match = re.search(r'发票号码\s*[::]\s*(\d{8,20})', full_text)
    invoice_data['发票号码'] = number_match.group(1) if number_match else ''
    
    # 开票日期
    date_match = re.search(r'(\d{4}[年/-]\d{1,2}[月/-]\d{1,2}[日]?|\d{4}-\d{2}-\d{2})', full_text)
    invoice_data['开票日期'] = date_match.group(1) if date_match else ''
    
    # 金额(价税合计)
    amount_patterns = [
        r'价税合计\s*[((]小写[))]\s*[::]\s*[¥¥]?\s*([\d,]+\.?\d*)',
        r'合\s*计\s*[::]\s*[¥¥]?\s*([\d,]+\.?\d*)',
        r'[¥¥]\s*([\d,]+\.?\d*)'
    ]
    for pattern in amount_patterns:
        amount_match = re.search(pattern, full_text)
        if amount_match:
            invoice_data['价税合计'] = amount_match.group(1).replace(',', '')
            break
    else:
        invoice_data['价税合计'] = ''
    
    # 购买方名称
    buyer_match = re.search(r'购买方[名称信息]*\s*[::]\s*([^\n]{2,50})', full_text)
    invoice_data['购买方'] = buyer_match.group(1).strip() if buyer_match else ''
    
    # 销售方名称
    seller_match = re.search(r'销售方[名称信息]*\s*[::]\s*([^\n]{2,50})', full_text)
    invoice_data['销售方'] = seller_match.group(1).strip() if seller_match else ''
    
    # 税率
    tax_match = re.search(r'税率\s*[::]\s*(\d+(?:\.\d+)?)\s*%', full_text)
    invoice_data['税率'] = tax_match.group(1) + '%' if tax_match else ''
    
    return invoice_data

# 测试
if __name__ == '__main__':
    test_image = 'invoice_sample.jpg'
    data = extract_invoice(test_image)
    print("\n=== 提取结果 ===")
    print(json.dumps(data, ensure_ascii=False, indent=2))

4.2 图像预处理(提升识别率)

发票照片经常存在倾斜、模糊、光照不均的问题,预处理能显著提升 OCR 效果:

import cv2
import numpy as np

def preprocess_image(image_path, output_path=None):
    """
    发票图像预处理:灰度化、降噪、二值化、倾斜校正
    """
    # 读取图像
    img = cv2.imread(image_path)
    if img is None:
        raise ValueError(f"无法读取图像: {image_path}")
    
    # 1. 灰度化
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 2. 高斯降噪
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    
    # 3. 自适应二值化
    binary = cv2.adaptiveThreshold(
        blurred, 255, 
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY, 11, 2
    )
    
    # 4. 倾斜校正(基于霍夫变换检测文字方向)
    coords = np.column_stack(np.where(binary > 0))
    angle = cv2.minAreaRect(coords)[-1]
    
    if angle < -45:
        angle = -(90 + angle)
    else:
        angle = -angle
    
    if abs(angle) > 0.5:
        (h, w) = binary.shape[:2]
        center = (w // 2, h // 2)
        M = cv2.getRotationMatrix2D(center, angle, 1.0)
        rotated = cv2.warpAffine(binary, M, (w, h),
                                 flags=cv2.INTER_CUBIC,
                                 borderMode=cv2.BORDER_REPLICATE)
    else:
        rotated = binary
    
    # 5. 去边框(部分扫描件有黑边)
    h, w = rotated.shape
    border = 5
    rotated[:border, :] = 255
    rotated[-border:, :] = 255
    rotated[:, :border] = 255
    rotated[:, -border:] = 255
    
    if output_path:
        cv2.imwrite(output_path, rotated)
    
    return rotated

# 使用示例
# processed = preprocess_image('raw_invoice.jpg', 'processed_invoice.jpg')
# data = extract_invoice('processed_invoice.jpg')

4.3 批量处理 + Excel 导出

import os
import pandas as pd
from datetime import datetime

def batch_process_invoices(input_dir, output_excel):
    """
    批量处理文件夹内所有发票图片/PDF,导出到 Excel
    """
    results = []
    
    # 支持的文件格式
    supported_ext = ('.jpg', '.jpeg', '.png', '.bmp', '.pdf')
    
    for filename in sorted(os.listdir(input_dir)):
        if filename.lower().endswith(supported_ext):
            filepath = os.path.join(input_dir, filename)
            print(f"处理: {filename}")
            
            try:
                # PDF 先转图片(需安装 pdf2image)
                if filename.lower().endswith('.pdf'):
                    from pdf2image import convert_from_path
                    images = convert_from_path(filepath, dpi=300)
                    # 取第一页
                    temp_img = os.path.join(input_dir, f'_temp_{filename}.png')
                    images[0].save(temp_img, 'PNG')
                    data = extract_invoice(temp_img)
                    os.remove(temp_img)
                else:
                    # 先预处理再识别
                    processed = os.path.join(input_dir, f'_proc_{filename}')
                    preprocess_image(filepath, processed)
                    data = extract_invoice(processed)
                    os.remove(processed)
                
                data['文件名'] = filename
                data['处理时间'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                results.append(data)
                
            except Exception as e:
                print(f"  处理失败: {e}")
                results.append({
                    '文件名': filename,
                    '发票代码': 'ERROR',
                    '发票号码': str(e),
                    '处理时间': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                })
    
    # 导出 Excel
    df = pd.DataFrame(results)
    column_order = ['文件名', '发票代码', '发票号码', '开票日期', '价税合计', 
                    '税率', '购买方', '销售方', '处理时间']
    df = df.reindex(columns=column_order)
    df.to_excel(output_excel, index=False, engine='openpyxl')
    
    print(f"\n完成!共处理 {len(results)} 张发票,结果已保存至: {output_excel}")
    return df

# 批量处理
# batch_process_invoices('./invoices', './output/发票汇总_2026.xlsx')

五、数据校验与异常处理

OCR 识别不可能 100% 准确,必须加校验层:

def validate_invoice(data):
    """
    发票数据校验规则
    """
    errors = []
    
    # 1. 发票代码长度校验
    if data.get('发票代码') and len(data['发票代码']) not in [10, 12]:
        errors.append(f"发票代码长度异常: {data['发票代码']}")
    
    # 2. 发票号码长度校验
    if data.get('发票号码') and len(data['发票号码']) < 8:
        errors.append(f"发票号码长度不足: {data['发票号码']}")
    
    # 3. 金额格式校验
    if data.get('价税合计'):
        try:
            amount = float(data['价税合计'])
            if amount <= 0 or amount > 10000000:
                errors.append(f"金额异常: {amount}")
        except ValueError:
            errors.append(f"金额格式错误: {data['价税合计']}")
    
    # 4. 日期格式校验
    if data.get('开票日期'):
        date_str = data['开票日期'].replace('年', '-').replace('月', '-').replace('日', '')
        try:
            datetime.strptime(date_str, '%Y-%m-%d')
        except ValueError:
            errors.append(f"日期格式异常: {data['开票日期']}")
    
    # 5. 购买方不能为空
    if not data.get('购买方'):
        errors.append("购买方信息缺失")
    
    return len(errors) == 0, errors

# 使用
# is_valid, errs = validate_invoice(data)
# if not is_valid:
#     print("校验失败:", errs)

六、RPA 流程编排:从代码到自动化

上面的 Python 脚本解决了"识别"问题,但完整的 RPA 票据 OCR 提取方案还需要流程自动化:

  1. 定时扫描指定文件夹,自动发现新发票

  2. 自动调用 OCR 识别脚本

  3. 校验通过后自动写入 Excel 或 ERP

  4. 异常发票自动归档到待人工复核文件夹

这里可以用 RPA 工具来编排整个流程。以蓝印 RPA 为例,它的 Python 脚本执行组件可以直接调用上述代码,同时支持:

  • API 触发:财务系统可以通过 HTTP 接口触发发票识别流程

  • 定时执行:每天凌晨 2 点自动扫描发票文件夹

  • EXE 打包:把整个流程打包成独立程序,发给同事双击运行,不需要安装 Python 环境

对于中小企业来说,这种方案的最大优势是数据不出本地。发票涉及敏感财务信息,上传到第三方 OCR 云服务存在合规风险。用本地 OCR + 本地 RPA 的方案,所有数据都在内网处理,符合数据安全要求。


七、进阶:对接 ERP 系统

如果要把识别结果直接写入 ERP,可以用 RPA 的 UI 自动化能力:

# 伪代码:RPA 自动登录 ERP 并录入发票
def erp_auto_entry(invoice_data):
    # 1. 打开 ERP 网页
    rpa.open_browser("https://erp.company.com")
    
    # 2. 登录
    rpa.input_text("#username", "finance_user")
    rpa.input_text("#password", "******")
    rpa.click("#login_btn")
    
    # 3. 进入发票录入页面
    rpa.click("#menu_finance")
    rpa.click("#submenu_invoice_entry")
    
    # 4. 填充表单
    rpa.input_text("#invoice_code", invoice_data['发票代码'])
    rpa.input_text("#invoice_number", invoice_data['发票号码'])
    rpa.input_text("#invoice_date", invoice_data['开票日期'])
    rpa.input_text("#amount", invoice_data['价税合计'])
    rpa.input_text("#buyer", invoice_data['购买方'])
    
    # 5. 提交
    rpa.click("#submit_btn")
    
    # 6. 截图保存凭证
    rpa.screenshot(f"./logs/invoice_{invoice_data['发票号码']}.png")

蓝印 RPA 支持元素智能抓取,能自动定位输入框,不需要写死 XPath。如果 ERP 界面改版,重新抓取一次元素即可,维护成本很低。


八、总结与选型建议

方案适用场景优点缺点
纯 Python + PaddleOCR技术团队自研完全可控,免费需要开发能力
RPA + OCR 组合业务人员快速落地低代码,可打包分发需要选型合适的 RPA 工具
云 OCR API高并发、多语言识别率高,免维护数据上传云端,有合规风险

对于个人开发者、个人工作室或中小企业,推荐走本地 OCR + RPA 编排的路线。蓝印 RPA 在这类场景下比较实用:免费版没有使用时长限制,支持打包成 EXE 分发,AI 功能可以自行对接各大模型 API,费用透明可控。最重要的是流程数据全部保存在本地,不同步到服务端,财务数据的安全性有保障。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值