Python Word对比两个文档差异:找不同
用python-docx加difflib对比两个Word文档的文本差异,输出新增、删除、修改的段落列表,批量对比合同版本、修改稿,不用Word自带比较功能。
场景痛点
一份合同改了三版,领导问"这版和上版到底改了哪里"。Word自带的"比较文档"功能能做,但每次选文件、等加载,两份文件几十页时半天出不来。用Python直接把两份文档的段落文本抽出来,用difflib做行级diff,几秒钟出差异报告,新增了哪段、删了哪段一目了然。
用到的库
pip install python-docx
完整代码
# -*- coding: utf-8 -*-
"""
对比两个 Word 文档的段落差异
输出:新增段落、删除段落、修改段落
"""
from pathlib import Path
import difflib
from docx import Document
def extract_paragraphs(docx_path: Path):
"""提取文档所有非空段落文本,按顺序返回列表"""
doc = Document(str(docx_path))
paras = []
for p in doc.paragraphs:
text = p.text.strip()
if text:
paras.append(text)
return paras
def compare_docs(old_path: Path, new_path: Path):
old_lines = extract_paragraphs(old_path)
new_lines = extract_paragraphs(new_path)
# 用 SequenceMatcher 做行级 diff
sm = difflib.SequenceMatcher(None, old_lines, new_lines, autojunk=False)
ops = sm.get_opcodes()
added, deleted, replaced = [], [], []
for tag, i1, i2, j1, j2 in ops:
if tag == "equal":
continue
elif tag == "delete":
for line in old_lines[i1:i2]:
deleted.append(line)
elif tag == "insert":
for line in new_lines[j1:j2]:
added.append(line)
elif tag == "replace":
# 旧版删了一段,新版加了一段,配成对展示
old_block = old_lines[i1:i2]
new_block = new_lines[j1:j2]
replaced.append((" | ".join(old_block), " | ".join(new_block)))
return {
"新增段落": added,
"删除段落": deleted,
"修改段落": replaced,
}
def print_report(old_path, new_path, result):
print("=" * 60)
print(f"对比:{old_path.name} -> {new_path.name}")
print("=" * 60)
print(f"\n【新增段落】共 {len(result['新增段落'])} 段")
for i, line in enumerate(result["新增段落"], 1):
print(f" + {i}. {line[:80]}{'...' if len(line) > 80 else ''}")
print(f"\n【删除段落】共 {len(result['删除段落'])} 段")
for i, line in enumerate(result["删除段落"], 1):
print(f" - {i}. {line[:80]}{'...' if len(line) > 80 else ''}")
print(f"\n【修改段落】共 {len(result['修改段落'])} 处")
for i, (old, new) in enumerate(result["修改段落"], 1):
print(f" ≈ {i}. 旧:{old[:60]}")
print(f" 新:{new[:60]}")
def save_report_txt(old_path, new_path, result, out_path):
"""把差异报告存成 txt"""
with open(out_path, "w", encoding="utf-8") as f:
f.write(f"对比报告:{old_path.name} -> {new_path.name}\n")
f.write("=" * 60 + "\n\n")
f.write(f"【新增段落】共 {len(result['新增段落'])} 段\n")
for line in result["新增段落"]:
f.write(f" + {line}\n")
f.write(f"\n【删除段落】共 {len(result['删除段落'])} 段\n")
for line in result["删除段落"]:
f.write(f" - {line}\n")
f.write(f"\n【修改段落】共 {len(result['修改段落'])} 处\n")
for old, new in result["修改段落"]:
f.write(f" 旧:{old}\n 新:{new}\n\n")
def demo():
# 造两份演示文档
doc1 = Document()
doc1.add_paragraph("第一条 甲方应在合同签订后7日内付款。")
doc1.add_paragraph("第二条 交货地点为甲方指定仓库。")
doc1.add_paragraph("第三条 争议由西安仲裁委员会仲裁。")
doc1.add_paragraph("第四条 本合同自双方签字盖章之日起生效。")
doc1.save("./v1.docx")
doc2 = Document()
doc2.add_paragraph("第一条 甲方应在合同签订后15日内付款。") # 改了
doc2.add_paragraph("第二条 交货地点为甲方指定仓库。")
doc2.add_paragraph("第三条 争议由宝鸡仲裁委员会仲裁。") # 改了
doc2.add_paragraph("第五条 本合同一式两份,双方各执一份。") # 新增
doc2.save("./v2.docx")
result = compare_docs(Path("./v1.docx"), Path("./v2.docx"))
print_report(Path("./v1.docx"), Path("./v2.docx"), result)
save_report_txt(Path("./v1.docx"), Path("./v2.docx"), result, "./diff_report.txt")
print("\n差异报告已保存:./diff_report.txt")
if __name__ == "__main__":
demo()
代码讲解
extract_paragraphs() 把两个文档的非空段落按顺序抽成字符串列表。difflib.SequenceMatcher 对两个列表做最长公共子序列比对,get_opcodes() 返回每段是 equal / delete / insert / replace 哪种操作。
insert:新版多出来的段落,算"新增"delete:旧版有新版没的段落,算"删除"replace:旧版一段被新版一段替换,配对成"修改",旧新一起展示
print_report() 在控制台打印分类结果,长段落截断到80字符。save_report_txt() 把完整结果写到txt,方便存档或发给同事。
运行结果
控制台输出三类差异:新增几段、删除几段、修改几处,每处新旧对照。同目录生成 diff_report.txt,用记事本打开看完整差异。演示数据里能看到"7日变15日""西安变宝鸡""多了第五条"三处变化。
注意事项
- 本对比是"段落级"diff,不是"字符级"。同一段里改了几个字会整段标记成"修改",不会圈出具体哪个字改了。要字符级diff,对 replace 块内部再做一次 difflib.ndiff 即可。
- 表格内容没对比,需要的话把表格文本也加进 paragraphs 列表。
- 文档里空段落会被忽略,避免空行差异干扰。
- Word自带的"比较文档"能生成带修订痕迹的新Word,可视化更好。本脚本适合批量、无人值守地出差异摘要,或者嵌入到审批流里。
- 大文档(几百页)跑SequenceMatcher可能慢,
autojunk=False关掉启发式过滤,结果更准但慢些。

更新时间:2026-09-15 08:55:51