使用Python從零打造智能票據(jù)處理機(jī)器人
引言:當(dāng)報(bào)銷流程遇見RPA
在企業(yè)日常運(yùn)營中,發(fā)票報(bào)銷是一個(gè)典型的高頻、低效、易出錯(cuò)場(chǎng)景。財(cái)務(wù)人員每天要面對(duì)大量紙質(zhì)或電子發(fā)票:人工錄入金額、日期、供應(yīng)商到Excel表格,再逐一發(fā)送給審核人。這個(gè)過程不僅耗時(shí),而且容易因?yàn)槭终`導(dǎo)致數(shù)據(jù)錯(cuò)誤。
那么,能否讓一個(gè)“機(jī)器人”自動(dòng)完成這些工作?答案是肯定的。本文將帶領(lǐng)讀者從零開始,打造一個(gè)智能票據(jù)處理機(jī)器人。它能夠:
- 自動(dòng)掃描指定文件夾中的發(fā)票圖片(JPG/PNG/PDF);
- 使用OCR技術(shù)提取發(fā)票的關(guān)鍵信息(金額、日期、供應(yīng)商名稱);
- 將提取的數(shù)據(jù)自動(dòng)填寫到Excel報(bào)銷單模板中;
- 最后通過郵件將填寫好的Excel文件發(fā)送給指定的審核人。
本文不僅會(huì)給出完整的代碼實(shí)現(xiàn),還會(huì)深入講解OCR選型與優(yōu)化、Excel自動(dòng)化操作、郵件發(fā)送以及異常處理與日志等RPA核心能力,幫助讀者構(gòu)建一套健壯、可維護(hù)的自動(dòng)化解決方案。
一、項(xiàng)目概述與技術(shù)選型
1.1 業(yè)務(wù)流程設(shè)計(jì)
智能票據(jù)處理機(jī)器人的工作流如下:
[監(jiān)控文件夾] → [發(fā)現(xiàn)新發(fā)票圖片] → [OCR提取關(guān)鍵字段] → [寫入Excel報(bào)銷單] → [保存Excel文件] → [發(fā)送郵件給審核人] → [歸檔或移動(dòng)原文件]
這是一個(gè)典型的事件驅(qū)動(dòng)型RPA,可以通過定時(shí)任務(wù)(如每5分鐘掃描一次)或文件系統(tǒng)監(jiān)控(watchdog)來觸發(fā)。
1.2 技術(shù)棧選擇
| 功能模塊 | 技術(shù)方案 | 理由 |
|---|---|---|
| OCR識(shí)別 | PaddleOCR(輕量級(jí)中文模型) | 中文發(fā)票識(shí)別準(zhǔn)確率高,支持印刷體,無需復(fù)雜預(yù)處理 |
| Excel操作 | openpyxl | 支持.xlsx格式,功能強(qiáng)大,可讀寫單元格、樣式、公式 |
| 郵件發(fā)送 | smtplib + email | Python標(biāo)準(zhǔn)庫,穩(wěn)定可靠,支持附件 |
| 文件監(jiān)控 | watchdog + 定時(shí)輪詢(簡(jiǎn)化版) | 生產(chǎn)環(huán)境可用watchdog,本文采用輪詢降低復(fù)雜度 |
| 配置管理 | dotenv + 環(huán)境變量 | 敏感信息不硬編碼 |
| 日志 | logging | 標(biāo)準(zhǔn)庫,便于調(diào)試和審計(jì) |
| 圖像預(yù)處理 | OpenCV (cv2) + PIL | 輔助處理傾斜、噪點(diǎn)等(如需) |
1.3 環(huán)境準(zhǔn)備
# 安裝依賴 pip install paddlepaddle paddleocr openpyxl pillow opencv-python python-dotenv # 郵件發(fā)送使用標(biāo)準(zhǔn)庫,無需額外安裝
二、OCR識(shí)別:從發(fā)票圖片中提取關(guān)鍵信息
2.1 OCR引擎對(duì)比與選擇
在RPA項(xiàng)目中,OCR的選型直接影響識(shí)別準(zhǔn)確率和開發(fā)效率。常見方案對(duì)比:
| OCR引擎 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場(chǎng)景 |
|---|---|---|---|
| Tesseract | 開源免費(fèi),支持多語言 | 中文準(zhǔn)確率一般,預(yù)處理復(fù)雜 | 英文文檔、簡(jiǎn)單驗(yàn)證碼 |
| PaddleOCR | 中文準(zhǔn)確率高,輕量模型僅8.6MB | 依賴PaddlePaddle框架 | 中文發(fā)票、身份證、表格 |
| EasyOCR | 支持80+語言,GPU加速 | 模型較大,速度較慢 | 多語言混合場(chǎng)景 |
| 百度/阿里OCR API | 準(zhǔn)確率最高(>95%),無需預(yù)處理 | 收費(fèi)、依賴網(wǎng)絡(luò)、數(shù)據(jù)隱私 | 生產(chǎn)級(jí)高要求場(chǎng)景 |
考慮到發(fā)票多為中文印刷體,且希望免費(fèi)離線運(yùn)行,本文選擇PaddleOCR。它提供了輕量級(jí)的中文檢測(cè)和識(shí)別模型,在普通CPU上也能達(dá)到實(shí)時(shí)識(shí)別速度。
2.2 使用PaddleOCR識(shí)別發(fā)票
初始化OCR引擎
from paddleocr import PaddleOCR
# 初始化OCR引擎(首次運(yùn)行會(huì)自動(dòng)下載模型)
ocr = PaddleOCR(
use_angle_cls=True, # 使用方向分類器,處理旋轉(zhuǎn)文字
lang='ch', # 中文模型
show_log=False # 關(guān)閉冗余日志
)
識(shí)別并提取關(guān)鍵字段
發(fā)票上的關(guān)鍵信息通常以“鍵值對(duì)”形式出現(xiàn),如“金額:¥100.00”、“開票日期:2025年03月15日”、“購買方名稱:XX公司”。我們可以通過正則表達(dá)式從OCR識(shí)別的文本中提取。
import re
def extract_invoice_info(ocr_result):
"""
從PaddleOCR識(shí)別結(jié)果中提取發(fā)票關(guān)鍵信息
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: # 只保留高置信度結(jié)果
full_text += text + " "
# 提取金額(支持多種寫法)
amount_pattern = r'(?:金額|合計(jì)|總計(jì))[::\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'(?:開票日期|發(fā)票日期)[::\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
# 提取供應(yīng)商(銷售方名稱)
seller_pattern = r'(?:銷售方|出售方|供應(yīng)商)[::\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調(diào)用函數(shù)
def ocr_invoice_image(image_path):
"""對(duì)單張發(fā)票圖片執(zhí)行OCR并返回結(jié)構(gòu)化信息"""
try:
result = ocr.ocr(image_path, cls=True)
if not result or not result[0]:
raise ValueError("OCR未識(shí)別到任何文本")
# result[0] 是圖片中所有文本行
info = extract_invoice_info(result[0])
return info
except Exception as e:
print(f"OCR識(shí)別失敗: {image_path}, 錯(cuò)誤: {e}")
return None
2.3 處理PDF格式的發(fā)票
實(shí)際場(chǎng)景中,供應(yīng)商可能發(fā)送PDF格式的電子發(fā)票。我們可以借助pdf2image將PDF轉(zhuǎn)換為圖片,再調(diào)用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轉(zhuǎn)換為臨時(shí)文件或直接處理
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自動(dòng)填寫:使用openpyxl操作報(bào)銷單
3.1 設(shè)計(jì)Excel報(bào)銷單模板
假設(shè)我們有一個(gè)名為報(bào)銷單模板.xlsx的文件,結(jié)構(gòu)如下:
| A列(字段) | B列(值) |
|---|---|
| 日期 | |
| 供應(yīng)商 | |
| 金額(元) | |
| 備注 |
或者更常見的列表式報(bào)銷明細(xì)表(每行一條記錄)。為簡(jiǎn)化,我們采用逐行追加的方式:每次處理一張發(fā)票,就在“報(bào)銷明細(xì)”工作表中新增一行,填寫日期、供應(yīng)商、金額。
3.2 使用openpyxl讀寫Excel
from openpyxl import load_workbook
import os
def append_to_excel(excel_path, invoice_info):
"""
將發(fā)票信息追加到Excel文件的報(bào)銷明細(xì)表中
假設(shè)工作表名為"報(bào)銷明細(xì)",表頭為:日期, 供應(yīng)商, 金額, 備注
"""
if not os.path.exists(excel_path):
# 如果文件不存在,創(chuàng)建新文件并寫入表頭
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "報(bào)銷明細(xì)"
ws.append(["日期", "供應(yīng)商", "金額", "備注"])
else:
wb = load_workbook(excel_path)
ws = wb["報(bào)銷明細(xì)"]
# 追加一行數(shù)據(jù)
row = [
invoice_info.get("date", ""),
invoice_info.get("supplier", ""),
invoice_info.get("amount", ""),
f"OCR自動(dòng)識(shí)別 {invoice_info.get('raw_text', '')[:20]}..."
]
ws.append(row)
# 保存文件
wb.save(excel_path)
print(f"已追加記錄到 {excel_path}")
進(jìn)階技巧:
- 使用
openpyxl.styles設(shè)置單元格格式(如金額保留兩位小數(shù)); - 使用公式自動(dòng)計(jì)算合計(jì)金額;
- 保護(hù)工作表防止誤修改。
四、郵件發(fā)送:將報(bào)銷單發(fā)給審核人
4.1 使用smtplib發(fā)送帶附件的郵件
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="報(bào)銷單待審核", body="請(qǐng)查收本周報(bào)銷明細(xì)"):
"""
發(fā)送Excel文件作為附件到指定郵箱
"""
# 郵箱配置(從環(huán)境變量讀取)
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") # 授權(quán)碼
if not sender_email or not sender_password:
raise ValueError("請(qǐng)?jiān)?env文件中配置發(fā)件郵箱和密碼")
# 構(gòu)建郵件
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)
# 發(fā)送
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"郵件已發(fā)送至 {receiver_email}")
4.2 郵件配置管理(.env文件)
創(chuàng)建.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()
五、整合實(shí)戰(zhàn):完整的票據(jù)處理機(jī)器人
5.1 項(xiàng)目結(jié)構(gòu)
invoice_robot/
├── main.py # 主程序入口
├── ocr_engine.py # OCR識(shí)別模塊
├── excel_handler.py # Excel操作模塊
├── mail_sender.py # 郵件發(fā)送模塊
├── config.py # 配置加載
├── invoices/ # 待處理的發(fā)票圖片文件夾
├── processed/ # 已處理的備份文件夾
├── output/ # 生成的Excel文件存放路徑
├── .env # 環(huán)境變量
└── requirements.txt
5.2 主程序?qū)崿F(xiàn)(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/報(bào)銷明細(xì).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):
"""處理單個(gè)發(fā)票文件"""
logger.info(f"開始處理文件: {file_path}")
ext = file_path.suffix.lower()
# 1. OCR識(shí)別
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識(shí)別失敗: {file_path}")
return False
logger.info(f"識(shí)別結(jié)果: 金額={info['amount']}, 日期={info['date']}, 供應(yīng)商={info['supplier']}")
# 2. 寫入Excel
try:
append_to_excel(OUTPUT_EXCEL, info)
except Exception as e:
logger.error(f"Excel寫入失敗: {e}")
return False
# 3. 移動(dòng)文件到已處理文件夾
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("智能票據(jù)處理機(jī)器人啟動(dòng)")
logger.info(f"監(jiān)控文件夾: {INPUT_DIR}, 掃描間隔: {SCAN_INTERVAL}秒")
# 確保輸出目錄存在
Path("output").mkdir(exist_ok=True)
Path(PROCESSED_DIR).mkdir(exist_ok=True)
# 持續(xù)運(yùn)行模式(也可改為單次執(zhí)行后退出)
try:
while True:
scan_and_process()
time.sleep(SCAN_INTERVAL)
except KeyboardInterrupt:
logger.info("機(jī)器人已停止")
if __name__ == "__main__":
main()
5.3 運(yùn)行與測(cè)試
- 將若干張發(fā)票圖片(或PDF)放入
invoices文件夾。 - 運(yùn)行
python main.py。 - 觀察控制臺(tái)日志,機(jī)器人會(huì)自動(dòng)識(shí)別、填寫Excel并發(fā)送郵件。
- 檢查
output/報(bào)銷明細(xì).xlsx文件,確認(rèn)數(shù)據(jù)已追加。 - 審核人郵箱會(huì)收到包含附件的郵件。
六、健壯性增強(qiáng)與最佳實(shí)踐
6.1 異常處理與重試機(jī)制
在實(shí)際生產(chǎn)中,OCR識(shí)別可能因圖片質(zhì)量差而失敗,網(wǎng)絡(luò)波動(dòng)可能導(dǎo)致郵件發(fā)送失敗。我們需要增加重試和降級(jí)邏輯。
OCR重試:對(duì)于識(shí)別結(jié)果置信度低的字段,可以嘗試對(duì)圖片進(jìn)行預(yù)處理(灰度、二值化、降噪)后再次識(shí)別。
import cv2
def preprocess_image(image_path):
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 自適應(yīng)閾值二值化
img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
# 去噪
img = cv2.medianBlur(img, 3)
return img
郵件發(fā)送重試:使用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 日志與監(jiān)控
- 記錄每張發(fā)票的處理時(shí)間、識(shí)別結(jié)果、成功/失敗狀態(tài)。
- 可將日志輸出到ELK或Splunk進(jìn)行集中監(jiān)控。
- 當(dāng)連續(xù)失敗超過閾值時(shí),發(fā)送告警郵件給管理員。
6.3 部署方式
- 定時(shí)任務(wù):使用cron(Linux)或任務(wù)計(jì)劃程序(Windows)每隔10分鐘運(yùn)行一次
python main.py(單次掃描后退出,而非無限循環(huán))。 - Docker容器化:方便部署在任何環(huán)境。
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 擴(kuò)展思路
- 支持更多發(fā)票類型:火車票、出租車票、增值稅專用發(fā)票等,只需調(diào)整正則表達(dá)式或使用更智能的字段定位(如基于坐標(biāo)模板)。
- 接入API:對(duì)于識(shí)別難度高的發(fā)票,可調(diào)用百度OCR API作為備選。
- Web界面:使用Flask或FastAPI提供上傳界面,用戶可手動(dòng)上傳發(fā)票并實(shí)時(shí)查看識(shí)別結(jié)果。
- 多審核人輪詢:根據(jù)報(bào)銷金額或部門自動(dòng)選擇不同的審核人郵箱。
七、總結(jié)與展望
本文從零開始,完整實(shí)現(xiàn)了一個(gè)智能票據(jù)處理機(jī)器人,涵蓋了RPA項(xiàng)目中的三大核心增強(qiáng)能力:
- OCR識(shí)別:使用PaddleOCR高效提取發(fā)票中的金額、日期、供應(yīng)商等關(guān)鍵信息;
- Excel自動(dòng)化:通過openpyxl動(dòng)態(tài)填寫報(bào)銷明細(xì)表;
- 郵件通知:利用smtplib自動(dòng)發(fā)送帶附件的郵件給審核人。
這個(gè)機(jī)器人能夠顯著提升財(cái)務(wù)報(bào)銷流程的效率,將人工處理一張發(fā)票的3-5分鐘縮短到10秒以內(nèi),且準(zhǔn)確率可達(dá)90%以上(通過持續(xù)優(yōu)化OCR和正則規(guī)則可進(jìn)一步提升)。
以上就是使用Python從零打造智能票據(jù)處理機(jī)器人的詳細(xì)內(nèi)容,更多關(guān)于Python智能票據(jù)處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- python+tkinter智能實(shí)現(xiàn)票據(jù)生成
- 使用Python搭建屬于智能問答機(jī)器人的實(shí)戰(zhàn)指南
- Python代碼實(shí)現(xiàn)輕松編寫一個(gè)QQ AI機(jī)器人
- 手把手教你Python如何調(diào)用API做個(gè)聊天機(jī)器人
- 基于Python和Telegram API構(gòu)建一個(gè)消息機(jī)器人
- 基于Python開發(fā)簡(jiǎn)易版QQ聊天機(jī)器人
- Python基于Wechaty構(gòu)建一個(gè)簡(jiǎn)單的微信機(jī)器人
- 基于Python編寫一個(gè)簡(jiǎn)易聊天機(jī)器人詳解
- 利用Python編寫一個(gè)簡(jiǎn)單的聊天機(jī)器人
相關(guān)文章
python 自動(dòng)重連wifi windows的方法
今天小編就為大家分享一篇python 自動(dòng)重連wifi windows的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-12-12
Python實(shí)現(xiàn)HTML轉(zhuǎn)Word的示例代碼
這篇文章主要為大家詳細(xì)介紹了使用Python實(shí)現(xiàn)HTML轉(zhuǎn)Word的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12
Python新手學(xué)習(xí)函數(shù)默認(rèn)參數(shù)設(shè)置
在本篇文章里小編給大家分享的是關(guān)于Python新手學(xué)習(xí)函數(shù)默認(rèn)參數(shù)設(shè)置的相關(guān)知識(shí)點(diǎn),需要的朋友們可以參考下。2020-06-06
Windows自動(dòng)化重啟python腳本的方法詳解
小編最近遇到一個(gè)需求,每天都需要定時(shí)重啟一個(gè)python腳本,這個(gè)腳本是跑在windows電腦上的,本文小編整理了三個(gè)實(shí)用的方法,大家可以參考一下2025-04-04
Python如何實(shí)現(xiàn)PDF隱私信息檢測(cè)
隨著越來越多的個(gè)人信息以電子形式存儲(chǔ)和傳輸,確保這些信息的安全至關(guān)重要,本文將介紹如何使用Python檢測(cè)PDF文件中的隱私信息,需要的可以參考下2025-02-02
從基礎(chǔ)到高級(jí)詳解Python臨時(shí)文件與目錄創(chuàng)建完全指南
在軟件開發(fā)中,臨時(shí)文件和目錄扮演著至關(guān)重要的角色,本文將深入探討Python中創(chuàng)建和管理臨時(shí)文件與目錄的各種方法,有需要的小伙伴可以了解下2025-09-09

