1. 项目背景与需求分析
每次处理大量PDF文档时,最让人头疼的就是如何高效地将分散在不同文件夹中的PDF合并成一个文件。作为经常需要整理项目文档的技术写作者,我经历过无数次手动拖拽合并的煎熬。直到开发了这个自动化脚本,才真正解放了生产力。
这个Python脚本的核心功能是遍历指定文件夹及其子目录,自动识别所有PDF文件,并按自定义规则进行合并。相比市面上常见的PDF工具,它的优势在于:
- 完全免费且无需安装第三方软件
- 支持递归搜索子文件夹
- 可自定义输出文件名和路径
- 保留原始文件的元数据和书签
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术实现方案选型
2.1 核心库对比
经过实际测试比较,最终选择PyPDF2作为基础库,原因如下:
| 库名称 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| PyPDF2 | 轻量级、API简洁 | 对加密PDF支持有限 | 基础合并/拆分操作 |
| pdfrw | 支持表单字段保留 | 文档较少 | 需要保留表单的场景 |
| PyMuPDF | 功能最全面 | 安装复杂 | 高级PDF编辑需求 |
提示:如果遇到加密PDF,建议先用qpdf工具解密后再处理
2.2 目录遍历方案
采用os.walk()递归遍历目录树,相比glob.glob()的优势在于:
- 自动处理嵌套子文件夹
- 返回完整路径信息
- 支持过滤隐藏文件
python复制def find_pdfs(root_dir):
pdf_files = []
for dirpath, _, filenames in os.walk(root_dir):
for file in filenames:
if file.lower().endswith('.pdf'):
pdf_files.append(os.path.join(dirpath, file))
return sorted(pdf_files) # 按文件名排序
3. 完整实现代码解析
3.1 核心合并逻辑
python复制from PyPDF2 import PdfMerger
def merge_pdfs(input_files, output_path):
merger = PdfMerger()
try:
for pdf in input_files:
with open(pdf, 'rb') as f:
merger.append(f)
with open(output_path, 'wb') as f:
merger.write(f)
except Exception as e:
print(f"合并失败: {str(e)}")
finally:
merger.close()
关键参数说明:
strict=True:严格模式验证PDF结构import_bookmarks=False:是否保留原书签appendvsmerge:前者保留独立文档结构
3.2 文件排序策略
实际项目中常见的排序需求:
- 按文件名自然排序:
python复制from natsort import natsorted
pdf_files = natsorted(pdf_files)
- 按修改时间排序:
python复制pdf_files.sort(key=lambda x: os.path.getmtime(x))
- 自定义排序规则:
python复制def custom_sort(filename):
# 示例:按文件名的数字部分排序
return int(re.search(r'\d+', filename).group())
pdf_files.sort(key=custom_sort)
4. 高级功能扩展
4.1 添加统一页眉页脚
python复制from reportlab.pdfgen import canvas
from PyPDF2 import PdfReader
def add_watermark(input_pdf, output_pdf, text):
packet = io.BytesIO()
can = canvas.Canvas(packet)
can.setFont("Helvetica", 9)
can.drawString(72, 72, text) # 左下角位置
can.save()
watermark = PdfReader(packet)
original = PdfReader(input_pdf)
output = PdfWriter()
for page in range(len(original.pages)):
page = original.pages[page]
page.merge_page(watermark.pages[0])
output.add_page(page)
with open(output_pdf, "wb") as f:
output.write(f)
4.2 批量添加目录页
python复制from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph
def create_toc(pdf_files, toc_path):
doc = SimpleDocTemplate(toc_path, pagesize=letter)
story = []
for i, pdf in enumerate(pdf_files):
story.append(Paragraph(
f"<b>{i+1}.</b> {os.path.basename(pdf)}",
style=styles['Normal']
))
doc.build(story)
return toc_path
5. 常见问题排查指南
5.1 合并后文件异常增大
可能原因及解决方案:
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 文件大小翻倍 | 保留了冗余资源 | 启用optimize参数 |
| 图片质量下降 | 被重新压缩 | 使用compress_content=False |
| 文字变成图片 | 字体嵌入问题 | 检查原始PDF字体设置 |
5.2 特殊字符文件名处理
Windows系统常见问题:
python复制# 处理非ASCII文件名
def safe_path(path):
try:
return path.encode('utf-8').decode('utf-8')
except:
return path.encode('gbk').decode('gbk')
5.3 性能优化技巧
处理1000+PDF文件时:
- 使用内存映射:
python复制with open(pdf, 'rb', buffering=0) as f:
merger.append(f)
- 分批处理:
python复制chunks = [pdf_files[i:i+100] for i in range(0, len(pdf_files), 100)]
for chunk in chunks:
partial_merge(chunk, f"temp_{chunk[0]}.pdf")
- 多进程加速:
python复制from multiprocessing import Pool
with Pool(4) as p:
p.map(process_pdf, pdf_files)
6. 完整脚本示例
python复制#!/usr/bin/env python3
import os
import argparse
from PyPDF2 import PdfMerger
from natsort import natsorted
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input_dir", help="包含PDF的文件夹路径")
parser.add_argument("output_file", help="合并后的输出路径")
args = parser.parse_args()
pdf_files = []
for root, _, files in os.walk(args.input_dir):
for file in files:
if file.lower().endswith('.pdf'):
pdf_files.append(os.path.join(root, file))
if not pdf_files:
print("未找到PDF文件")
return
merger = PdfMerger()
try:
for pdf in natsorted(pdf_files):
print(f"正在处理: {pdf}")
merger.append(pdf, import_bookmarks=False)
print(f"正在保存到: {args.output_file}")
merger.write(args.output_file)
print(f"成功合并 {len(pdf_files)} 个文件")
except Exception as e:
print(f"错误: {str(e)}")
finally:
merger.close()
if __name__ == "__main__":
main()
使用方式:
bash复制python merge_pdfs.py ./documents merged_output.pdf
7. 实际应用案例
7.1 学术论文整理
某研究生需要合并200+篇参考文献:
- 按发表日期排序
- 添加统一页眉标注来源
- 生成带超链接的目录
解决方案:
python复制# 1. 按日期排序
pdfs = sorted(pdfs, key=get_publish_date)
# 2. 批量添加水印
for pdf in pdfs:
add_watermark(pdf, "temp.pdf", "From: IEEE Xplore")
# 3. 生成目录
create_toc(pdfs, "toc.pdf")
7.2 企业合同归档
法务部门季度合同整理需求:
- 自动识别合同编号
- 按客户分类合并
- 保留原始签名页
关键代码:
python复制client_files = defaultdict(list)
for pdf in pdfs:
client = extract_client_id(pdf) # 正则提取客户ID
client_files[client].append(pdf)
for client, files in client_files.items():
merge_pdfs(files, f"{client}_Q3_contracts.pdf")
这个脚本经过两年多的实际使用迭代,目前已经稳定处理超过50,000份PDF文档。最实用的建议是:对于定期执行的合并任务,可以结合Windows任务计划或Linux cron实现全自动化运行。
