影刀RPA 法律合同自动审查:关键条款提取与风险预警
影刀RPA 法律合同自动审查关键条款提取与风险预警什么情况用什么 → 怎么做 → 有什么坑作者林焱 | 飞行社出品什么情况用什么法务/律师每天要审几十份合同眼睛都看花了还容易漏掉关键风险条款用RPAPython自动提取关键条款风险提前预警。这套方案适合企业法务部门批量审查供应商/客户合同律师事务所合同初审筛选合同管理系统的数据自动录入核心工具影刀RPA pdfplumberPDF合同 python-docxWord合同 正则表达式 风险规则引擎店群矩阵自动化突破运营极限怎么做第一步从合同PDF/Word中提取文字importpdfplumberimportdocximportredefextract_contract_text(file_path):从PDF或Word合同中提取纯文本iffile_path.endswith(.pdf):textwithpdfplumber.open(file_path)aspdf:forpageinpdf.pages:page_textpage.extract_text()ifpage_text:textpage_text\nreturntexteliffile_path.endswith(.docx):docdocx.Document(file_path)text\n.join([para.textforparaindoc.paragraphs])# 也提取表格内容fortableindoc.tables:forrowintable.rows:forcellinrow.cells:textcell.text text\nreturntexteliffile_path.endswith(.doc):# .doc需要借助antiword或LibreOffice转换importsubprocess resultsubprocess.run([antiword,file_path],capture_outputTrue,textTrue)returnresult.stdoutreturn# 测试提取一份合同textextract_contract_text(采购合同_20260610.pdf)print(f提取到{len(text)}个字符)第二步定义关键条款的提取规则importrefromdatetimeimportdatetimeclassContractClauseExtractor:合同条款提取器# 定义各类条款的正则规则CLAUSE_PATTERNS{合同金额:[r总金额[:]\s*人民币?\s*([0-9,]\.?[0-9]*)\s*元,r合同总价[:]\s*人民币?\s*([0-9,]\.?[0-9]*)\s*元,r金额[:]\s*([0-9,]\.?[0-9]*)\s*元,],付款期限:[r付款期限[:]\s*([^\n]{2,50}),r应于[^\n]{0,20}日内[^\n]{0,30}付款,r付款时间[:]\s*([^\n]{2,50}),],违约责任:[r违约金[:]\s*([^\n]{2,100}),r违约方[应|须|需][^\n]{0,50},r如.*违约[^\n]{0,100},],合同期限:[r合同期限[:]\s*([^\n]{2,50}),r自\s*(\d{4}年\d{1,2}月\d{1,2}日)\s*至\s*(\d{4}年\d{1,2}月\d{1,2}日),r有效期[:]\s*([^\n]{2,50}),],争议解决:[r争议解决[:]\s*([^\n]{2,100}),r向\s*([^\n]{2,30}法院|仲裁委员会)\s*起诉,r适用\s*([^\n]{2,20}法律),],保密条款:[r保密[义务|条款][:]\s*([^\n]{2,200}),r不得向[第三方|外界][^\n]{0,50},],终止条件:[r合同终止[:]\s*([^\n]{2,200}),r有权解除[合同|协议][^\n]{0,100},]}defextract_clauses(self,text):提取所有关键条款results{}forclause_type,patternsinself.CLAUSE_PATTERNS.items():results[clause_type][]forpatterninpatterns:matchesre.findall(pattern,text)ifmatches:results[clause_type].extend(matches)# 去重forclause_typeinresults:results[clause_type]list(set(results[clause_type]))returnresults第三步风险预警规则引擎classRiskDetector:合同风险检测器RISK_RULES[{name:金额过大预警,check:lambdaclauses:self._extract_amount(clauses.get(合同金额,[]))1000000,severity:high,message:合同金额超过100万元需要上级审批},{name:付款期限过短,check:lambdaclauses:self._extract_days(clauses.get(付款期限,[30日])[0])15,severity:medium,message:付款期限少于15天注意现金流},{name:缺失违约责任条款,check:lambdaclauses:len(clauses.get(违约责任,[]))0,severity:high,message:⚠️ 合同缺失违约责任条款},{name:争议解决地不利,check:lambdaclauses:self._check_unfavorable_jurisdiction(clauses.get(争议解决,[])),severity:medium,message:争议解决地在对方所在地建议修改},{name:无限期保密义务,check:lambdaclauses:永久in .join(clauses.get(保密条款,[])),severity:low,message:保密义务期限为永久建议设置合理期限如3-5年},{name:单方解除权,check:lambdaclauses:self._check_unilateral_termination(clauses.get(终止条件,[])),severity:high,message:⚠️ 合同赋予对方单方解除权对我方不利}]defdetect_risks(self,clauses):检测风险返回风险列表risks[]forruleinself.RISK_RULES:try:ifrule[check](clauses):risks.append({rule_name:rule[name],severity:rule[severity],message:rule[message]})exceptExceptionase:print(f规则{rule[name]}执行失败:{e})returnrisksdef_extract_amount(self,amount_list):从金额列表中提取数值ifnotamount_list:return0amount_stramount_list[0].replace(,,)try:returnfloat(amount_str)except:return0def_extract_days(self,text):从文本中提取天数matchre.search(r(\d)\s*日,text)ifmatch:returnint(match.group(1))return30# 默认30天def_check_unfavorable_jurisdiction(self,clauses):检查是否约定在对方所在地争议解决text .join(clauses)keywords[对方所在地,被告所在地,甲方所在地]# 如果你是乙方甲方所在地对你不利returnany(kwintextforkwinkeywords)def_check_unilateral_termination(self,clauses):检查是否有单方解除权条款text .join(clauses)# 简单检测如果只有对方可以解除不利return对方有权解除intextand我方有权解除notintext第四步批量处理合同并生成审查报告defbatch_review_contracts(folder_path,output_excel):批量审查合同生成审查报告importosimportpandasaspdfromdatetimeimportdatetime extractorContractClauseExtractor()detectorRiskDetector()results[]contract_files[fforfinos.listdir(folder_path)iff.endswith((.pdf,.docx,.doc))]print(f发现{len(contract_files)}份合同开始审查...)forfilenameincontract_files:file_pathos.path.join(folder_path,filename)print(f正在审查:{filename})# 1. 提取文本textextract_contract_text(file_path)# 2. 提取条款clausesextractor.extract_clauses(text)# 3. 检测风险risksdetector.detect_risks(clauses)# 4. 汇总结果result{文件名:filename,合同金额:, .join(clauses.get(合同金额,[]))or未提取到,付款期限:, .join(clauses.get(付款期限,[]))or未提取到,高风险数:len([rforrinrisksifr[severity]high]),中风险数:len([rforrinrisksifr[severity]medium]),风险概要:; .join([r[message]forrinrisks])or未发现明显风险,审查时间:datetime.now().strftime(%Y-%m-%d %H:%M:%S)}results.append(result)# 5. 导出到Exceldfpd.DataFrame(results)df.to_excel(output_excel,indexFalse)print(f审查报告已生成:{output_excel})print(f共发现高风险条款{df[高风险数].sum()}处)returndf# 使用示例dfbatch_review_contracts(D:/合同待审/,合同审查报告_20260626.xlsx)第五步影刀RPA完整流程编排【定时触发】每天上午10点检查合同待审文件夹 ↓ 【读取文件夹】获取所有待审合同文件列表 ↓ 【循环】遍历每个合同文件 ↓ 【Python节点】extract_contract_text() → 提取文字 ↓ 【Python节点】extractor.extract_clauses() → 提取关键条款 ↓ 【Python节点】detector.detect_risks() → 风险检测 ↓ 【条件判断】发现高风险 ├─ 是 → 【企微通知】发送高风险预警给法务总监 └─ 否 → 继续 ↓ 【写入Excel】将审查结果写入合同审查汇总表 ↓ 【移动文件】将已审合同移动到已审合同文件夹按日期分子文件夹 ↓ 【生成报告】所有合同处理完后 → 生成合同审查日报.xlsx → 发送给法务部门全体有什么坑坑1PDF合同是扫描件无法直接提取文字很多老合同是扫描版PDF本质是图片pdfplumber提取出来是空的。解决# 先用OCR识别扫描版PDFfrompdf2imageimportconvert_from_pathimportpytesseractdefocr_pdf(file_path):将PDF扫描件转文字imagesconvert_from_path(file_path,dpi300)textforimginimages:textpytesseract.image_to_string(img,langchi_sim)returntext# 检测是否为扫描版defis_scanned_pdf(file_path):withpdfplumber.open(file_path)aspdf:# 第一页能提取到文字说明不是扫描版textpdf.pages[0].extract_text()returntextisNoneorlen(text.strip())50ifis_scanned_pdf(合同.pdf):textocr_pdf(合同.pdf)else:textextract_contract_text(合同.pdf)坑2合同格式千差万别正则匹配覆盖率低temu店群自动化报活动案例不同企业/行业的合同模板差异很大一套正则规则不可能覆盖所有。解决方案结合关键词AI模型做语义提取# 用大模型做合同条款提取准确率更高importrequestsdefextract_clauses_by_llm(text,api_key):用大模型提取合同条款promptf 请从以下合同中提取关键信息以JSON格式返回 - 合同金额数字单位元 - 付款期限天数 - 违约责任简要描述 - 合同期限起止日期 - 争议解决方式 合同内容{text[:3000]}# 只取前3000字避免超token限制 # 调用大模型API通义千问/文心一言/GPT等responserequests.post(https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation,headers{Authorization:fBearer{api_key}},json{model:qwen-turbo,input:{prompt:prompt},parameters:{}})resultresponse.json()returnjson.loads(result[output][text])坑3大批量处理时速度慢100份合同纯Python处理可能要10-20分钟。优化方案# 用多线程加速fromconcurrent.futuresimportThreadPoolExecutorimportosdefbatch_review_concurrent(folder_path,max_workers4):多线程并行审查合同contract_files[fforfinos.listdir(folder_path)iff.endswith((.pdf,.docx))]withThreadPoolExecutor(max_workersmax_workers)asexecutor:futures[]forfilenameincontract_files:file_pathos.path.join(folder_path,filename)futureexecutor.submit(review_single_contract,file_path)futures.append(future)results[f.result()forfinfutures]returnresults坑4敏感合同内容不能上传到外部API法律合同属于高度敏感信息不能发到外部大模型API。解决方案用本地部署的开源模型如ChatGLM-6B、Qwen本地版或者用规则引擎正则关键词不依赖外部API总结功能准确率适用场景正则提取60-70%格式规范的模板合同pdfplumber提取80-90%可选取文字的PDFOCR提取70-80%扫描版PDF合同大模型提取85-95%格式不规范的合同需脱敏后使用实际落地建议先用正则pdfplumber做第一版跑通流程对提取失败的合同再针对性优化规则或接入大模型辅助。