乐于分享
好东西不私藏

《彻底告别手动比对!用Python打造Word文档智能比对工具,差异自动高亮,爽到没朋友!》

《彻底告别手动比对!用Python打造Word文档智能比对工具,差异自动高亮,爽到没朋友!》

“带你横跨办公自动化的数据江海”

@摸鱼

前言

关于摸鱼

ABOUT MOYU

闻道有先后,术业有专攻。

各位大佬朋友们好!

~我依旧是你们的老朋友摸鱼~

在职场摸爬滚打的这十多年里,我用Python悄悄干了不少“正事”——不知不觉攒下了一整套办公自动化的实用项目技巧。去年10月初创立了公众号 「码海听潮」 ,初衷很简单:把重复的劳动交给代码,把摸鱼的时间留给生活。

目前已经吭哧吭哧更新了140多篇原创文章,每一篇都是实操干货,不讲虚的,只聊怎么用代码真正解放双手,帮大家早点下班、准点摸鱼

好了,多了不说,少了不唠,今天给大佬们分享一款本人原创的【Word文档差异智能对比工具这款工具相比手动逐字逐句对比两个Word文档,效率提升数十倍,这是一个非常实用的文档自动化工具,特别适合需要快速发现文档差异、版本对比的场景。支持批量比对和单文件比对两种模式,差异内容自动用红字黄底标注,一目了然!

《该办公工具成品展示:》

《该办公工具的演示视频:》

已关注

关注

重播 分享

《该办公工具核心功能亮点:》

