我的知识记录

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 关掉启发式过滤,结果更准但慢些。

Python Word对比两个文档差异:找不同

标签:

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

上一篇:Python Word生成合同模板批量填充方法

下一篇:Python Word批量打印文档:自动送打印机