目录
- [手把手教你用 PyQt5 开发一个 PDF 批量合并工具](#手把手教你用 PyQt5 开发一个 PDF 批量合并工具)
专栏导读
🌸 欢迎来到Python办公自动化专栏---Python处理办公问题,解放您的双手
🏳️🌈 个人博客主页:请点击------> 个人的博客主页 求收藏
🏳️🌈 Github主页:请点击------> Github主页 求Star⭐
🏳️🌈 知乎主页:请点击------> 知乎主页 求关注
🏳️🌈 CSDN博客主页:请点击------> CSDN的博客主页 求关注
👍 该系列文章专栏:请点击------>Python办公自动化专栏 求订阅
🕷 此外还有爬虫专栏:请点击------>Python爬虫基础专栏 求订阅
📕 此外还有python基础专栏:请点击------>Python基础学习专栏 求订阅
文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
❤️ 欢迎各位佬关注! ❤️
手把手教你用 PyQt5 开发一个 PDF 批量合并工具
日常工作中经常需要把多个 PDF 合并成一个?网上的在线工具要么有广告、要么有页数限制、要么担心隐私泄漏。今天我们就用 Python + PyQt5 从零打造一款本地运行、无广告、无限制的 PDF 批量合并工具!
一、为什么要自己做?
市面上的 PDF 合并方案大致分三类:
| 方案 | 缺点 |
|---|---|
| 在线网站(如 SmallPDF) | 文件上传有大小限制,隐私风险 |
| Adobe Acrobat | 正版昂贵,杀鸡焉用牛刀 |
| 命令行脚本 | 对非技术用户不友好 |
我们的目标:一个有图形界面、支持拖拽、本地运行的小工具。
二、技术选型
| 需求 | 选型 | 理由 |
|---|---|---|
| GUI 框架 | PyQt5 | 成熟稳定,跨平台,控件丰富 |
| PDF 操作 | PyPDF2 | 轻量级,合并功能完善,纯 Python |
| 多线程 | QThread | 与 PyQt5 完美配合,避免界面冻结 |
三、安装依赖
bash
pip install PyQt5 PyPDF2
或者使用 requirements.txt:
bash
pip install -r requirements.txt
四、整体架构设计
程序分为 3 个核心模块:
pdf_merger.py
├── MergeWorker --- 后台合并线程
├── DragDropListWidget --- 支持拖拽的文件列表控件
└── PDFMergerApp --- 主窗口(界面 + 业务逻辑)
数据流
用户操作 → 添加文件到列表 → 点击合并
↓
启动 MergeWorker 线程
↓
逐个 append PDF → 写入输出文件
↓
信号通知主线程 → 更新日志和进度条
五、核心代码详解
5.1 后台合并线程 --- MergeWorker
为什么要用线程?因为如果在主线程中执行 IO 密集的合并操作,界面会"假死"。PyQt5 的 QThread 配合信号(Signal)可以完美解决:
python
class MergeWorker(QThread):
log_signal = pyqtSignal(str) # 日志消息
progress_signal = pyqtSignal(int) # 进度百分比
finished_signal = pyqtSignal(bool, str) # 完成状态
def __init__(self, file_list, output_path):
super().__init__()
self.file_list = file_list
self.output_path = output_path
def run(self):
merger = PdfMerger()
total = len(self.file_list)
try:
for idx, fpath in enumerate(self.file_list, 1):
self.log_signal.emit(f"[{idx}/{total}] 正在添加: {os.path.basename(fpath)}")
merger.append(fpath)
self.progress_signal.emit(int(idx / total * 100))
merger.write(self.output_path)
merger.close()
self.finished_signal.emit(True, self.output_path)
except Exception as e:
self.finished_signal.emit(False, str(e))
关键点:
- 定义了三个信号:日志、进度、完成状态
run()方法在子线程执行,通过信号回传到主线程更新 UI- 使用
try/except捕获异常,合并失败不会崩溃
5.2 拖拽列表控件 --- DragDropListWidget
PyQt5 的 QListWidget 本身不支持从系统拖入文件,我们需要重写几个事件:
python
class DragDropListWidget(QListWidget):
files_dropped = pyqtSignal(list) # 拖入的路径列表
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptDrops(True)
self.setDragDropMode(QAbstractItemView.InternalMove)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
else:
super().dragEnterEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
paths = [url.toLocalFile() for url in event.mimeData().urls() if url.toLocalFile()]
self.files_dropped.emit(paths)
event.acceptProposedAction()
else:
super().dropEvent(event)
关键点:
setAcceptDrops(True)允许接受外部拖拽setDragDropMode(InternalMove)同时支持列表内部拖拽排序dragEnterEvent判断拖入的是否为文件 URLdropEvent提取本地路径,发射自定义信号
5.3 主窗口 --- PDFMergerApp
主窗口负责布局和业务逻辑。核心布局结构:
QMainWindow
└── QVBoxLayout (根布局)
├── QLabel (标题)
├── QLabel (副标题)
├── QSplitter (垂直分割)
│ ├── QGroupBox --- 文件列表区
│ │ ├── DragDropListWidget
│ │ └── QHBoxLayout --- 按钮行
│ └── QGroupBox --- 日志区
│ └── QTextEdit
├── QProgressBar (进度条)
└── QPushButton (合并按钮)
文件收集逻辑
支持同时传入文件和文件夹,递归收集所有 PDF:
python
def _collect_pdfs(self, paths):
pdfs = []
for p in paths:
if os.path.isfile(p) and p.lower().endswith(".pdf"):
pdfs.append(p)
elif os.path.isdir(p):
for root, _, files in os.walk(p):
for f in sorted(files):
if f.lower().endswith(".pdf"):
pdfs.append(os.path.join(root, f))
return pdfs
去重添加
同一个文件不会被重复添加:
python
def _add_to_list(self, new_files):
added = 0
for f in new_files:
if f not in self.pdf_files:
self.pdf_files.append(f)
self.file_list_widget.addItem(f)
added += 1
列表排序
上移/下移通过交换列表中的位置实现:
python
def _move_up(self):
row = self.file_list_widget.currentRow()
if row > 0:
item = self.file_list_widget.takeItem(row)
self.file_list_widget.insertItem(row - 1, item)
self.file_list_widget.setCurrentRow(row - 1)
self.pdf_files[row], self.pdf_files[row - 1] = (
self.pdf_files[row - 1], self.pdf_files[row]
)
六、样式美化
使用 Qt StyleSheet(类似 CSS)统一美化控件:
css
QMainWindow {
background-color: #f5f7fa;
}
QGroupBox {
border: 1px solid #dcdfe6;
border-radius: 8px;
background-color: #ffffff;
}
#mergeBtn {
background-color: #3498db;
color: white;
font-size: 15px;
font-weight: bold;
border-radius: 8px;
}
QProgressBar::chunk {
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 #3498db, stop:1 #2ecc71);
border-radius: 5px;
}
美化要点:
QGroupBox使用白色背景 + 圆角,呈现卡片效果- 合并按钮使用蓝色主题色,hover 和 pressed 有层次感
- 进度条渐变色(蓝 → 绿),视觉上更有活力
- 字体使用
Microsoft YaHei,中文显示清晰美观
七、使用流程
第一步:启动程序
bash
python pdf_merger.py
第二步:添加文件
三种方式任选:
- 拖拽:直接从文件资源管理器拖入 PDF 文件或文件夹
- 添加文件:点击按钮,在文件选择对话框中多选 PDF
- 添加目录:点击按钮,选择一个文件夹,自动扫描其中所有 PDF
第三步:调整顺序
- 选中某个文件,用 ⬆/⬇ 按钮调整位置
- 或者直接在列表中拖拽排序
第四步:点击合并
- 点击 "🚀 开始合并"
- 选择保存位置和文件名
- 等待完成,日志区实时显示处理进度
八、可能遇到的问题与解决
1. 安装 PyQt5 失败
bash
# 尝试使用国内镜像
pip install PyQt5 -i https://pypi.tuna.tsinghua.edu.cn/simple
2. 合并时报错 "PdfReadError"
通常是因为 PDF 文件损坏或加密。解决方法:
- 排除问题文件(可在日志中看到是哪个文件报错)
- 使用其他工具先修复或解密该 PDF
3. 打包为 EXE
想分发给没有 Python 环境的同事?使用 PyInstaller:
bash
pip install pyinstaller
pyinstaller --onefile --windowed --name "PDF合并工具" pdf_merger.py
打包后的 EXE 位于 dist/ 目录。
九、进阶扩展思路
如果你想继续完善这个工具,以下是一些扩展方向:
| 方向 | 说明 |
|---|---|
| 🔒 支持加密 PDF | 在 append 前使用 PdfReader 解密 |
| 📑 选择页面范围 | 允许用户选择每个 PDF 的特定页面 |
| 🖼 预览功能 | 使用 pdf2image 生成缩略图预览 |
| 💾 保存/加载配置 | 记住上次的文件列表和输出路径 |
| 🌐 多语言 | 使用 Qt 的 i18n 机制支持英文等 |
| 📦 打包发布 | 使用 PyInstaller / Nuitka 打包为单文件 EXE |
十、完整项目结构
19-批量PDF合并工具/
├── pdf_merger.py # 主程序源码
├── requirements.txt # 依赖清单
├── README.md # 项目说明
└── blog.md # 本篇开发博客
总结
通过这篇文章,我们从零实现了一个功能完整的 PDF 批量合并工具:
- PyPDF2 负责 PDF 的读取与合并
- PyQt5 构建了美观的图形界面
- QThread 保证了合并过程中界面不冻结
- 拖拽功能 让操作更加便捷
- QSS 样式 让界面更加美观
整个项目只有一个 Python 文件,代码约 300 行,简洁易懂。希望这个小工具能帮到你,也希望这篇教程能给你一些 PyQt5 桌面开发的启发!
如果觉得有帮助,欢迎点赞、收藏、转发!有问题欢迎评论区交流 🎉
结尾
希望对初学者有帮助;致力于办公自动化的小小程序员一枚
希望能得到大家的【❤️一个免费关注❤️】感谢!
求个 🤞 关注 🤞 +❤️ 喜欢 ❤️ +👍 收藏 👍
此外还有办公自动化专栏,欢迎大家订阅:Python办公自动化专栏
此外还有爬虫专栏,欢迎大家订阅:Python爬虫基础专栏
此外还有Python基础专栏,欢迎大家订阅:Python基础学习专栏