Python实现一键PDF转Word工具代码

Python实现一键PDF转Word工具

下面我将实现一个完整的PDF转Word工具,支持图形界面操作,可以预览PDF页面并选择转换范围。

解决方案思路

  1. 使用PyMuPDF处理PDF文件(读取、渲染页面预览)

  2. 使用pdf2docx库进行PDF到Word的转换

  3. 使用tkinter构建图形界面

  4. 实现预览功能,让用户可以选择要转换的页面范围

完整代码

import os
import fitz  # PyMuPDF
from pdf2docx import Converter
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from PIL import Image, ImageTk
import threading

class PDFtoWordConverter:
    def __init__(self, root):
        self.root = root
        self.root.title("PDF转Word工具 v1.0")
        self.root.geometry("800x600")
        self.root.configure(bg="#f0f0f0")
        
        # 设置应用图标(需要图标文件)
        try:
            self.root.iconbitmap("pdf_icon.ico")
        except:
            pass
        
        # 初始化变量
        self.pdf_path = ""
        self.output_path = ""
        self.preview_images = []
        self.total_pages = 0
        self.selected_pages = []
        
        # 创建UI
        self.create_widgets()
        
    def create_widgets(self):
        # 创建样式
        style = ttk.Style()
        style.configure("TButton", padding=6, font=("Microsoft YaHei", 10))
        style.configure("TLabel", background="#f0f0f0", font=("Microsoft YaHei", 10))
        style.configure("TFrame", background="#f0f0f0")
        
        # 顶部控制区域
        control_frame = ttk.Frame(self.root)
        control_frame.pack(fill=tk.X, padx=10, pady=10)
        
        # 文件选择按钮
        ttk.Button(control_frame, text="选择PDF文件", command=self.select_pdf).grid(row=0, column=0, padx=5, pady=5)
        self.pdf_label = ttk.Label(control_frame, text="未选择文件")
        self.pdf_label.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
        
        # 输出路径选择
        ttk.Button(control_frame, text="选择输出位置", command=self.select_output).grid(row=1, column=0, padx=5, pady=5)
        self.output_label = ttk.Label(control_frame, text="未选择输出位置")
        self.output_label.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
        
        # 转换按钮
        self.convert_btn = ttk.Button(control_frame, text="开始转换", command=self.start_conversion, state=tk.DISABLED)
        self.convert_btn.grid(row=0, column=2, rowspan=2, padx=10, pady=5)
        
        # 页面预览区域
        preview_frame = ttk.LabelFrame(self.root, text="PDF预览 - 选择要转换的页面")
        preview_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
        
        # 创建画布和滚动条
        self.canvas = tk.Canvas(preview_frame, bg="white")
        self.scrollbar = ttk.Scrollbar(preview_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = ttk.Frame(self.canvas)
        
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )
        
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        
        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")
        
        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 页面选择控制
        page_control = ttk.Frame(self.root)
        page_control.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(page_control, text="页面范围:").pack(side=tk.LEFT)
        
        self.all_pages_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(page_control, text="全部页面", variable=self.all_pages_var,
                       command=self.toggle_page_selection).pack(side=tk.LEFT, padx=5)
        
        self.range_label = ttk.Label(page_control, text="选择页面 (例如: 1-3,5,7-9):")
        self.range_label.pack(side=tk.LEFT, padx=(20, 5))
        
        self.range_entry = ttk.Entry(page_control, width=20, state=tk.DISABLED)
        self.range_entry.pack(side=tk.LEFT, padx=5)
        
        self.page_count_label = ttk.Label(page_control, text="总页数: 0")
        self.page_count_label.pack(side=tk.RIGHT, padx=10)
        
    def select_pdf(self):
        file_path = filedialog.askopenfilename(
            filetypes=[("PDF文件", "*.pdf"), ("所有文件", "*.*")]
        )
        if file_path:
            self.pdf_path = file_path
            self.pdf_label.config(text=os.path.basename(file_path))
            self.load_pdf_preview()
            self.convert_btn.config(state=tk.NORMAL)
    
    def select_output(self):
        output_path = filedialog.asksaveasfilename(
            defaultextension=".docx",
            filetypes=[("Word文档", "*.docx"), ("所有文件", "*.*")]
        )
        if output_path:
            self.output_path = output_path
            self.output_label.config(text=os.path.basename(output_path))
    
    def toggle_page_selection(self):
        if self.all_pages_var.get():
            self.range_entry.config(state=tk.DISABLED)
            self.range_label.config(foreground="gray")
        else:
            self.range_entry.config(state=tk.NORMAL)
            self.range_label.config(foreground="black")
    
    def load_pdf_preview(self):
        # 清除之前的预览
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
        
        self.preview_images = []
        self.selected_pages = []
        
        if not self.pdf_path:
            return
        
        try:
            doc = fitz.open(self.pdf_path)
            self.total_pages = doc.page_count
            self.page_count_label.config(text=f"总页数: {self.total_pages}")
            
            # 创建页面选择按钮
            page_btns_frame = ttk.Frame(self.scrollable_frame)
            page_btns_frame.pack(fill=tk.X, pady=5)
            
            for page_num in range(1, self.total_pages + 1):
                btn = ttk.Button(page_btns_frame, text=str(page_num), width=3,
                                command=lambda p=page_num: self.toggle_page_selection(p))
                btn.pack(side=tk.LEFT, padx=2)
                self.selected_pages.append(page_num)
            
            # 渲染页面预览
            for page_num in range(min(10, self.total_pages)):  # 最多预览10页
                page = doc.load_page(page_num)
                pix = page.get_pixmap(matrix=fitz.Matrix(150/72, 150/72))
                img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
                img.thumbnail((700, 500))  # 调整预览大小
                photo = ImageTk.PhotoImage(img)
                
                # 保存引用,防止被垃圾回收
                self.preview_images.append(photo)
                
                # 创建页面预览
                page_frame = ttk.LabelFrame(self.scrollable_frame, text=f"第 {page_num+1} 页")
                page_frame.pack(fill=tk.X, padx=10, pady=5)
                
                label = ttk.Label(page_frame, image=photo)
                label.image = photo  # 保持引用
                label.pack(padx=10, pady=10)
            
            doc.close()
            self.status_var.set(f"已加载PDF预览 - 共{self.total_pages}页")
        except Exception as e:
            messagebox.showerror("错误", f"无法打开PDF文件: {str(e)}")
            self.status_var.set("加载PDF失败")
    
    def toggle_page_selection(self, page_num):
        if page_num in self.selected_pages:
            self.selected_pages.remove(page_num)
        else:
            self.selected_pages.append(page_num)
        self.selected_pages.sort()
    
    def parse_page_range(self, range_str):
        """解析页面范围字符串"""
        pages = []
        parts = range_str.split(',')
        
        for part in parts:
            if '-' in part:
                start, end = part.split('-')
                try:
                    start = int(start.strip())
                    end = int(end.strip())
                    pages.extend(range(start, end+1))
                except:
                    pass
            else:
                try:
                    pages.append(int(part.strip()))
                except:
                    pass
        
        # 过滤无效页码
        pages = [p for p in pages if 1 <= p <= self.total_pages]
        return sorted(set(pages))  # 去重并排序
    
    def get_pages_to_convert(self):
        """获取要转换的页面列表"""
        if self.all_pages_var.get():
            return list(range(1, self.total_pages + 1))
        
        range_str = self.range_entry.get().strip()
        if range_str:
            return self.parse_page_range(range_str)
        
        return self.selected_pages
    
    def start_conversion(self):
        if not self.pdf_path:
            messagebox.showwarning("警告", "请先选择PDF文件")
            return
        if not self.output_path:
            messagebox.showwarning("警告", "请选择输出位置")
            return
        
        pages = self.get_pages_to_convert()
        if not pages:
            messagebox.showwarning("警告", "请选择要转换的页面")
            return
        
        # 禁用按钮,防止重复点击
        self.convert_btn.config(state=tk.DISABLED)
        self.status_var.set(f"正在转换... (共{len(pages)}页)")
        
        # 在新线程中执行转换,防止界面冻结
        threading.Thread(target=self.convert_pdf, args=(pages,), daemon=True).start()
    
    def convert_pdf(self, pages):
        try:
            # 创建转换器对象
            cv = Converter(self.pdf_path)
            
            # 设置转换参数
            cv.convert(self.output_path, pages=pages)
            cv.close()
            
            # 更新UI
            self.root.after(0, lambda: self.status_var.set(f"转换完成!已保存到: {self.output_path}"))
            self.root.after(0, lambda: messagebox.showinfo("成功", "PDF转换完成!"))
            self.root.after(0, lambda: self.convert_btn.config(state=tk.NORMAL))
        except Exception as e:
            self.root.after(0, lambda: self.status_var.set(f"转换失败: {str(e)}"))
            self.root.after(0, lambda: messagebox.showerror("错误", f"转换失败: {str(e)}"))
            self.root.after(0, lambda: self.convert_btn.config(state=tk.NORMAL))

