前言:日常生活中,我们常常会跟WPS Office打交道。作表格,写报告,写PPT......可以说,我们的生活已经离不开WPS Office了。与此同时,我们在这个过程中也会遇到各种各样的技术阻碍,例如部分软件的PDF转Word需要收取额外费用等。那么,可不可以自己开发一个小工具来实现PDF转Word这个功能呢?答案是肯定的,Python生来就是为应用层开发的。话不多说,我们直接开始今天的Python学习之路-利用Python编写一个基于PyQt5,pdf2docx,pathlib,sys,os,datetimePDF转Word工具。

 编程思路:本次编程我们将会调用到PyQt5,pdf2docx,pathlib,datetime,sys,os等库。其中,PyQt5提供了图形用户交互界面,线程池,信号槽等槽点。pdf2docx则负责将PDF文档转换为Word文档。pathlib获取生成的Word文档的绝对路径,并保证Word文档存放在系统的指定位置。datetime库用来获取系统当前的时间参数,并将其以时间戳的形式给生成的Word文档命名,这对用户后面方便搜索和查询文件详细信息非常重要。sys使我们与Python解释器(PyCharm)进行交互、以及控制程序的执行成为了可能。本次编程中,os库被用于启动文件浏览器(os.startfile),以便我们选择要转换的 PDF 文件。具体来说,我们可以用它在 Windows 系统上打开所想要转换的PDF的所在文件或目录。本次,我们还会将gif动画导入GUI界面,让用户的体验感更好。

第一步:导入库

标准库:pathlib,sys,os,datetime。

第三方库:PyQt5,pdf2docx。

# 导入库
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QMovie  # 导入QMovie类
from pdf2docx import Converter
from pathlib import Path
from datetime import datetime

 

第二步:建立功能类

这一步中我们需要建立两个类:1,线程进度类  2,转换窗口类。

线程进度类:负责PDF文档从PDF格式转换为Word格式的目录创建,格式转换,新文件生成等步骤的执行。

转换窗口类:负责PDF文档转Word文档过程中GUI各控件(按钮,主窗口,进度条,信息框等)的布局。

本次我们将动态进度条改为了固定的文字显示,以免用户被系统报错而停滞的进度条误导。

# 线程进度类
class ConversionThread(QThread):
    progress_updated = pyqtSignal(str)  # 修改为仅传递描述信息
    conversion_finished = pyqtSignal(bool, str)

    # 线程初始化
    def __init__(self, pdf_path, output_dir):
        super().__init__()
        self.pdf_path = pdf_path
        self.output_dir = output_dir
        self.docx_path = ""

    def run(self):
        try:
            # 创建输出目录
            output_path = Path(self.output_dir)
            output_path.mkdir(parents=True, exist_ok=True)

            # 生成带时间戳的文件名
            timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            original_name = Path(self.pdf_path).stem
            self.docx_path = str(output_path / f"{original_name}_{timestamp}.docx")

            # 初始化转换器
            cv = Converter(self.pdf_path)

            # 获取总页数用于进度计算
            total_pages = len(cv.pages)
            current_page = 0

            def detailed_progress(progress, description):
                nonlocal current_page
                if description["event"] == "page_parsed":
                    current_page += 1
                    self.progress_updated.emit(
                        f"正在转换,请耐心等待...(已处理 {current_page}/{total_pages} 页)"
                    )
                elif description["event"] == "created":
                    self.progress_updated.emit(
                        "正在生成Word文档..."
                    )

            # 执行转换
            cv.convert(self.docx_path, progress_callback=detailed_progress)
            cv.close()

            self.conversion_finished.emit(True, "转换成功!")
        except Exception as e:
            self.conversion_finished.emit(False, f"转换失败: {str(e)}")
        finally:
            self.progress_updated.emit("")

