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

 更新时间:2025年08月29日 08:22:18   作者:幸福清风  
在日常工作和学习中,我们经常需要对两个版本的文档进行比对,本文将使用 Python + python-docx + difflib 实现一个自动化 Word 文档对比工具,感兴趣的可以了解下

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

今天,我将带你使用 Python + python-docx + difflib 实现一个自动化 Word 文档对比工具,不仅能精准识别段落增删改,还能生成美观的 HTML 可视化报告,并自动在浏览器中打开查看!

项目效果预览

运行脚本后:

  • 自动读取两个 .docx 文件
  • 按段落逐一对比内容差异
  • 生成一份 高亮标注差异 的 HTML 报告
  • 支持浏览器自动打开,无需手动查找文件

完全自动化 | 差异高亮显示 |  支持中文 | 界面美观专业

技术栈简介

技术作用
python-docx读取 .docx 文件内容
difflib计算文本差异(增删改)
webbrowser自动打开浏览器预览结果
os / datetime文件路径处理与时间戳记录
原生 HTML + CSS生成结构清晰、样式现代化的报告页面

核心功能解析

读取.docx文件内容

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 文件
  • 提取所有非空段落,并去除首尾空格
  • 异常捕获确保程序健壮性

利用difflib实现文本差异分析

matcher = difflib.SequenceMatcher(None, p1, p2)
opcodes = matcher.get_opcodes()

difflib.SequenceMatcher 是 Python 内置的强大工具,可以分析两个字符串之间的差异操作(equal, insert, delete, replace),我们据此为每个字符添加 HTML 标签高亮。

例如:

<span class='diff-delete'>[删除: 这句话被删了]</span>
<span class='diff-add'>[新增: 这是新内容]</span>

生成精美 HTML 报告

我们构建了一个完整的 HTML 页面,包含:

响应式设计 + 现代化 UI 风格

  • 渐变标题栏
  • 圆角卡片式表格
  • 悬停交互效果
  • 适配中文字体

对比概览区

显示文件名、段落数量、生成时间等元信息。

详细对比表格

段落文档1内容文档2内容差异状态
第1段相同相同完全相同
第2段被删除新增内容内容有差异

每一段都用颜色区分:

  • 绿色:相同内容
  • 红色:删除或缺失
  • 蓝色:新增内容
  • 黄色背景:整段新增/删除

自动生成并打开报告

abs_path = os.path.abspath(report_path)
url = f"file://{abs_path}"
webbrowser.open(url)

一键生成报告后,自动调用系统默认浏览器打开,用户体验极佳!

使用方法(超简单)

步骤 1:安装依赖

pip install python-docx

注意:difflib 是标准库,无需安装。

步骤 2:修改主函数中的文件路径

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:运行脚本

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()

以上就是Python轻松实现Word文档对比并生成可视化HTML报告的详细内容,更多关于Python对比Word文档的资料请关注脚本之家其它相关文章!

相关文章

  • python中如何使用正则表达式的非贪婪模式示例

    python中如何使用正则表达式的非贪婪模式示例

    贪婪与非贪婪模式影响的是被量词修饰的子表达式的匹配行为,下面这篇文章主要给大家介绍了关于python中如何使用正则表达式的非贪婪模式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-10-10
  • python 根据pid杀死相应进程的方法

    python 根据pid杀死相应进程的方法

    下面小编就为大家带来一篇python 根据pid杀死相应进程的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • python celery beat实现定时任务的示例代码

    python celery beat实现定时任务的示例代码

    在日常工作中,我们常常会用到需要周期性执行的任务,本文主要介绍了python celery beat实现定时任务的示例代码,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • python删掉重复行之drop_duplicates()用法示例

    python删掉重复行之drop_duplicates()用法示例

    Pandas的drop_duplicates()方法用于从DataFrame中删除重复的行,这篇文章主要给大家介绍了关于python删掉重复行之drop_duplicates()用法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-08-08
  • Flask创建并运行数据库迁移的实现过程

    Flask创建并运行数据库迁移的实现过程

    Flask创建并运行数据库迁移的过程是一个涉及多个步骤的操作,旨在帮助开发者在开发过程中管理数据库模式的变化,而不需要手动地删除和重建数据库表,从而避免数据丢失,以下是一个详细的步骤说明,需要的朋友可以参考下
    2024-09-09
  • Python能干什么、Python主要应用于哪些方面

    Python能干什么、Python主要应用于哪些方面

    无论是从入门级选手到专业级选手都在做的爬虫,还是Web程序开发、桌面程序开发还是科学计算、图像处理, Python都可以胜任。Python为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、 数据库、文本等大量内容。用Python开发,许多功能不必从零编写
    2023-06-06
  • Python中dict排序的两种方法

    Python中dict排序的两种方法

    字典本身是无序的,所以它每次输出都是不一样的,顺序都是乱的,那么字典如何排序,本文主要介绍了Python中dict排序的两种方法,感兴趣的可以了解一下
    2024-01-01
  • python同时替换多个字符串方法示例

    python同时替换多个字符串方法示例

    这篇文章主要介绍了python同时替换多个字符串方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • PyQt5学习之QThread类的使用详解

    PyQt5学习之QThread类的使用详解

    QThread是Qt线程类中最核心的底层类。要使用QThrea开始一个线程,可以创建它的一个子类,然后覆盖其QThread.run()函数。这篇文章就来和大家聊聊QThread类的使用,感兴趣的可以学习一下
    2022-12-12
  • pandas中std和numpy的np.std区别及说明

    pandas中std和numpy的np.std区别及说明

    这篇文章主要介绍了pandas中std和numpy的np.std区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08

最新评论