if __name__ == "__main__":
    root = tk.Tk()
    app = PDFtoWordConverter(root)
    root.mainloop()

使用说明

安装依赖库

pip install PyMuPDF pdf2docx pillow

 

运行程序
执行上面的Python脚本,将打开图形界面

操作步骤

    • 点击”选择PDF文件”按钮选择要转换的PDF

    • 点击”选择输出位置”按钮指定Word文档保存路径

    • 在预览区域查看PDF页面(最多显示前10页)

    • 使用页面顶部的按钮选择要转换的页面(或使用范围输入框)

    • 点击”开始转换”按钮执行转换操作

功能特点

  1. 直观的图形界面:使用tkinter创建用户友好的界面

  2. PDF预览功能:可以查看PDF的前10页内容

  3. 灵活的页面选择

    • 支持选择全部页面

    • 支持通过按钮选择特定页面

    • 支持输入页面范围(如1-3,5,7-9)

  4. 状态反馈:显示转换进度和结果

  5. 多线程处理:转换过程在后台线程进行,不会冻结界面

注意事项

  1. 对于扫描版的PDF(图片格式),转换效果可能不佳

  2. 复杂格式的PDF(如包含特殊字体、复杂表格)可能无法完美转换

  3. 转换大型PDF文件可能需要较长时间

这个工具提供了基本的PDF转Word功能,界面简洁易用,适合日常办公使用。对于更复杂的需求,可以考虑使用专业软件或在线转换服务。

© 版权声明
THE END
喜欢就支持一下吧
点赞11赞赏 分享