3. 文本处理基础:字符串操作、正则表达式入门、文件读写(TXT/PDF/DOCX)
在构建采购合同版本比对系统之前,必须掌握文本处理的三项核心能力:字符串操作(清洗与分割)、正则表达式(模式匹配与提取)以及多格式文件读写(TXT/PDF/DOCX)。本章将围绕这三部分展开,为后续的差异检测算法奠定数据预处理基础。
3.1 字符串操作:清洗与结构化
合同文本通常包含多余空格、换行符、制表符以及不一致的标点符号。Python 内置的字符串方法足以完成大部分清洗工作。
| 方法 | 作用 | 示例 |
|---|---|---|
strip() |
去除首尾空白字符 | " 合同条款 ".strip() → "合同条款" |
split() |
按分隔符拆分为列表 | "第1条;第2条".split(";") → ["第1条","第2条"] |
replace() |
替换指定子串 | "甲方:".replace(":",":") → "甲方:" |
join() |
将列表合并为字符串 | " ".join(["条款A","条款B"]) → "条款A 条款B" |
实战要点: 合同比对前,统一将全角字符转为半角(如全角逗号“,”→半角“,”),避免因编码差异导致误判。
# 全角转半角示例
def full_to_half(text):
result = []
for char in text:
code = ord(char)
if 0xFF01 <= code <= 0xFF5E:
result.append(chr(code - 0xFEE0))
elif code == 0x3000:
result.append(' ')
else:
result.append(char)
return ''.join(result)
sample = "采购合同(2024版)"
print(full_to_half(sample)) # 输出: 采购合同(2024版)
3.2 正则表达式入门:模式匹配与提取
正则表达式是合同文本中提取编号、金额、日期、条款号等结构化信息的最佳工具。Python 的 re 模块提供了完整的支持。
3.2.1 常用元字符与量词
| 元字符 | 含义 | 匹配示例 |
|---|---|---|
\d |
数字 | \d{4} 匹配 4 位数字 |
\w |
字母、数字、下划线 | \w+ 匹配一个单词 |
. |
除换行符外的任意字符 | 第.条 匹配“第1条”“第A条” |
* |
前一个字符出现 0 次或多次 | ab*c 匹配 ac、abc、abbc |
+ |
前一个字符出现 1 次或多次 | ab+c 匹配 abc、abbc |
? |
前一个字符出现 0 次或 1 次 | colou?r 匹配 color、colour |
3.2.2 核心函数
re.findall(pattern, text):返回所有匹配的子串列表。re.search(pattern, text):返回第一个匹配对象,可用.group()提取。re.sub(pattern, repl, text):替换匹配到的内容。
import re
text = "合同总金额:¥1,250,000.00,签订日期:2024-03-15"
# 提取金额(含千分位和小数)
amount_pattern = r'¥[\d,]+\.\d{2}'
amounts = re.findall(amount_pattern, text)
print(amounts) # ['¥1,250,000.00']
# 提取日期
date_pattern = r'\d{4}-\d{2}-\d{2}'
dates = re.findall(date_pattern, text)
print(dates) # ['2024-03-15']
提示: 在正则前加
r(原始字符串)可以避免反斜杠转义问题,例如 r'\d+' 比 '\\d+' 更清晰。
3.3 文件读写:TXT / PDF / DOCX
采购合同通常以 PDF 或 DOCX 格式交付,TXT 则常用于中间处理。Python 生态提供了成熟的第三方库来应对每种格式。
3.3.1 TXT 文件读写
纯文本文件读写使用内置 open() 函数,指定编码为 utf-8 以避免乱码。
# 读取 TXT
with open('contract.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 写入 TXT
with open('output.txt', 'w', encoding='utf-8') as f:
f.write(content)
3.3.2 PDF 文件读取
推荐使用 pdfplumber 库,它比 PyPDF2 更擅长提取表格和保留文本顺序。
import pdfplumber
with pdfplumber.open('contract.pdf') as pdf:
full_text = []
for page in pdf.pages:
full_text.append(page.extract_text())
text = '\n'.join(full_text)
注意: PDF 中的扫描件(图片型 PDF)无法直接提取文本,需要先经过 OCR(如 Tesseract)处理。本课程后续章节会专门讲解 OCR 方案。
3.3.3 DOCX 文件读写
使用 python-docx 库,可以逐段落读取 Word 文档,保留段落结构。
from docx import Document
doc = Document('contract.docx')
paragraphs = [p.text for p in doc.paragraphs if p.text.strip()]
full_text = '\n'.join(paragraphs)
3.3.4 格式对比一览
| 格式 | 推荐库 | 安装命令 | 适用场景 |
|---|---|---|---|
| TXT | 内置 open() |
无需安装 | 中间处理、日志、纯文本合同 |
pdfplumber |
pip install pdfplumber |
正式签署版合同、扫描件除外 | |
| DOCX | python-docx |
pip install python-docx |
可编辑版合同、修订稿 |
3.4 综合案例:合同文本预处理流水线
将本章知识点串联成一个完整的预处理函数,用于清洗并提取合同中的关键字段。
import re
import pdfplumber
from docx import Document
def preprocess_contract(file_path):
# 1. 根据扩展名读取文件
if file_path.endswith('.txt'):
with open(file_path, 'r', encoding='utf-8') as f:
text = f.read()
elif file_path.endswith('.pdf'):
with pdfplumber.open(file_path) as pdf:
text = '\n'.join([p.extract_text() for p in pdf.pages])
elif file_path.endswith('.docx'):
doc = Document(file_path)
text = '\n'.join([p.text for p in doc.paragraphs if p.text.strip()])
else:
raise ValueError("不支持的文件格式")
# 2. 全角转半角
text = full_to_half(text)
# 3. 去除多余空白
text = re.sub(r'\s+', ' ', text).strip()
# 4. 提取关键信息
info = {}
date_match = re.search(r'\d{4}-\d{2}-\d{2}', text)
if date_match:
info['date'] = date_match.group()
amount_match = re.search(r'¥[\d,]+\.\d{2}', text)
if amount_match:
info['amount'] = amount_match.group()
return text, info