在日常工作和学习中,我们经常需要对两个版本的文档进行比对,比如合同修改、论文修订、报告更新等。手动逐字检查不仅耗时费力,还容易遗漏细节。
今天,我将带你使用 Python + python-docx
+ difflib
实现一个自动化 Word 文档对比工具 ,不仅能精准识别段落增删改,还能生成美观的 HTML 可视化报告,并自动在浏览器中打开查看!
🚀 项目效果预览
运行脚本后:
- 自动读取两个
.docx
文件 - 按段落逐一对比内容差异
- 生成一份 高亮标注差异 的 HTML 报告
- 支持浏览器自动打开,无需手动查找文件
✅ 完全自动化 | ✅ 差异高亮显示 | ✅ 支持中文 | ✅ 界面美观专业
🛠️ 技术栈简介
技术 | 作用 |
---|---|
python-docx |
读取 .docx 文件内容 |
difflib |
计算文本差异(增删改) |
webbrowser |
自动打开浏览器预览结果 |
os / datetime |
文件路径处理与时间戳记录 |
原生 HTML + CSS | 生成结构清晰、样式现代化的报告页面 |
📂 核心功能解析
1️⃣ 读取 .docx
文件内容
python
from docx import Document
def read_docx(file_path):
"""读取 .docx 文件,返回段落列表"""
try:
doc = Document(file_path)
paragraphs = [para.text.strip() for para in doc.paragraphs if para.text.strip()]
return paragraphs
except Exception as e:
print(f"❌ 读取文件 {file_path} 时出错: {e}")
return []
📌 说明:
- 使用
Document
加载 Word 文件 - 提取所有非空段落,并去除首尾空格
- 异常捕获确保程序健壮性
2️⃣ 利用 difflib
实现文本差异分析
python
matcher = difflib.SequenceMatcher(None, p1, p2)
opcodes = matcher.get_opcodes()
difflib.SequenceMatcher
是 Python 内置的强大工具,可以分析两个字符串之间的差异操作(equal, insert, delete, replace),我们据此为每个字符添加 HTML 标签高亮。
例如:
html
<span class='diff-delete'>[删除: 这句话被删了]</span>
<span class='diff-add'>[新增: 这是新内容]</span>
3️⃣ 生成精美 HTML 报告
我们构建了一个完整的 HTML 页面,包含:
✅ 响应式设计 + 现代化 UI 风格
- 渐变标题栏
- 圆角卡片式表格
- 悬停交互效果
- 适配中文字体
✅ 对比概览区
显示文件名、段落数量、生成时间等元信息。
✅ 详细对比表格
段落 | 文档1内容 | 文档2内容 | 差异状态 |
---|---|---|---|
第1段 | ✅ 相同 | ✅ 相同 | ✅ 完全相同 |
第2段 | ❌ 被删除 | ➕ 新增内容 | ⚠️ 内容有差异 |
每一段都用颜色区分:
- 🟢 绿色:相同内容
- 🔴 红色:删除或缺失
- 🔵 蓝色:新增内容
- 🟡 黄色背景:整段新增/删除
4️⃣ 自动生成并打开报告
python
abs_path = os.path.abspath(report_path)
url = f"file://{abs_path}"
webbrowser.open(url)
一键生成报告后,自动调用系统默认浏览器打开,用户体验极佳!
💡 使用方法(超简单)
步骤 1:安装依赖
bash
pip install python-docx
⚠️ 注意:
difflib
是标准库,无需安装。
步骤 2:修改主函数中的文件路径
python
file1_path = r'C:\Users\Administrator\Desktop\11.docx'
file2_path = r'C:\Users\Administrator\Desktop\22docx.docx'
file1_name = "11.docx (原文版)"
file2_name = "22docx.docx (对比版)"
output_html_file = "荷塘月色_对比报告.html"
📌 支持任意 .docx
文件,建议使用绝对路径避免出错。
步骤 3:运行脚本
bash
python docx_compare.py
输出示例:
🚀 正在启动文档对比程序...
============================================================
✅ 成功读取文档!
• 11.docx (原文版) 共 15 段
• 22docx.docx (对比版) 共 17 段
✅ HTML 报告已成功生成: '荷塘月色_对比报告.html'
🌐 报告已自动在默认浏览器中打开。
🖼️ 报告截图展示
- 顶部标题:带有时间戳和渐变背景
- 对比表格:左右并列展示,差异高亮
- 状态标识:图标+颜色提示,一目了然
📎 完整代码下载
你可以将本文提供的完整代码保存为 docx_compare.py
,稍作配置即可使用。
import difflib
from docx import Document
import webbrowser
import os
from datetime import datetime
def read_docx(file_path):
"""读取 .docx 文件,返回段落列表"""
try:
doc = Document(file_path)
paragraphs = [para.text.strip() for para in doc.paragraphs if para.text.strip()]
return paragraphs
except Exception as e:
print(f"❌ 读取文件 {file_path} 时出错: {e}")
return []
def generate_html_report(paras1, paras2, file1_name, file2_name, output_html="对比报告.html"):
"""
根据对比结果生成 HTML 报告。
"""
# 构建 HTML 内容
html_content = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>文档对比报告</title>
<style>
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.8;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f9f9f9;
}}
.header {{
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}}
.header h1 {{
margin: 0;
font-size: 2.5em;
}}
.header p {{
margin: 10px 0 0;
opacity: 0.9;
}}
.comparison-table {{
width: 100%;
border-collapse: collapse;
margin: 20px 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
background-color: white;
border-radius: 8px;
overflow: hidden;
}}
.comparison-table th {{
background-color: #4a5568;
color: white;
text-align: left;
padding: 15px;
font-weight: 600;
}}
.comparison-table td {{
padding: 15px;
border-bottom: 1px solid #e2e8f0;
vertical-align: top;
}}
.comparison-table tr:nth-child(even) {{
background-color: #f7fafc;
}}
.comparison-table tr:hover {{
background-color: #ebf8ff;
}}
.diff-equal {{
color: #22c55e;
font-weight: 600;
}}
.diff-delete {{
color: #e53e3e;
font-weight: 600;
}}
.diff-add {{
color: #3182ce;
font-weight: 600;
}}
.context {{
font-family: 'Courier New', monospace;
background-color: #f1f5f9;
padding: 10px;
border-radius: 5px;
white-space: pre-wrap;
word-wrap: break-word;
font-size: 0.95em;
}}
.footer {{
text-align: center;
margin-top: 40px;
color: #718096;
font-size: 0.9em;
}}
.highlight {{
background-color: #fff3cd;
padding: 2px 4px;
border-radius: 3px;
}}
</style>
</head>
<body>
<div class="header">
<h1>📄 文档对比报告</h1>
<p>生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
</div>
<h2>📊 对比概览</h2>
<p><strong>文档 1:</strong> {file1_name}</p>
<p><strong>文档 2:</strong> {file2_name}</p>
<p><strong>段落数量:</strong> 文档1: {len(paras1)} 段, 文档2: {len(paras2)} 段</p>
<h2>🔍 详细对比结果</h2>
<table class="comparison-table">
<thead>
<tr>
<th>段落</th>
<th>{file1_name}</th>
<th>{file2_name}</th>
<th>差异状态</th>
</tr>
</thead>
<tbody>
"""
max_len = max(len(paras1), len(paras2))
for i in range(max_len):
p1 = paras1[i] if i < len(paras1) else None
p2 = paras2[i] if i < len(paras2) else None
# 生成差异标记文本
if p1 is not None and p2 is not None:
matcher = difflib.SequenceMatcher(None, p1, p2)
opcodes = matcher.get_opcodes()
result1 = []
result2 = []
for tag, i1, i2, j1, j2 in opcodes:
if tag == 'equal':
result1.append(p1[i1:i2])
result2.append(p2[j1:j2])
elif tag == 'delete':
result1.append(f"<span class='diff-delete'>[删除: {p1[i1:i2]}]</span>")
result2.append(f"<span class='diff-add'>[缺失]</span>")
elif tag == 'insert':
result1.append(f"<span class='diff-add'>[缺失]</span>")
result2.append(f"<span class='diff-delete'>[新增: {p2[j1:j2]}]</span>")
elif tag == 'replace':
result1.append(f"<span class='diff-delete'>[删除: {p1[i1:i2]}]</span>")
result2.append(f"<span class='diff-add'>[新增: {p2[j1:j2]}]</span>")
marked_p1 = ''.join(result1)
marked_p2 = ''.join(result2)
else:
marked_p1 = p1 or "<span class='diff-add'>[该段落不存在]</span>"
marked_p2 = p2 or "<span class='diff-add'>[该段落不存在]</span>"
# 确定差异状态
if p1 is None:
status = f"<span class='diff-add'>➕ 新增段落</span>"
elif p2 is None:
status = f"<span class='diff-delete'>➖ 删除段落</span>"
elif p1 == p2:
status = f"<span class='diff-equal'>✅ 完全相同</span>"
else:
status = f"<span class='diff-delete'>⚠️ 内容有差异</span>"
# 添加到HTML表格中
paragraph_num = f"第 {i+1} 段"
html_content += f"""
<tr>
<td><strong>{paragraph_num}</strong></td>
<td class="context">{marked_p1}</td>
<td class="context">{marked_p2}</td>
<td>{status}</td>
</tr>
"""
# 闭合HTML标签
html_content += f"""
</tbody>
</table>
<div class="footer">
<p>此报告由 Python 文档对比工具自动生成</p>
</div>
</body>
</html>
"""
# 写入文件
try:
with open(output_html, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"✅ HTML 报告已成功生成: '{output_html}'")
return output_html
except Exception as e:
print(f"❌ 生成 HTML 文件时出错: {e}")
return None
def main():
# 🔧 请修改为您的实际文件路径
file1_path = r'C:\Users\Administrator\Desktop\11.docx'
file2_path = r'C:\Users\Administrator\Desktop\22docx.docx'
# 自定义显示名称
file1_name = "11.docx (原文版)"
file2_name = "22docx.docx (对比版)"
output_html_file = "荷塘月色_对比报告.html"
print("🚀 正在启动文档对比程序...")
print("=" * 60)
# 读取文档
paras1 = read_docx(file1_path)
paras2 = read_docx(file2_path)
if not paras1 or not paras2:
print("⛔ 文档读取失败,请检查文件。")
return
print(f"✅ 成功读取文档!")
print(f" • {file1_name} 共 {len(paras1)} 段")
print(f" • {file2_name} 共 {len(paras2)} 段")
# 生成 HTML 报告
report_path = generate_html_report(paras1, paras2, file1_name, file2_name, output_html_file)
if report_path:
# 获取文件的绝对路径
abs_path = os.path.abspath(report_path)
# 构造 file:// URL
url = f"file://{abs_path}"
# 自动打开浏览器
try:
webbrowser.open(url)
print(f"🌐 报告已自动在默认浏览器中打开。")
except Exception as e:
print(f"⚠️ 自动打开浏览器失败,您可以手动打开文件:\n {abs_path}")
else:
print("❌ 报告生成失败。")
if __name__ == "__main__":
main()