本文介绍如何基于语法解析技术(而非仅正则匹配)精确定位excel中合同描述文本的格式错误类型及发生位置,支持批量校验数千行数据,并输出可读性强的错误诊断信息。
本文介绍如何基于语法解析技术(而非仅正则匹配)精确定位excel中合同描述文本的格式错误类型及发生位置,支持批量校验数千行数据,并输出可读性强的错误诊断信息。
在合同履约信息自动化校验场景中,仅判断“是否合规”(OK / not OK)远远不够——业务人员真正需要的是可操作的纠错指引:错误是什么?出现在哪个词之后?是空格冗余、标点缺失,还是日期格式错位?传统正则表达式(re.match)虽能做整体匹配,但一旦失败,便无法提供细粒度错误定位。为此,我们应将合同描述视为一种轻量级领域特定语言(DSL),采用语法驱动的解析方法,实现结构化校验与精准报错。
为什么选择语法解析而非增强型正则?
正则表达式擅长“模式存在性判断”,但难以表达顺序依赖、上下文敏感、局部容错等语义。例如:
- № 90/11/122 前必须有空格,但 №90/11/122(紧邻)即为错误;
- VAT exempt. 结尾必须带英文句号,缺则报错,且需指出错误发生在 , VAT exempt 末尾;
- 多余空格(如 10023__Serviced 中的双下划线或双空格)需定位到具体字符偏移。
这些问题用单一正则几乎无法优雅解决;而基于 PEG(Parsing Expression Grammar) 的解析器(如 parsimonious)天然支持:
- 显式定义语法规则与子结构;
- 在解析失败时返回最左侧、最深层的不匹配规则及精确位置(行/列);
- 支持自定义访问逻辑,便于扩展错误分类与修复建议。
实战:构建可诊断的合同描述解析器
以下是一个生产就绪的解析方案,使用 parsimonious 库(轻量、纯Python、无C依赖):
from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor
from parsimonious.exceptions import ParseError
import re
# 定义严格但可诊断的语法规则(注意:ws 规则显式捕获空格,便于定位)
GRAMMAR = Grammar(r"""
entry = prefix ws object_no ws serviced ws date ws fulfilment ws obligation_no ws dated ws date ws vat
prefix = "Object №"
serviced = "Serviced"
fulfilment = "Fulfilment of obligations under agr. №"
dated = "dated"
vat = ", VAT exempt\."
object_no = ~r"d+"
date = ~r"(0[1-9]|[12][0-9]|3[01]).(0[1-9]|1[0-2]).(20dd)"
obligation_no = ~r"d+/d+/d+"
ws = ~r"s+" # 匹配一个或多个空白符(含空格、制表符),关键:此处可捕获多余空格
""")
class ContractParser(NodeVisitor):
def __init__(self):
self.errors = []
def visit_entry(self, node, visited_children):
return "OK"
def generic_visit(self, node, visited_children):
return visited_children or node
def diagnose_contract(text: str) -> str:
"""对单条合同描述进行诊断,返回错误详情或'OK'"""
parser = ContractParser()
try:
_ = GRAMMAR.parse(text)
return "OK"
except ParseError as e:
# 提取关键错误信息:失败规则名 + 文本位置
rule_name = e.expr.name if e.expr.name else "unknown"
line, col = e.line(), e.column()
# 尝试从上下文推断更友好的错误描述
if rule_name == "ws":
# 检查附近是否有多余空格或缺失空格
context_start = max(0, e.pos - 10)
context_end = min(len(text), e.pos + 15)
context = text[context_start:context_end]
if " " in context or "__" in context:
return f"多余空格(位于'{context.strip()}'附近)"
else:
return f"缺失必要空格(期望分隔符,但在'{context.strip()}'处未匹配)"
elif rule_name == "vat":
if text.rstrip().endswith(", VAT exempt"):
return "结尾缺少英文句号(.)"
else:
return f"VAT声明格式错误(期望', VAT exempt.',实际未匹配)"
elif rule_name == "object_no":
return "合同编号应为纯数字,发现非数字字符"
elif rule_name == "date":
return "日期格式错误(应为 dd.mm.yyyy,如 30.11.2023)"
else:
return f"语法错误:{rule_name} 规则不匹配(位置:第{line}行,第{col}列)"
# 批量应用到DataFrame
import pandas as pd
df['text_verification'] = df['original information'].apply(diagnose_contract)关键优势与注意事项
✅ 精准定位:ParseError 自带 line() 和 column() 方法,可直接映射到原始字符串索引,无需手动计算偏移。
✅ 可扩展性强:新增校验项(如校验 Object 后必须跟空格、禁止全角符号)只需修改语法规则或 visit_* 方法。
✅ 错误友好:通过 diagnose_contract() 函数封装,将底层解析异常转化为业务人员可理解的语言(如“多余空格”“缺句号”)。
⚠️ 注意事项:
- 首次运行需安装:pip install parsimonious;
- 语法规则中的 ws = ~r"s+" 是关键——它强制所有分隔符必须是一个或多个空白符,从而让双空格、制表符等异常被明确捕获;
- 实际部署前,务必用真实样本覆盖边界情况(如年份超限 2100、日期无效 31.02.2023),可在 date 规则中加入 Python 层校验;
- 对于超大数据集(>10万行),建议结合 concurrent.futures.ThreadPoolExecutor 并行处理,提升吞吐量。
通过将文本校验升维为语法解析,你不仅获得了错误“在哪里”,更掌握了“为什么错”和“怎么改”。这正是自动化数据治理迈向精细化、可解释性的关键一步。