# 转换窗口类
class PDFToWordConverter(QMainWindow):

    # 窗口初始化
    def __init__(self):
        super().__init__()
        self.initUI()
        self.conversion_thread = None
        self.output_dir = str(Path.home() / "Desktop" / "PDF转换结果")

    def initUI(self):
        self.setWindowTitle('PDF转Word工具(专业版)')
        self.setGeometry(300, 300, 600, 250)  # 扩大窗口尺寸

        # 创建控件
        self.pdf_path_label = QLabel('PDF文件路径:')
        self.pdf_path_edit = QLineEdit()
        self.pdf_browse_btn = QPushButton('浏览...')

        self.convert_btn = QPushButton('开始转换')

        # 替换进度条为固定文字提示
        self.progress_label = QLabel('准备就绪')
        self.progress_label.setAlignment(Qt.AlignCenter)

        self.status_label = QLabel('')
        self.status_label.setAlignment(Qt.AlignCenter)

        # 添加GIF动画
        self.loading_label = QLabel(self)
        self.movie = QMovie("loading.gif")  # 加载GIF文件
        self.loading_label.setMovie(self.movie)
        self.loading_label.setAlignment(Qt.AlignCenter)
        self.loading_label.hide()  # 初始隐藏

        # 布局设置
        layout = QVBoxLayout()

        # PDF文件选择
        pdf_layout = QHBoxLayout()
        pdf_layout.addWidget(self.pdf_path_label)
        pdf_layout.addWidget(self.pdf_path_edit)
        pdf_layout.addWidget(self.pdf_browse_btn)

        # 添加控件到主布局
        layout.addLayout(pdf_layout)
        layout.addWidget(self.progress_label)  # 显示固定文字提示
        layout.addWidget(self.status_label)
        layout.addWidget(self.convert_btn)
        layout.addWidget(self.loading_label)  # 添加GIF动画

        # 设置中央窗口
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # 连接信号和槽
        self.pdf_browse_btn.clicked.connect(self.browse_pdf)
        self.convert_btn.clicked.connect(self.start_conversion)

    def browse_pdf(self):
        path, _ = QFileDialog.getOpenFileName(self, "选择PDF文件", "", "PDF文件 (*.pdf)")
        if path:
            self.pdf_path_edit.setText(path)

    def start_conversion(self):
        pdf_path = self.pdf_path_edit.text()

        if not pdf_path:
            QMessageBox.warning(self, "警告", "请先选择PDF文件!")
            return

        if self.conversion_thread and self.conversion_thread.isRunning():
            QMessageBox.warning(self, "警告", "当前正在转换中,请稍候!")
            return

        # 重置界面状态
        self.progress_label.setText('正在转换,请耐心等待...')
        self.status_label.setText("")

        # 显示GIF动画
        self.loading_label.show()
        self.movie.start()

        # 创建并启动转换线程
        self.conversion_thread = ConversionThread(pdf_path, self.output_dir)
        self.conversion_thread.progress_updated.connect(self.update_progress)
        self.conversion_thread.conversion_finished.connect(self.conversion_complete)
        self.conversion_thread.start()

        self.convert_btn.setEnabled(False)

    def update_progress(self, description):
        # 更新进度文字
        if description:
            self.progress_label.setText(description)

    def conversion_complete(self, success, message):
        self.convert_btn.setEnabled(True)
        self.status_label.setText(message)

        # 停止并隐藏GIF动画
        self.movie.stop()
        self.loading_label.hide()

        if success:
            save_path = self.conversion_thread.docx_path
            reply = QMessageBox.question(
                self, '转换完成',
                f"{message}\n文件已保存到:\n{save_path}\n\n是否要打开所在文件夹?",
                QMessageBox.Yes | QMessageBox.No
            )
            if reply == QMessageBox.Yes:
                os.startfile(os.path.dirname(save_path))
        else:
            QMessageBox.critical(self, "错误", message)

第三步:构建驱动单元

同前一期一样,接下来我们也需要编写一个驱动单元来执行以上所有类。

# 驱动程序单元
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = PDFToWordConverter()
    ex.show()
    sys.exit(app.exec_())

第四步:完整代码展示

# 导入库
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QMovie  # 导入QMovie类
from pdf2docx import Converter
from pathlib import Path
from datetime import datetime

# 线程进度类
class ConversionThread(QThread):
    progress_updated = pyqtSignal(str)  # 修改为仅传递描述信息
    conversion_finished = pyqtSignal(bool, str)

    # 线程初始化
    def __init__(self, pdf_path, output_dir):
        super().__init__()
        self.pdf_path = pdf_path
        self.output_dir = output_dir
        self.docx_path = ""

    def run(self):
        try:
            # 创建输出目录
            output_path = Path(self.output_dir)
            output_path.mkdir(parents=True, exist_ok=True)

            # 生成带时间戳的文件名
            timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            original_name = Path(self.pdf_path).stem
            self.docx_path = str(output_path / f"{original_name}_{timestamp}.docx")

            # 初始化转换器
            cv = Converter(self.pdf_path)

            # 获取总页数用于进度计算
            total_pages = len(cv.pages)
            current_page = 0

            def detailed_progress(progress, description):
                nonlocal current_page
                if description["event"] == "page_parsed":
                    current_page += 1
                    self.progress_updated.emit(
                        f"正在转换,请耐心等待...(已处理 {current_page}/{total_pages} 页)"
                    )
                elif description["event"] == "created":
                    self.progress_updated.emit(
                        "正在生成Word文档..."
                    )

            # 执行转换
            cv.convert(self.docx_path, progress_callback=detailed_progress)
            cv.close()

            self.conversion_finished.emit(True, "转换成功!")
        except Exception as e:
            self.conversion_finished.emit(False, f"转换失败: {str(e)}")
        finally:
            self.progress_updated.emit("")

