【Word】用 Python 轻松实现 Word 文档对比并生成可视化 HTML 报告

在日常工作和学习中,我们经常需要对两个版本的文档进行比对,比如合同修改、论文修订、报告更新等。手动逐字检查不仅耗时费力,还容易遗漏细节。

今天,我将带你使用 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()
相关推荐
青 春 记 忆6 小时前
Dify Docker Compose 通用无损升级指南:从备份、双版本预演到切换与回滚
运维·人工智能·python·docker·容器
GlueNa2SiO36 小时前
03-Flask模板引擎Jinja2详解
笔记·python·flask
临沂GEO8 小时前
GEO搜索优化科普|正规地理位置流量运营入门指南
大数据·人工智能·python·流量运营
大模型码小白8 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
荷蒲10 小时前
【小白量化Qbuddy】利用AI学习中文Python
人工智能·python·学习
数据狐(Datafox)11 小时前
京东商品列表API技术解析与落地应用(含标准 JSON 示例)
java·大数据·前端·人工智能·python·数据分析·json
量化吞吐机11 小时前
2026年下半年手工交易规则走向量化表达,产品该先接住哪一步
人工智能·python
Metaphor69212 小时前
使用 Python 读取和删除 Excel 文件属性
python·excel
迷迭香yy13 小时前
Python构建转融券多空识别系统从接口到因子的全流程 IG50免费开源股票数据API接口
开发语言·python·开源
月光船幽幽13 小时前
真实即粗糙,光滑是伪造
人工智能·python