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

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

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


🚀 项目效果预览

运行脚本后:

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

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


🛠️ 技术栈简介

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

📂 核心功能解析

1️⃣ 读取 .docx 文件内容

from docx import Documentdef read_docx(file_path):"""读取 .docx 文件,返回段落列表"""try:doc = Document(file_path)paragraphs = [para.text.strip() for para in doc.paragraphs if para.text.strip()]return paragraphsexcept Exception as e:print(f"❌ 读取文件 {file_path} 时出错: {e}")return []

📌 说明:

  • 使用 Document 加载 Word 文件
  • 提取所有非空段落,并去除首尾空格
  • 异常捕获确保程序健壮性

2️⃣ 利用 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>

3️⃣ 生成精美 HTML 报告

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

✅ 响应式设计 + 现代化 UI 风格
  • 渐变标题栏
  • 圆角卡片式表格
  • 悬停交互效果
  • 适配中文字体
✅ 对比概览区

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

✅ 详细对比表格
段落文档1内容文档2内容差异状态
第1段✅ 相同✅ 相同✅ 完全相同
第2段❌ 被删除➕ 新增内容⚠️ 内容有差异

每一段都用颜色区分:

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

4️⃣ 自动生成并打开报告

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 datetimedef read_docx(file_path):"""读取 .docx 文件,返回段落列表"""try:doc = Document(file_path)paragraphs = [para.text.strip() for para in doc.paragraphs if para.text.strip()]return paragraphsexcept 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 Nonep2 = 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_htmlexcept Exception as e:print(f"❌ 生成 HTML 文件时出错: {e}")return Nonedef 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("⛔ 文档读取失败,请检查文件。")returnprint(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:// URLurl = 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()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94605.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94605.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0开始搭建一个前端项目(vue + vite + typescript)

版本 node&#xff1a;v22.17.1 pnpm&#xff1a;v10.13.1 vue&#xff1a;^3.5.18 vite&#xff1a;^7.0.6 typescipt&#xff1a;~5.8.0脚手架初始化vue pnpm create vuelatest只选择&#xff1a; TypeScript, JSX 3. 用vscode打开创建的项目&#xff0c;并删除多余的代码esl…

1.ImGui-环境安装

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 IMGUI是一个被广泛应用到逆向里面的&#xff0c;它可以用来做外部的绘制&#xff0c;比如登录界面&…

基于springboot的二手车交易系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

修改win11任务栏时间字体和小图标颜色

1 打开运行提示框 在桌面按快捷键winR&#xff0c;然后如下图所示输入regedit2 查找路径 1、在路径处粘贴路径计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize 2、如下图所示&#xff0c;双击打开ColorPrevalence&#xff0c;将里面的…

第13集 当您的USB设备不在已实测支持列表,如何让TOS-WLink支持您的USB设备--答案Wireshark USB抓包

问&#xff1a;当您的USB设备不在已实测支持列表&#xff0c;如何让TOS-WLink支持您的USB设备&#xff1f; 答案&#xff1a;使用Wireshark USB抓包&#xff0c;日志发给我 为什么要抓包&#xff1a; USB设备种类繁多&#xff1b;TOS-WLink是单片机&#xff0c;内存紧张&#…

[灵动微电子 MM32BIN560CN MM32SPIN0280]读懂电机MCU之比较器

作为刚接触微控制器的初学者&#xff0c;在看到MM32SPIN0280用户手册中“比较器”相关内容时&#xff0c;是不是会感到困惑&#xff1f;比如“5个通用比较器”“轮询功能”“迟滞电压”这些术语&#xff0c;好像都和电机控制有关&#xff0c;但又不知道具体怎么用。别担心&…

⸢ 贰 ⸥ ⤳ 安全架构:数字银行安全体系规划

&#x1f44d;点「赞」&#x1f4cc;收「藏」&#x1f440;关「注」&#x1f4ac;评「论」 &#x1f525;更多文章戳&#x1f449;Whoami&#xff01;-CSDN博客&#x1f680; 在金融科技深度融合的背景下&#xff0c;信息安全已从单纯的技术攻防扩展至架构、合规、流程与创新的…

布隆过滤器完全指南:从原理到实战

布隆过滤器完全指南:从原理到实战 摘要:本文深入解析布隆过滤器的核心原理、实现细节和实际应用,提供完整的Java实现代码,并探讨性能优化策略。适合想要深入理解概率数据结构的开发者阅读。 前言 在大数据时代,如何快速判断一个元素是否存在于海量数据集合中?传统的Hash…

​嵌入式Linux学习 - 网络服务器实现与客户端的通信

1.单循环服务器 2.并发服务器 1. 设置socket属性 2. 进程 ​3. 线程 3.多路IO复用模型 - 提高并发程度 1. 区别 2. IO处理模型 1. 阻塞IO模型 2. 非阻塞IO模型 3. 信号驱动IO 4. IO多路复用 3. 特点 4. 函数接口 1. select 2. poll 3. epoll 半包 1.单循环服务…

Mybatis中缓存机制的理解以及优缺点

文章目录一、MyBatis 缓存机制详解1. 一级缓存&#xff08;Local Cache&#xff09;2. 二级缓存&#xff08;Global Cache&#xff09;3. 缓存执行顺序二、MyBatis 缓存的优点三、MyBatis 缓存的缺点四、适用场景与最佳实践总结MyBatis 提供了完善的缓存机制&#xff0c;用于减…

Rust 登堂 之 类型转换(三)

Rust 是类型安全的语言&#xff0c;因此在Rust 中做类型转换不是一件简单的事&#xff0c;这一章节&#xff0c;我们将对Rust 中的类型转换进行详尽讲解。 高能预警&#xff0c;本章节有些难&#xff0c;可以考虑学了进阶后回头再看 as 转换 先来看一段代码 fn main() {let a…

【MySQL 为什么默认会给 id 建索引? MySQL 主键索引 = 聚簇索引?】

MySQL 索引 MySQL 为什么默认会给 id 建索引&#xff1f; & MySQL 主键索引 聚簇索引&#xff1f; 结论&#xff1a;在 MySQL (InnoDB) 中&#xff0c;主键索引是自动创建的聚簇索引&#xff0c;不需要删除&#xff0c;其他索引是补充优化。 1. MySQL 的id 索引是怎么来的…

[光学原理与应用-321]:皮秒深紫外激光器产品不同阶段使用的工具软件、对应的输出文件

在皮秒深紫外激光器的开发过程中&#xff0c;不同阶段使用的工具软件及其对应的输出文件如下&#xff1a;一、设计阶段工具软件&#xff1a;Zemax OpticStudio&#xff1a;用于光学系统的初步设计和仿真&#xff0c;包括光线追迹、像差分析、优化设计等。MATLAB&#xff1a;用于…

openEuler常用操作指令

openEuler常用操作指令 一、前言 1.简介 openEuler是由开放原子开源基金会孵化的全场景开源操作系统项目&#xff0c;面向数字基础设施四大核心场景&#xff08;服务器、云计算、边缘计算、嵌入式&#xff09;&#xff0c;全面支持ARM、x86、RISC-V、loongArch、PowerPC、SW…

Python爬虫实战:构建网易云音乐个性化音乐播放列表同步系统

1. 引言 1.1 研究背景 在数字音乐生态中,各大音乐平台凭借独家版权、个性化推荐等优势占据不同市场份额。根据国际唱片业协会(IFPI)2024 年报告,全球流媒体音乐用户已突破 50 亿,其中超过 60% 的用户同时使用 2 个及以上音乐平台。用户在不同平台积累的播放列表包含大量…

vscode 配置 + androidStudio配置

插件代码片段 饿了么 icon{"Print to console": {"prefix": "ii-ep-","body": ["i-ep-"],"description": "elementPlus Icon"} }Ts 初始化模版{"Print to console": {"prefix": &q…

DQN(深度Q网络):深度强化学习的里程碑式突破

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; ✨ 1. DQN概述&#xff1a;当深度学习遇见强化学习 DQN&#xff08;D…

个人博客运行3个月记录

个人博客 自推一波&#xff0c;目前我的Hexo个人博客已经优化的足够好了&#xff0c; 已经足够稳定的和简单进行发布和管理&#xff0c;但还是有不少问题&#xff0c;总之先记下来再说 先总结下 关于评论系统方面&#xff0c;我从Waline (快速上手 | Waline) 更换成了&#x…

C89标准关键字以及运算符分类汇总

开发单片机项目学好C语言尤其重要&#xff0c;我感觉学习C语言需要先学好关键字和运算符&#xff0c;我对C语言的关键字和运算符做一下汇总。一、关键字&#xff1a;&#xff08;C89标准一共有32个关键字&#xff09;(1) 数据类型关键字&#xff08;一共12个&#xff0c;分为基…

吱吱企业通讯软件打破跨部门沟通壁垒,为企业搭建安全的通讯环境

在数字化转型浪潮中&#xff0c;企业通讯软件不再仅仅作为企业跨部门沟通桥梁&#xff0c;更是承载着保护通讯数据安全的使命。吱吱企业通讯凭借其“私有化部署全链路加密”双重机制&#xff0c;为企业构建了一套“沟通便捷、通讯安全”的数字化通讯解决方案。 一、打破沟通壁垒…