# 转换窗口类
class PDFToWordConverter(QMainWindow):

    # 窗口初始化
    def __init__(self):
        super().__init__()
        self.initUI()
        self.conversion_thread = None
        self.output_dir = str(Path.home() / "Desktop" / "PDF转换结果")

    def initUI(self):
        self.setWindowTitle('PDF转Word工具(专业版)')
        self.setGeometry(300, 300, 600, 250)  # 扩大窗口尺寸

        # 创建控件
        self.pdf_path_label = QLabel('PDF文件路径:')
        self.pdf_path_edit = QLineEdit()
        self.pdf_browse_btn = QPushButton('浏览...')

        self.convert_btn = QPushButton('开始转换')

        # 替换进度条为固定文字提示
        self.progress_label = QLabel('准备就绪')
        self.progress_label.setAlignment(Qt.AlignCenter)

        self.status_label = QLabel('')
        self.status_label.setAlignment(Qt.AlignCenter)

        # 添加GIF动画
        self.loading_label = QLabel(self)
        self.movie = QMovie("loading.gif")  # 加载GIF文件
        self.loading_label.setMovie(self.movie)
        self.loading_label.setAlignment(Qt.AlignCenter)
        self.loading_label.hide()  # 初始隐藏

        # 布局设置
        layout = QVBoxLayout()

        # PDF文件选择
        pdf_layout = QHBoxLayout()
        pdf_layout.addWidget(self.pdf_path_label)
        pdf_layout.addWidget(self.pdf_path_edit)
        pdf_layout.addWidget(self.pdf_browse_btn)

        # 添加控件到主布局
        layout.addLayout(pdf_layout)
        layout.addWidget(self.progress_label)  # 显示固定文字提示
        layout.addWidget(self.status_label)
        layout.addWidget(self.convert_btn)
        layout.addWidget(self.loading_label)  # 添加GIF动画

        # 设置中央窗口
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # 连接信号和槽
        self.pdf_browse_btn.clicked.connect(self.browse_pdf)
        self.convert_btn.clicked.connect(self.start_conversion)

    def browse_pdf(self):
        path, _ = QFileDialog.getOpenFileName(self, "选择PDF文件", "", "PDF文件 (*.pdf)")
        if path:
            self.pdf_path_edit.setText(path)

    def start_conversion(self):
        pdf_path = self.pdf_path_edit.text()

        if not pdf_path:
            QMessageBox.warning(self, "警告", "请先选择PDF文件!")
            return

        if self.conversion_thread and self.conversion_thread.isRunning():
            QMessageBox.warning(self, "警告", "当前正在转换中,请稍候!")
            return

        # 重置界面状态
        self.progress_label.setText('正在转换,请耐心等待...')
        self.status_label.setText("")

        # 显示GIF动画
        self.loading_label.show()
        self.movie.start()

        # 创建并启动转换线程
        self.conversion_thread = ConversionThread(pdf_path, self.output_dir)
        self.conversion_thread.progress_updated.connect(self.update_progress)
        self.conversion_thread.conversion_finished.connect(self.conversion_complete)
        self.conversion_thread.start()

        self.convert_btn.setEnabled(False)

    def update_progress(self, description):
        # 更新进度文字
        if description:
            self.progress_label.setText(description)

    def conversion_complete(self, success, message):
        self.convert_btn.setEnabled(True)
        self.status_label.setText(message)

        # 停止并隐藏GIF动画
        self.movie.stop()
        self.loading_label.hide()

        if success:
            save_path = self.conversion_thread.docx_path
            reply = QMessageBox.question(
                self, '转换完成',
                f"{message}\n文件已保存到:\n{save_path}\n\n是否要打开所在文件夹?",
                QMessageBox.Yes | QMessageBox.No
            )
            if reply == QMessageBox.Yes:
                os.startfile(os.path.dirname(save_path))
        else:
            QMessageBox.critical(self, "错误", message)

# 驱动程序单元
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = PDFToWordConverter()
    ex.show()
    sys.exit(app.exec_())

第五步:运行效果展示

点击"Yes":

点击"No":

第六步:注意事项

- 转换效果取决于PDF文件的复杂程度(特别是包含复杂表格和样式的PDF可能无法完美转换)
- 转换大文件可能需要较长时间
- 转换后的文档可能需要手动调整格式
- 不支持批量转换(大家可以自行扩展)
- 不支持PDF扫描件(图片型PDF需要OCR处理)

(我是闪云-微星,感谢你的点赞/关注)

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