PDF 转成图片
PDF 转成图片
依赖库:pdf2image,(内部调用 poppler,所以需要安装 poppler)
https://github.com/oschwartz10612/poppler-windows/releases/
- 但是 poppler 的 bin 目录添加环境变量,程序也无法察觉到 ===> pdf2image 不会自动搜索系统 PATH(这是它的设计缺陷)
- 所以在程序中,手动指定 path
pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=r'D:\Release-25.11.0-0\poppler-25.11.0\Library\bin')
完整程序
import os
import threading
from tkinter import Tk, Label, Button, Entry, filedialog, StringVar, ttk, messagebox
from pdf2image import convert_from_path
def select_pdf():
path = filedialog.askopenfilename(
title="选择 PDF 文件",
filetypes=[("PDF 文件", "*.pdf")]
)
pdf_path_var.set(path)
def select_output_dir():
path = filedialog.askdirectory(title="选择输出目录")
output_dir_var.set(path)
def convert_pdf():
pdf_path = pdf_path_var.get()
output_dir = output_dir_var.get()
dpi = dpi_var.get()
fmt = format_var.get().lower()
if not pdf_path:
messagebox.showerror("错误", "请选择 PDF 文件")
return
if not output_dir:
messagebox.showerror("错误", "请选择输出目录")
return
try:
dpi = int(dpi)
except:
messagebox.showerror("错误", "DPI 必须是整数")
return
# 启动异步线程执行转换(防止 GUI 卡死)
threading.Thread(target=convert_worker, args=(pdf_path, output_dir, dpi, fmt), daemon=True).start()
def convert_worker(pdf_path, output_dir, dpi, fmt):
progress_bar["value"] = 0
progress_label.config(text="正在加载 PDF...")
try:
pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=r'D:\Release-25.11.0-0\poppler-25.11.0\Library\bin')
except Exception as e:
print(pdf_path)
print("=======")
messagebox.showerror("错误", f"PDF 读取失败:\n{e}")
return
total = len(pages)
progress_bar["maximum"] = total
try:
for i, page in enumerate(pages):
filename = os.path.join(output_dir, f"page_{i+1}.{fmt}")
page.save(filename, fmt.upper())
progress_bar["value"] = i + 1
progress_label.config(text=f"正在转换第 {i+1}/{total} 页...")
except Exception as e:
messagebox.showerror("错误", f"转换失败:\n{e}")
return
progress_label.config(text="完成!")
messagebox.showinfo("完成", f"全部页面已成功导出至:\n{output_dir}")
# GUI -----------------------------
root = Tk()
root.title("PDF 分页保存为图片转换器")
root.geometry("500x350")
Label(root, text="选择 PDF 文件:").pack()
pdf_path_var = StringVar()
Entry(root, textvariable=pdf_path_var, width=60).pack()
Button(root, text="浏览...", command=select_pdf).pack(pady=5)
Label(root, text="选择输出目录:").pack()
output_dir_var = StringVar()
Entry(root, textvariable=output_dir_var, width=60).pack()
Button(root, text="浏览...", command=select_output_dir).pack(pady=5)
Label(root, text="DPI(清晰度,默认 200):").pack()
dpi_var = StringVar(value="200")
Entry(root, textvariable=dpi_var, width=10).pack()
Label(root, text="输出图片格式:").pack()
format_var = StringVar(value="PNG")
ttk.Combobox(root, textvariable=format_var, values=["PNG", "JPG"], width=10).pack()
# 进度条
progress_label = Label(root, text="等待开始...")
progress_label.pack(pady=10)
progress_bar = ttk.Progressbar(root, length=350)
progress_bar.pack()
Button(root, text="开始转换", command=convert_pdf, width=20).pack(pady=15)
root.mainloop()
打包成 exe
难点:码依赖于 pdf2image,而 pdf2image 又依赖于外部程序 Poppler,所以打包过程需要特别注意如何包含 Poppler。
1. 定位 Poppler 路径
为了让打包后的 .exe 文件在任何电脑上都能运行,您需要将 Poppler 的相关动态链接库(DLLs) 和可执行文件(如 pdftoppm.exe, pdftocairo.exe 等)包含到您的可执行文件或其生成的目录中。
推荐做法:
- 将 Poppler 的 bin 文件夹(包含 DLLs 和 EXE 文件)拷贝到您的 Python 脚本(your_script_name.py)所在的目录下,并将其命名为,例如,poppler_bin。
- 修改代码,使其在打包后能找到 Poppler。
修改后的代码片段(convert_worker 函数):
由于打包后文件的结构会变化,最好的方法是让程序去寻找一个相对路径的 Poppler 目录。在 PyInstaller 打包的程序中,运行时文件的临时路径可以通过 sys._MEIPASS 获取,但为了简化,我们可以让它查找程序运行目录下的 poppler_bin 文件夹。
将硬编码路径替换为相对路径:
import os
import threading
from tkinter import Tk, Label, Button, Entry, filedialog, StringVar, ttk, messagebox
from pdf2image import convert_from_path
import sys # 导入 sys 模块
# ... (其他函数保持不变)
def convert_worker(pdf_path, output_dir, dpi, fmt):
progress_bar["value"] = 0
progress_label.config(text="正在加载 PDF...")
# ----------------------------------------------------
# 修改 Poppler 路径处理
if getattr(sys, 'frozen', False):
# 如果是打包后的 exe,Poppler 文件夹应该放在程序运行目录下
# 这里假设您将 Poppler bin 文件夹重命名为 'poppler_bin' 放在程序根目录
base_path = os.path.dirname(sys.executable)
else:
# 如果是直接运行 Python 脚本,则使用原始路径(或相对路径)
# 在 Python 脚本中:
# __file__ 这个内置变量(magic variable)总是指向当前被执行的 Python 模块(文件)的完整路径(包括文件名)。
# os.path.dirname(...) 的作用: 获取给定路径的目录部分。
# 所以,base_path 最终获取的就是 当前正在运行的 Python 脚本文件所在的目录。
base_path = os.path.dirname(os.path.abspath(__file__))
# 构建 Poppler 的 bin 目录路径
poppler_bin_path = os.path.join(base_path, 'poppler_bin')
# 检查路径是否存在
if not os.path.exists(poppler_bin_path):
messagebox.showerror("错误", f"Poppler 路径未找到:\n{poppler_bin_path}\n请确保 'poppler_bin' 文件夹存在于程序运行目录。")
return
# ----------------------------------------------------
try:
# 使用修改后的 poppler_bin_path
pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=poppler_bin_path)
except Exception as e:
# ... (错误处理)
# ...
2. 执行 PyInstaller 命令
pyinstaller --onefile --noconsole --name "PDFToImageConverter" pdf_converter.py
- --onefile: 将所有内容打包成一个单独的 .exe 文件(文件较大,但方便分发)。
- --noconsole 或 --windowed: 对于 GUI 程序,强烈建议使用,可以防止在运行程序时弹出一个黑色命令行窗口。
- --name "PDFToImageConverter": 设置最终 .exe 文件的名称。
3. 最终部署
执行 PyInstaller 后,会在您的目录下生成两个文件夹:build 和 dist。
最终的可执行文件位于 dist 文件夹内。
部署文件结构:
将以下两个项目一起提供给用户:
- dist/PDFToImageConverter.exe
- poppler_bin 文件夹 (这是您在步骤二中准备的 Poppler 的 bin 文件夹的拷贝,需要与 .exe 文件放在同一目录下)。
用户运行 PDFToImageConverter.exe 时,程序就会在同级目录下找到 poppler_bin 文件夹,从而成功调用 Poppler 进行 PDF 转换。
4. 完整代码
import os
import sys
import threading
from tkinter import Tk, Label, Button, Entry, filedialog, StringVar, ttk, messagebox
from pdf2image import convert_from_path
def select_pdf():
path = filedialog.askopenfilename(
title="选择 PDF 文件",
filetypes=[("PDF 文件", "*.pdf")]
)
pdf_path_var.set(path)
def select_output_dir():
path = filedialog.askdirectory(title="选择输出目录")
output_dir_var.set(path)
def convert_pdf():
pdf_path = pdf_path_var.get()
output_dir = output_dir_var.get()
dpi = dpi_var.get()
fmt = format_var.get().lower()
if not pdf_path:
messagebox.showerror("错误", "请选择 PDF 文件")
return
if not output_dir:
messagebox.showerror("错误", "请选择输出目录")
return
try:
dpi = int(dpi)
except:
messagebox.showerror("错误", "DPI 必须是整数")
return
# 启动异步线程执行转换(防止 GUI 卡死)
threading.Thread(target=convert_worker, args=(pdf_path, output_dir, dpi, fmt), daemon=True).start()
def convert_worker(pdf_path, output_dir, dpi, fmt):
progress_bar["value"] = 0
progress_label.config(text="正在加载 PDF...")
# ----------------------------------------------------
# 修改 Poppler 路径处理
if getattr(sys, 'frozen', False):
# 如果是打包后的 exe,Poppler 文件夹应该放在程序运行目录下
# 这里假设您将 Poppler bin 文件夹重命名为 'poppler_bin' 放在程序根目录
base_path = os.path.dirname(sys.executable)
else:
# 如果是直接运行 Python 脚本,则使用原始路径(或相对路径)
base_path = os.path.dirname(os.path.abspath(__file__))
# 构建 Poppler 的 bin 目录路径
poppler_bin_path = os.path.join(base_path, 'poppler_bin')
# 检查路径是否存在
if not os.path.exists(poppler_bin_path):
messagebox.showerror("错误", f"Poppler 路径未找到:\n{poppler_bin_path}\n请确保 'poppler_bin' 文件夹存在于程序运行目录。")
return
# ----------------------------------------------------
try:
# 使用修改后的 poppler_bin_path
pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=poppler_bin_path)
except Exception as e:
print(pdf_path)
print("=======")
messagebox.showerror("错误", f"PDF 读取失败:\n{e}")
return
total = len(pages)
progress_bar["maximum"] = total
try:
for i, page in enumerate(pages):
filename = os.path.join(output_dir, f"page_{i+1}.{fmt}")
page.save(filename, fmt.upper())
progress_bar["value"] = i + 1
progress_label.config(text=f"正在转换第 {i+1}/{total} 页...")
except Exception as e:
messagebox.showerror("错误", f"转换失败:\n{e}")
return
progress_label.config(text="完成!")
messagebox.showinfo("完成", f"全部页面已成功导出至:\n{output_dir}")
# GUI -----------------------------
root = Tk()
root.title("PDF 分页保存为图片转换器")
root.geometry("500x350")
Label(root, text="选择 PDF 文件:").pack()
pdf_path_var = StringVar()
Entry(root, textvariable=pdf_path_var, width=60).pack()
Button(root, text="浏览...", command=select_pdf).pack(pady=5)
Label(root, text="选择输出目录:").pack()
output_dir_var = StringVar()
Entry(root, textvariable=output_dir_var, width=60).pack()
Button(root, text="浏览...", command=select_output_dir).pack(pady=5)
Label(root, text="DPI(清晰度,默认 200):").pack()
dpi_var = StringVar(value="200")
Entry(root, textvariable=dpi_var, width=10).pack()
Label(root, text="输出图片格式:").pack()
format_var = StringVar(value="PNG")
ttk.Combobox(root, textvariable=format_var, values=["PNG", "JPG"], width=10).pack()
# 进度条
progress_label = Label(root, text="等待开始...")
progress_label.pack(pady=10)
progress_bar = ttk.Progressbar(root, length=350)
progress_bar.pack()
Button(root, text="开始转换", command=convert_pdf, width=20).pack(pady=15)
root.mainloop()

浙公网安备 33010602011771号