01

 亮点1:双模式灵活比对

        • 📁 批量比对:一键对比两个文件夹下所有Word文档,自动匹配同名文件

        • 📄 单文件比对:精准对比两个指定文档,适合快速核查

        02

         亮点2:差异自动高亮标注🎨

            • 比对结果自动用红字黄底清晰标注差异内容

            • 拒绝修订后颜色保留,结果直观易读

            03

            亮点3:批量处理高效省时⚙️

            • 自动扫描匹配同名文件,无需手动逐一比对

            • 数十份文档一键完成,效率提升数十倍

            《该办公工具代码展示:》

            下面,我就用python代码让各位大佬见识一下,什么叫”传统文化遇上赛博效率”

            import osimport sysfrom PyQt6.QtWidgets import (    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,    QPushButton, QLabel, QLineEdit, QTextEdit, QGroupBox, QGridLayout,    QMessageBox, QFileDialog, QRadioButton, QButtonGroup, QFrame)from PyQt6.QtCore import Qt, QThread, pyqtSignalclass CompareWorker(QThread):    """比对工作线程"""    log_signal = pyqtSignal(str)    progress_signal = pyqtSignal(intint)    finished_signal = pyqtSignal(dict)    error_signal = pyqtSignal(str)    def __init__(self, folder_a=None, folder_b=None, output_folder=None,                 file_a=None, file_b=None, output_file=None, mode='batch'):        super().__init__()        self.folder_a = folder_a        self.folder_b = folder_b        self.output_folder = output_folder        self.file_a = file_a        self.file_b = file_b        self.output_file = output_file        self.mode = mode    def run(self):        # 这里需要调用实际的比对逻辑        # 示例代码        self.log_signal.emit("开始比对...")        self.progress_signal.emit(110)        # ... 比对逻辑        self.finished_signal.emit({'success'1'failed'0'no_difference'0})# ==================== 主窗口 ====================class MainWindow(QMainWindow):    def __init__(self):        super().__init__()        self.setWindowTitle("Word文档比对工具(欢迎关注微信公众号:码海听潮)")        self.setFixedSize(900750)        # 样式设置        self.setStyleSheet("""            QMainWindow {                background-color: #f0f2f5;            }            QGroupBox {                font-weight: bold;                border: 2px solid #d0d7e3;                border-radius: 8px;                margin-top: 10px;                padding-top: 15px;                background-color: white;            }            QGroupBox::title {                subcontrol-origin: margin;                left: 15px;                padding: 0 10px;                color: #2c3e50;            }            QPushButton {                background-color: #3498db;                color: white;                border: none;                border-radius: 6px;                padding: 8px 20px;                font-weight: bold;                font-size: 13px;            }            QPushButton:hover {                background-color: #2980b9;            }            QPushButton:pressed {                background-color: #21618c;            }            QPushButton:disabled {                background-color: #bdc3c7;                color: #7f8c8d;            }            QPushButton#startBtn {                background-color: #27ae60;                font-size: 15px;                padding: 10px 30px;            }            QPushButton#startBtn:hover {                background-color: #229954;            }            QPushButton#startBtn:disabled {                background-color: #bdc3c7;            }            QPushButton#clearBtn {                background-color: #e74c3c;            }            QPushButton#clearBtn:hover {                background-color: #c0392b;            }            QRadioButton {                font-size: 13px;                color: #2c3e50;                padding: 5px;            }            QRadioButton::indicator {                width: 18px;                height: 18px;            }            QLineEdit {                border: 2px solid #d0d7e3;                border-radius: 6px;                padding: 6px 10px;                font-size: 13px;                background-color: white;            }            QLineEdit:focus {                border-color: #3498db;            }            QLineEdit:disabled {                background-color: #f0f2f5;            }            QTextEdit {                border: 2px solid #d0d7e3;                border-radius: 6px;                background-color: #1e1e1e;                color: #d4d4d4;                font-family: 'Consolas', 'Monaco', monospace;                font-size: 12px;            }            QLabel {                color: #2c3e50;            }            QFrame#separator {                background-color: #d0d7e3;                max-height: 1px;                min-height: 1px;            }        """)        # 创建中央部件        central_widget = QWidget()        self.setCentralWidget(central_widget)        main_layout = QVBoxLayout(central_widget)        main_layout.setSpacing(15)        main_layout.setContentsMargins(25202520)        # 模式选择        mode_group = QGroupBox("比对模式")        mode_layout = QHBoxLayout()        mode_layout.setContentsMargins(15101510)        self.mode_group = QButtonGroup()        self.radio_batch = QRadioButton("📁 批量比对")        self.radio_single = QRadioButton("📄 单文件比对")        self.radio_batch.setChecked(True)        self.mode_group.addButton(self.radio_batch)        self.mode_group.addButton(self.radio_single)        self.radio_batch.toggled.connect(self.on_mode_changed)        mode_layout.addWidget(self.radio_batch)        mode_layout.addWidget(self.radio_single)        mode_layout.addStretch()        mode_group.setLayout(mode_layout)        main_layout.addWidget(mode_group)        # 文件设置区域        file_group = QGroupBox("文件设置")        file_layout = QGridLayout()        file_layout.setSpacing(10)        file_layout.setContentsMargins(15151515)        # 文件夹A        self.label_a = QLabel("📁 源文件夹:")        file_layout.addWidget(self.label_a, 00)        self.folder_a_edit = QLineEdit()        self.folder_a_edit.setPlaceholderText("请选择或输入源文件夹路径")        file_layout.addWidget(self.folder_a_edit, 01)        self.btn_a = QPushButton("浏览...")        self.btn_a.clicked.connect(lambdaself.select_folder(self.folder_a_edit))        file_layout.addWidget(self.btn_a, 02)        # 文件夹B        self.label_b = QLabel("📁 目标文件夹:")        file_layout.addWidget(self.label_b, 10)        self.folder_b_edit = QLineEdit()        self.folder_b_edit.setPlaceholderText("请选择或输入目标文件夹路径")        file_layout.addWidget(self.folder_b_edit, 11)        self.btn_b = QPushButton("浏览...")        self.btn_b.clicked.connect(lambdaself.select_folder(self.folder_b_edit))        file_layout.addWidget(self.btn_b, 12)        # 分隔线        separator = QFrame()        separator.setObjectName("separator")        file_layout.addWidget(separator, 2013)        # 单文件A        self.label_file_a = QLabel("📄 文件A:")        file_layout.addWidget(self.label_file_a, 30)        self.file_a_edit = QLineEdit()        self.file_a_edit.setPlaceholderText("请选择或输入第一个Word文档路径")        self.file_a_edit.setEnabled(False)        file_layout.addWidget(self.file_a_edit, 31)        self.btn_file_a = QPushButton("浏览...")        self.btn_file_a.clicked.connect(lambdaself.select_file(self.file_a_edit))        self.btn_file_a.setEnabled(False)        file_layout.addWidget(self.btn_file_a, 32)        # 单文件B        self.label_file_b = QLabel("📄 文件B:")        file_layout.addWidget(self.label_file_b, 40)        self.file_b_edit = QLineEdit()        self.file_b_edit.setPlaceholderText("请选择或输入第二个Word文档路径")        self.file_b_edit.setEnabled(False)        file_layout.addWidget(self.file_b_edit, 41)        self.btn_file_b = QPushButton("浏览...")        self.btn_file_b.clicked.connect(lambdaself.select_file(self.file_b_edit))        self.btn_file_b.setEnabled(False)        file_layout.addWidget(self.btn_file_b, 42)        # 输出文件(单文件模式)        self.label_output = QLabel("📂 输出文件:")        file_layout.addWidget(self.label_output, 50)        self.output_file_edit = QLineEdit()        self.output_file_edit.setPlaceholderText("留空则自动生成在源文件同目录")        self.output_file_edit.setEnabled(False)        file_layout.addWidget(self.output_file_edit, 51)        self.btn_output_file = QPushButton("浏览...")        self.btn_output_file.clicked.connect(lambdaself.select_output_file())        self.btn_output_file.setEnabled(False)        file_layout.addWidget(self.btn_output_file, 52)        file_group.setLayout(file_layout)        main_layout.addWidget(file_group)        # 控制按钮        btn_layout = QHBoxLayout()        btn_layout.addStretch()        self.start_btn = QPushButton("🚀 开始比对")        self.start_btn.setObjectName("startBtn")        self.start_btn.clicked.connect(self.start_comparison)        btn_layout.addWidget(self.start_btn)        self.clear_btn = QPushButton("🗑️ 清空日志")        self.clear_btn.setObjectName("clearBtn")        self.clear_btn.clicked.connect(self.clear_log)        btn_layout.addWidget(self.clear_btn)        btn_layout.addStretch()        main_layout.addLayout(btn_layout)        # 日志输出区域        log_group = QGroupBox("日志输出")        log_layout = QVBoxLayout()        log_layout.setContentsMargins(15101510)        self.log_text = QTextEdit()        self.log_text.setReadOnly(True)        self.log_text.setMinimumHeight(400)        log_layout.addWidget(self.log_text)        log_group.setLayout(log_layout)        main_layout.addWidget(log_group)        # 工作线程        self.worker = None        # 初始化日志        self.append_log("欢迎使用Word文档比对工具!\n")        self.append_log("请选择比对模式并设置文件路径,然后点击'开始比对'\n")        self.append_log("=" * 60 + "\n")    def on_mode_changed(self):        """切换模式"""        is_batch = self.radio_batch.isChecked()        # 批量模式        self.folder_a_edit.setEnabled(is_batch)        self.folder_b_edit.setEnabled(is_batch)        self.btn_a.setEnabled(is_batch)        self.btn_b.setEnabled(is_batch)        self.label_a.setEnabled(is_batch)        self.label_b.setEnabled(is_batch)        # 单文件模式        self.file_a_edit.setEnabled(not is_batch)        self.file_b_edit.setEnabled(not is_batch)        self.btn_file_a.setEnabled(not is_batch)        self.btn_file_b.setEnabled(not is_batch)        self.label_file_a.setEnabled(not is_batch)        self.label_file_b.setEnabled(not is_batch)        self.output_file_edit.setEnabled(not is_batch)        self.btn_output_file.setEnabled(not is_batch)        self.label_output.setEnabled(not is_batch)        # 清空日志        self.clear_log()        self.append_log(f"已切换到{'批量'if is_batch else'单文件'}比对模式\n")        self.append_log("=" * 60 + "\n")    def select_folder(self, line_edit):        """选择文件夹"""        folder = QFileDialog.getExistingDirectory(            self"选择文件夹", line_edit.text() or os.path.expanduser("~")        )        if folder:            line_edit.setText(folder)    def select_file(self, line_edit):        """选择文件"""        file_path, _ = QFileDialog.getOpenFileName(            self"选择Word文档"            line_edit.text() or os.path.expanduser("~"),            "Word文档 (*.docx *.doc)"        )        if file_path:            line_edit.setText(file_path)    def select_output_file(self):        """选择输出文件"""        file_path, _ = QFileDialog.getSaveFileName(            self"保存比对结果",            os.path.expanduser("~"),            "Word文档 (*.docx)"        )        if file_path:            if not file_path.endswith('.docx'and not file_path.endswith('.doc'):                file_path += '.docx'            self.output_file_edit.setText(file_path)    def append_log(self, text):        """追加日志"""        self.log_text.append(text)        cursor = self.log_text.textCursor()        cursor.movePosition(cursor.MoveOperation.End)        self.log_text.setTextCursor(cursor)        QApplication.processEvents()    def clear_log(self):        """清空日志"""        self.log_text.clear()    def start_comparison(self):        """开始比对"""        is_batch = self.radio_batch.isChecked()        if is_batch:            folder_a = self.folder_a_edit.text().strip()            folder_b = self.folder_b_edit.text().strip()            if not folder_a or not folder_b:                QMessageBox.warning(self"警告""请选择源文件夹和目标文件夹")                return            if not os.path.exists(folder_a):                QMessageBox.warning(self"警告"f"源文件夹不存在: {folder_a}")                return            if not os.path.exists(folder_b):                QMessageBox.warning(self"警告"f"目标文件夹不存在: {folder_b}")                return            self.set_controls_enabled(False)            self.log_text.clear()            self.append_log("🚀 开始批量比对...\n")            self.append_log(f"📁 源文件夹: {folder_a}")            self.append_log(f"📁 目标文件夹: {folder_b}")            self.append_log("-" * 60 + "\n")            self.worker = CompareWorker(                folder_a=folder_a,                folder_b=folder_b,                mode='batch'            )        else:            file_a = self.file_a_edit.text().strip()            file_b = self.file_b_edit.text().strip()            output_file = self.output_file_edit.text().strip() or None            if not file_a or not file_b:                QMessageBox.warning(self"警告""请选择两个Word文档")                return            if not os.path.exists(file_a):                QMessageBox.warning(self"警告"f"文件不存在: {file_a}")                return            if not os.path.exists(file_b):                QMessageBox.warning(self"警告"f"文件不存在: {file_b}")                return            if not file_a.lower().endswith(('.docx''.doc')):                QMessageBox.warning(self"警告""请选择Word文档 (.docx 或 .doc)")                return            if not file_b.lower().endswith(('.docx''.doc')):                QMessageBox.warning(self"警告""请选择Word文档 (.docx 或 .doc)")                return            self.set_controls_enabled(False)            self.log_text.clear()            self.append_log("🚀 开始单文件比对...\n")            self.append_log(f"📄 文件A: {file_a}")            self.append_log(f"📄 文件B: {file_b}")            if output_file:                self.append_log(f"📂 输出文件: {output_file}")            else:                self.append_log(f"📂 输出文件: 自动生成")            self.append_log("-" * 60 + "\n")            self.worker = CompareWorker(                file_a=file_a,                file_b=file_b,                output_file=output_file,                mode='single'            )        self.worker.log_signal.connect(self.append_log)        self.worker.progress_signal.connect(self.update_progress)        self.worker.finished_signal.connect(self.on_finished)        self.worker.error_signal.connect(self.on_error)        self.worker.start()    def set_controls_enabled(self, enabled):        """启用/禁用控件"""        self.start_btn.setEnabled(enabled)        self.clear_btn.setEnabled(enabled)        self.radio_batch.setEnabled(enabled)        self.radio_single.setEnabled(enabled)        is_batch = self.radio_batch.isChecked()        self.folder_a_edit.setEnabled(enabled and is_batch)        self.folder_b_edit.setEnabled(enabled and is_batch)        self.btn_a.setEnabled(enabled and is_batch)        self.btn_b.setEnabled(enabled and is_batch)        self.file_a_edit.setEnabled(enabled and not is_batch)        self.file_b_edit.setEnabled(enabled and not is_batch)        self.btn_file_a.setEnabled(enabled and not is_batch)        self.btn_file_b.setEnabled(enabled and not is_batch)        self.output_file_edit.setEnabled(enabled and not is_batch)        self.btn_output_file.setEnabled(enabled and not is_batch)    def update_progress(self, current, total):        """更新进度"""        pass    def on_finished(self, stats):        """比对完成"""        self.set_controls_enabled(True)        self.start_btn.setEnabled(True)        self.append_log("\n" + "=" * 60)        if self.radio_single.isChecked():            self.append_log("📊 单文件比对结果")            self.append_log("=" * 60)            self.append_log(f"✅ 有差异并保存: {stats.get('success'0)}")            self.append_log(f"ℹ️  无差异: {stats.get('no_difference'0)}")            self.append_log(f"❌ 比对失败: {stats.get('failed'0)}")        else:            self.append_log("📊 批量比对完成统计")            self.append_log("=" * 60)            self.append_log(f"📄 总文档对数: {stats.get('total_pairs'0)}")            self.append_log(f"✅ 有差异并保存: {stats.get('success'0)}")            self.append_log(f"ℹ️  无差异: {stats.get('no_difference'0)}")            self.append_log(f"❌ 比对失败: {stats.get('failed'0)}")        self.append_log("=" * 60)    def on_error(self, error_msg):        """处理错误"""        self.set_controls_enabled(True)        self.start_btn.setEnabled(True)        self.append_log(f"\n❌ 错误: {error_msg}")        QMessageBox.critical(self"错误"f"比对过程中发生错误:\n\n{error_msg}")# ==================== 主程序入口 ====================def main():    app = QApplication(sys.argv)    app.setStyle('Fusion')    window = MainWindow()    window.show()    sys.exit(app.exec())if __name__ == "__main__":    main()

            这款Word文档差异智能比对工具,可自动完成原需手动逐字逐句对比、反复翻阅查找差异一整天的文档比对工作。从最初打开两个文档来回切换、肉眼逐行扫描差异、手动标注修订、再到反复确认修改内容的繁琐与崩溃,到用自动化工具实现一键比对、差异自动高亮标注的畅快,工作效率获得指数级提升,终于解放了双手和时间!

            大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

            《该办公工具的应用场景:》

            • 合同版本比对

            • 标书修改审核

            • 论文查重修订

            • 文档版本管理

            • 多人协作内容合并审查

            结语

            当Python遇见办公,牛马打工人终于笑出了猪叫声

            【职场人必看】每天早上一睁眼,想到又要面对:

            1.📊 堆积如山的Excel表格

            2.📑 机械重复的复制粘贴

            3.✍️ 永远改不完的各类文档

            4.诸如此类的更多……..

            是不是连Ctrl+Alt+Delete的心都有了?

            别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!

            感谢各位大佬观看,还望各位大佬抬抬贵手一键三连,多多关注点赞转发评论,大佬们的支持才是摸鱼孜孜不倦更新原创干货的动力!