从手动整理300份合同说起
去年底接手一个烂尾项目:把公司近三年的采购合同全部电子化归档。300多份PDF扫描件,每份少则5页多则30页,全部混在一起,没有目录,没有分类,文件名就是日期加一串编号。
我的第一反应是找实习生——但想想时薪和出错率,最后还是决定自己动手。先试了OCR在线识别平台,识别准确率感人,大量表格数据对不齐。花了三天识别完之后,更崩溃的事情来了:要把识别出来的文本归类到"合同主体"、"付款条款"、"验收条款"等十几类中,纯手工复制粘贴,又花了五天。
那段时间每天下班都腰酸背痛。后来痛定思痛,用Python+PaddleOCR搭了一套自动化分类流水线,类似的活现在半天就能搞定。这篇文章就复盘整个方案,包含踩过的坑和最终落地的完整代码。
为什么选PaddleOCR而不是Tesseract
选型阶段对比了三个主流方案:Tesseract、EasyOCR和PaddleOCR。Tesseract英文识别不错,但中文表格简直是灾难;EasyOCR上手快,准确率对中文场景不够理想;最终选了PaddleOCR,原因很实际:
- 中文场景优化:PaddleOCR是百度开源的,中文识别准确率在我测试的三个方案里最高,尤其是中文合同里常见的宋体、仿宋体
- 表格识别内置支持:原生支持表格结构还原,不用自己写后处理
- 部署方便:pip install paddleocr一条命令搞定,不用配环境变量
硬件要求方面,CPU可以跑,但速度感人——我实测识别一份20页的合同PDF,CPU需要3-5分钟,GPU(RTX 3060)只需要15-20秒。有批量处理需求的话,建议配个GPU。
完整方案架构
整个流水线分四步走:
PDF/图片 → OCR识别 → 关键词分类 → 归档输出
↓ ↓ ↓ ↓
读取文件 PaddleOCR 规则引擎 按分类存放
↓ ↓ ↓ ↓
转图片 结构化文本 置信度判断 自动命名
核心逻辑是:OCR识别后,对文本内容做关键词匹配和置信度打分,根据分值决定分类。分类结果写入对应目录,文件名自动按"日期_合同编号_分类"格式重命名。
环境搭建:三行命令跑起来
pip install paddleocr paddlepaddle # CPU版本
# pip install paddlepaddle-gpu # GPU版本(需要NVIDIA驱动)
# 国内镜像加速安装
pip install paddleocr -i https://mirror.baidu.com/pypi/simple
GPU版本安装有个坑要注意:如果同时装了CPU和GPU版本,运行时可能会随机选版本,导致速度不稳定。建议先pip uninstall paddlepaddle,再装GPU版:
pip uninstall paddlepaddle paddlepaddle-gpu -y
pip install paddlepaddle-gpu -i https://mirror.baidu.com/pypi/simple
安装完成后验证一下GPU是否正常调用:
import paddle
print(paddle.device.cuda.device_count()) # 返回GPU数量
OCR识别模块:核心代码
我封装了一个FileOCR类,统一处理PDF和图片:
import os
from paddleocr import PaddleOCR
from pdf2image import convert_from_path
import numpy as np
from PIL import Image
class FileOCR:
def __init__(self, use_gpu=True):
self.ocr = PaddleOCR(
use_angle_cls=True,
lang='ch',
use_gpu=use_gpu,
show_log=False,
rec_algorithm='CRNN' # 轻量算法,速度优先
)
def extract_text_from_image(self, img_path):
"""从单张图片提取文本"""
result = self.ocr.ocr(img_path, cls=True)
texts = []
for line in result[0] if result and result[0] else []:
text = line[1][0]
confidence = line[1][1]
texts.append({"text": text, "conf": confidence})
return texts
def extract_text_from_pdf(self, pdf_path, dpi=200):
"""从PDF提取文本(每页转图片后OCR)"""
images = convert_from_path(pdf_path, dpi=dpi)
all_texts = []
for i, image in enumerate(images):
img_array = np.array(image)
result = self.ocr.ocr(img_array, cls=True)
page_texts = []
for line in result[0] if result and result[0] else []:
text = line[1][0]
confidence = line[1][1]
page_texts.append({"text": text, "conf": confidence})
all_texts.append({"page": i+1, "content": page_texts})
return all_texts
这里有个关键参数dpi:设置越高识别越准确,但速度越慢。扫描件质量好的设150就够,模糊的老档案建议设300。实测同样一份合同,150dpi和300dpi的识别时间差3倍,准确率差距不到2%。
分类引擎:规则+置信度双保险
分类逻辑我用的是关键词权重打分:先定义每个分类的关键词库,然后对OCR识别出来的文本做匹配计数,最后按权重加总得出分类结论。
class DocumentClassifier:
def __init__(self):
# 定义分类关键词库(带权重)
self.categories = {
"合同主体": {
"keywords": ["甲方","乙方","合同编号","签订日期","合同金额","合同期限"],
"weight": 1.5
},
"付款条款": {
"keywords": ["付款方式","付款期限","预付款","尾款","月结","对公转账","银行账号"],
"weight": 1.2
},
"验收条款": {
"keywords": ["验收标准","验收方式","验收期限","交付标准","验收报告"],
"weight": 1.2
},
"保密协议": {
"keywords": ["保密义务","保密期限","违约金","商业秘密","保密协议"],
"weight": 1.0
},
"通用文件": {
"keywords": [],
"weight": 0.5
}
}
def classify(self, ocr_result):
"""对OCR结果进行分类"""
# 合并所有页面的文本
all_text = " ".join([
item["text"]
for page in ocr_result
for item in page["content"]
])
scores = {}
for cat, config in self.categories.items():
if cat == "通用文件":
continue
score = 0
for kw in config["keywords"]:
count = all_text.count(kw)
if count > 0:
score += count * config["weight"]
scores[cat] = score
if not scores or max(scores.values()) == 0:
return "通用文件", 0.0
best_cat = max(scores, key=scores.get)
confidence = min(scores[best_cat] / 10, 1.0) # 归一化到0-1
return best_cat, confidence
这个方案有个局限性:纯关键词匹配对表述多样的文本效果一般。比如"甲乙双方协商确定"这段话,表面是合同主体,但实际可能是其他条款的一部分。后来我在关键词库里加了一个"置信度阈值"——低于0.3的不直接分类,而是标记为"待审核",手动处理。
批量处理主流程
def batch_process(input_dir, output_base):
ocr = FileOCR(use_gpu=True)
classifier = DocumentClassifier()
files = [f for f in os.listdir(input_dir)
if f.lower().endswith(('.pdf', '.jpg', '.png'))]
results = []
for i, file in enumerate(files):
print(f"[{i+1}/{len(files)}] 处理中: {file}")
filepath = os.path.join(input_dir, file)
if filepath.endswith('.pdf'):
text_data = ocr.extract_text_from_pdf(filepath)
else:
text_data = [{"page": 1, "content": ocr.extract_text_from_image(filepath)}]
category, conf = classifier.classify(text_data)
# 提取日期和编号(简单正则)
import re
date_match = re.search(r'd{4}[-/]d{2}[-/]d{2}', file)
date_str = date_match.group().replace('-', '') if date_match else 'nodate'
new_filename = f"{date_str}_{os.path.splitext(file)[0]}_{category}.pdf"
# 按分类创建目录并保存
cat_dir = os.path.join(output_base, category)
os.makedirs(cat_dir, exist_ok=True)
shutil.copy(filepath, os.path.join(cat_dir, new_filename))
results.append({
"file": file,
"category": category,
"confidence": round(conf, 2)
})
print(f" → 分类: {category} (置信度: {conf:.0%})")
return results
实战效果与坑点总结
用这套方案处理前面的300份合同:
| 指标 | 手动处理 | 自动化方案 |
|---|---|---|
| 总耗时 | 8天(约64小时) | 6小时 |
| 准确率 | 约95%(取决于疲劳程度) | 约92%(需人工复核低置信度) |
| 一致性 | 前后标准不一 | 统一分类标准 |
| 人力成本 | 约2000元(实习生) | 一次性开发成本 |
准确率略低于纯手工的原因主要是:部分扫描件字迹模糊,或者合同格式不规范(不同公司模板差异很大)。不过可以通过扩充关键词库来提升。
进阶优化方向
当前方案还有优化空间,列几个我验证过有效的:
- 接大模型做语义分类:OCR后接DeepSeek API做分类判断,准确率能到97%以上,但成本高,适合高价值文档
- 训练专用分类模型:如果文档格式固定,可以收集一批标注数据微调分类器,长期收益大
- 多语言支持:PaddleOCR支持多语言,但需要单独下载模型包,中英双语合同建议切换lang参数
- 增量处理:加一个已处理文件记录表,定期扫描新文件自动处理,适合持续归档场景
写在最后
这套方案不是银弹,它的价值在于把"需要专注力"的重复工作变成"可以喝着咖啡等它跑完"的自动化任务。OCR+规则分类的组合适合标准化程度较高的文档,如果是完全非标准的文件(比如手写信件、涂改过的表格),还是得靠人工。
但至少,它让我从那300份合同里解放了出来——现在那些时间可以用来写代码、写方案、写这篇复盘文章。技术终归是为人服务的,解放时间才是硬道理。
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论