Python实现一键PDF转Word工具
下面我将实现一个完整的PDF转Word工具,支持图形界面操作,可以预览PDF页面并选择转换范围。
解决方案思路
-
使用
PyMuPDF
处理PDF文件(读取、渲染页面预览) -
使用
pdf2docx
库进行PDF到Word的转换 -
使用
tkinter
构建图形界面 -
实现预览功能,让用户可以选择要转换的页面范围
完整代码
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页)
-
使用页面顶部的按钮选择要转换的页面(或使用范围输入框)
-
点击”开始转换”按钮执行转换操作
-
功能特点
-
直观的图形界面:使用tkinter创建用户友好的界面
-
PDF预览功能:可以查看PDF的前10页内容
-
灵活的页面选择:
-
支持选择全部页面
-
支持通过按钮选择特定页面
-
支持输入页面范围(如1-3,5,7-9)
-
-
状态反馈:显示转换进度和结果
-
多线程处理:转换过程在后台线程进行,不会冻结界面
注意事项
-
对于扫描版的PDF(图片格式),转换效果可能不佳
-
复杂格式的PDF(如包含特殊字体、复杂表格)可能无法完美转换
-
转换大型PDF文件可能需要较长时间
这个工具提供了基本的PDF转Word功能,界面简洁易用,适合日常办公使用。对于更复杂的需求,可以考虑使用专业软件或在线转换服务。
© 版权声明
本站资源来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!
THE END