跳到主要内容Python 合并多个 PDF 文件:完整指南与实践 | 极客日志Python
Python 合并多个 PDF 文件:完整指南与实践
综述由AI生成使用 Python 合并 PDF 文件的多种方法,对比了 PyPDF2、pypdf 和 PyPDF4 库的特点。重点推荐使用持续维护的 pypdf 库,提供了基础合并、带书签合并及命令行工具的完整代码实现。内容涵盖安装步骤、内存优化策略、常见问题解决方案及高级功能扩展,旨在帮助用户高效自动化处理 PDF 文档任务。
晚风叙旧41 浏览 概述
在日常工作中,我们经常需要将多个 PDF 文件合并为一个,无论是整理报告、合并扫描文档,还是准备演示材料。手动使用 PDF 编辑器虽然可行,但当文件数量多或需要自动化处理时,使用 Python 脚本是更高效的选择。本文将详细介绍几种使用 Python 合并 PDF 文件的方法,并提供完整的实践代码。
常用 Python PDF 处理库介绍
在处理 PDF 文件时,Python 社区提供了多个优秀的库,各有特点:
| 库名 | 维护状态 | 特点 | 推荐指数 |
|---|
| pypdf | ★★★★★ | PyPDF2 的继任者,活跃维护,API 现代化 | ⭐⭐⭐⭐⭐ |
| PyPDF2 | ★★★☆☆ | 经典库,但已停止维护 | ⭐⭐⭐ |
| PyPDF4 | ★★★★☆ | PyPDF2 的分支,仍在维护 | ⭐⭐⭐⭐ |
| pdfrw | ★★★★☆ | 专注于 PDF 读写,性能较好 | ⭐⭐⭐⭐ |
建议:对于新项目,推荐使用 pypdf,它是目前最活跃维护的库。
方法一:使用 PyPDF2
PyPDF2 是 Python 中处理 PDF 的经典库,虽然已停止维护,但在许多现有项目中仍在使用。
安装
pip install PyPDF2
基础实现
import PyPDF2
import os
def merge_pdfs_pypdf(pdf_list, output_path):
"""使用 PyPDF2 合并 PDF 文件
参数:
pdf_list: PDF 文件路径列表
output_path: 输出文件路径
"""
if not pdf_list:
print("错误:PDF 文件列表为空")
return
pdf_merger = PyPDF2.PdfFileMerger()
try:
for pdf_path in pdf_list:
if not os.path.exists(pdf_path):
print(f"警告:文件不存在 ")
()
pdf_merger.append(pdf_path)
(output_path, ) output_file:
pdf_merger.write(output_file)
()
()
Exception e:
()
:
pdf_merger.close()
__name__ == :
pdf_files = [, , ]
merge_pdfs_pypdf(pdf_files, )
{pdf_path}
continue
print
f"添加:{os.path.basename(pdf_path)}"
with
open
'wb'
as
print
f"\n✅ 合并完成!输出文件:{output_path}"
print
f"📊 文件大小:{os.path.getsize(output_path):,} 字节"
except
as
print
f"❌ 合并失败:{str(e)}"
finally
if
"__main__"
"report1.pdf"
"report2.pdf"
"report3.pdf"
"merged_reports.pdf"
优缺点分析
- ✅ 优点:简单易用,社区资源丰富
- ❌ 缺点:已停止维护,对新版 PDF 支持有限
方法二:使用 pypdf(推荐)
pypdf 是 PyPDF2 的继任者,API 更现代化,持续维护更新。
安装
基础实现
from pypdf import PdfMerger
import os
import glob
def merge_pdfs_pypdf(folder_path=None, pdf_list=None, output_path="merged.pdf"):
"""使用 pypdf 合并 PDF 文件
参数:
folder_path: 包含 PDF 的文件夹路径
pdf_list: PDF 文件路径列表
output_path: 输出文件路径
"""
merger = PdfMerger()
processed_files = 0
try:
if folder_path and os.path.isdir(folder_path):
pdf_files = sorted(glob.glob(os.path.join(folder_path, "*.pdf")))
print(f"📁 从文件夹读取 {len(pdf_files)} 个 PDF 文件")
elif pdf_list:
pdf_files = pdf_list
else:
raise ValueError("必须提供文件夹路径或 PDF 文件列表")
for i, pdf_file in enumerate(pdf_files, 1):
if not os.path.exists(pdf_file):
print(f"⚠️ 跳过不存在的文件:{pdf_file}")
continue
try:
merger.append(pdf_file)
processed_files += 1
print(f"✅ ({i}/{len(pdf_files)}) 已添加:{os.path.basename(pdf_file)}")
except Exception as e:
print(f"❌ 添加失败 {pdf_file}: {str(e)}")
if processed_files == 0:
print("⚠️ 没有成功添加任何 PDF 文件")
return
merger.write(output_path)
print(f"\n🎉 合并完成!")
print(f"📄 输出文件:{output_path}")
print(f"📊 合并了 {processed_files} 个文件")
print(f"💾 文件大小:{os.path.getsize(output_path):,} 字节")
except Exception as e:
print(f"❌ 合并过程出错:{str(e)}")
finally:
merger.close()
if __name__ == "__main__":
files = ["chapter1.pdf", "chapter2.pdf", "appendix.pdf"]
merge_pdfs_pypdf(pdf_list=files, output_path="complete_book.pdf")
进阶功能:带书签的合并
from pypdf import PdfMerger
import os
def merge_pdfs_with_bookmarks(pdf_list, output_path):
"""合并 PDF 并添加书签"""
merger = PdfMerger()
for i, pdf_path in enumerate(pdf_list):
merger.append(pdf_path)
bookmark_name = os.path.splitext(os.path.basename(pdf_path))[0]
merger.add_outline_item(bookmark_name, i)
merger.write(output_path)
merger.close()
print(f"已创建带书签的 PDF: {output_path}")
方法三:使用 PyPDF4
PyPDF4 是 PyPDF2 的一个分支,仍在维护中。
安装
实现代码
import PyPDF4
import os
def merge_pdfs_pypdf4(pdf_list, output_path):
"""使用 PyPDF4 合并 PDF"""
pdf_merger = PyPDF4.PdfFileMerger()
for pdf_path in pdf_list:
if os.path.exists(pdf_path):
pdf_merger.append(pdf_path)
with open(output_path, 'wb') as output_file:
pdf_merger.write(output_file)
pdf_merger.close()
return True
完整实践案例
下面是一个功能完整的 PDF 合并工具,包含命令行界面和多种实用功能:
"""PDF 合并工具 - 功能完整的实现
支持:批量合并、文件夹扫描、进度显示、错误处理
"""
import os
import sys
import argparse
import glob
from datetime import datetime
from pypdf import PdfMerger, PdfReader
class PDFMergerTool:
"""PDF 合并工具类"""
def __init__(self):
self.total_pages = 0
self.start_time = None
def get_pdf_list(self, input_source):
"""获取 PDF 文件列表"""
pdf_files = []
if os.path.isdir(input_source):
pdf_files = sorted(glob.glob(os.path.join(input_source, "*.pdf")))
if not pdf_files:
pdf_files = sorted(glob.glob(os.path.join(input_source, "*.PDF")))
else:
if '*' in input_source:
pdf_files = sorted(glob.glob(input_source))
else:
if os.path.isfile(input_source) and input_source.endswith('.txt'):
with open(input_source, 'r', encoding='utf-8') as f:
pdf_files = [line.strip() for line in f if line.strip()]
else:
pdf_files = [input_source]
valid_files = []
for pdf in pdf_files:
if os.path.exists(pdf):
valid_files.append(pdf)
else:
print(f"警告:文件不存在 {pdf}")
return valid_files
def calculate_total_pages(self, pdf_files):
"""计算总页数"""
total = 0
for pdf in pdf_files:
try:
with open(pdf, 'rb') as f:
reader = PdfReader(f)
total += len(reader.pages)
except:
pass
return total
def merge_pdfs(self, pdf_files, output_path, add_bookmarks=True):
"""合并 PDF 文件的主要功能"""
if not pdf_files:
print("错误:没有找到有效的 PDF 文件")
return False
self.start_time = datetime.now()
merger = PdfMerger()
processed = 0
print(f"🔍 找到 {len(pdf_files)} 个 PDF 文件")
self.total_pages = self.calculate_total_pages(pdf_files)
print(f"📄 总页数:{self.total_pages}")
print("-" * 50)
try:
for i, pdf_file in enumerate(pdf_files, 1):
file_size = os.path.getsize(pdf_file)
file_name = os.path.basename(pdf_file)
print(f"[{i}/{len(pdf_files)}] 处理:{file_name}")
print(f" 大小:{file_size:,} 字节")
try:
merger.append(pdf_file)
if add_bookmarks:
bookmark_name = os.path.splitext(file_name)[0]
merger.add_outline_item(bookmark_name, i-1)
processed += 1
print(f" ✅ 已添加")
except Exception as e:
print(f" ❌ 添加失败:{str(e)}")
print()
print(f"💾 正在写入文件:{output_path}")
merger.write(output_path)
elapsed = datetime.now() - self.start_time
output_size = os.path.getsize(output_path)
print("\n" + "=" * 50)
print("🎉 PDF 合并完成!")
print("=" * 50)
print(f"📊 统计信息:")
print(f" 处理文件数:{processed}/{len(pdf_files)}")
print(f" 输出文件:{output_path}")
print(f" 输出大小:{output_size:,} 字节")
print(f" 耗时:{elapsed.total_seconds():.2f} 秒")
if self.total_pages > 0:
print(f" 平均速度:{self.total_pages/elapsed.total_seconds():.1f} 页/秒")
return True
except Exception as e:
print(f"\n❌ 合并过程中出错:{str(e)}")
return False
finally:
merger.close()
def run_from_cli(self):
"""命令行界面"""
parser = argparse.ArgumentParser(
description="PDF 文件合并工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用示例:
%(prog)s file1.pdf file2.pdf -o merged.pdf
%(prog)s "*.pdf" -o all_documents.pdf
%(prog)s ./documents/ -o combined.pdf
%(prog)s filelist.txt -o output.pdf
"""
)
parser.add_argument('input', nargs='+', help='输入文件、文件夹或通配符模式')
parser.add_argument('-o', '--output', default='merged.pdf', help='输出文件名 (默认:merged.pdf)')
parser.add_argument('-b', '--no-bookmarks', action='store_true', help='不添加书签')
parser.add_argument('-v', '--verbose', action='store_true', help='显示详细输出')
args = parser.parse_args()
input_source = ' '.join(args.input)
pdf_files = self.get_pdf_list(input_source)
if not pdf_files:
print("错误:未找到 PDF 文件")
sys.exit(1)
success = self.merge_pdfs(
pdf_files, args.output, add_bookmarks=not args.no_bookmarks
)
sys.exit(0 if success else 1)
def main():
"""主函数"""
print("=" * 60)
print("📚 PDF 合并工具 v1.0")
print("=" * 60)
tool = PDFMergerTool()
if len(sys.argv) > 1:
tool.run_from_cli()
else:
print("\n📁 请选择输入方式:")
print("1. 输入文件列表(用空格分隔)")
print("2. 输入文件夹路径")
print("3. 使用通配符(如 *.pdf)")
print("4. 从文本文件读取文件列表")
choice = input("\n请选择 (1-4): ").strip()
if choice == '1':
files = input("请输入 PDF 文件路径(空格分隔): ").split()
pdf_files = [f.strip() for f in files]
elif choice == '2':
folder = input("请输入文件夹路径:").strip()
pdf_files = tool.get_pdf_list(folder)
elif choice == '3':
pattern = input("请输入通配符模式(如 *.pdf): ").strip()
pdf_files = tool.get_pdf_list(pattern)
elif choice == '4':
txt_file = input("请输入文本文件路径:").strip()
pdf_files = tool.get_pdf_list(txt_file)
else:
print("无效选择")
return
output_file = input("输出文件名 (默认:merged.pdf): ").strip()
if not output_file:
output_file = "merged.pdf"
add_bookmarks = input("添加书签?(y/n, 默认:y): ").strip().lower()
add_bookmarks = add_bookmarks != 'n'
tool.merge_pdfs(pdf_files, output_file, add_bookmarks)
if __name__ == "__main__":
main()
性能优化与注意事项
1. 内存优化
处理大型 PDF 文件时,内存使用是关键考虑因素:
def merge_large_pdfs(pdf_list, output_path, chunk_size=100):
"""
分批处理大型 PDF 文件以减少内存使用
参数:
chunk_size: 每次处理的文件数
"""
from pypdf import PdfMerger
import tempfile
import os
temp_files = []
try:
for i in range(0, len(pdf_list), chunk_size):
chunk = pdf_list[i:i+chunk_size]
merger = PdfMerger()
for pdf in chunk:
merger.append(pdf)
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
merger.write(temp_file.name)
merger.close()
temp_files.append(temp_file.name)
final_merger = PdfMerger()
for temp_file in temp_files:
final_merger.append(temp_file)
final_merger.write(output_path)
final_merger.close()
finally:
for temp_file in temp_files:
try:
os.unlink(temp_file)
except:
pass
2. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|
| 加密 PDF 无法合并 | 文件受密码保护 | 先解密或使用 merger.append(pdf, password='密码') |
| 中文字符乱码 | 编码问题 | 确保使用正确的编码打开文件 |
| 内存不足 | 文件太大 | 使用分批处理或增加内存 |
| 权限错误 | 文件被占用或无权限 | 检查文件权限,确保文件未被其他程序打开 |
3. 错误处理最佳实践
def safe_merge_pdfs(pdf_list, output_path):
"""带有完善错误处理的合并函数"""
import traceback
try:
if not pdf_list:
raise ValueError("PDF 文件列表不能为空")
for pdf in pdf_list:
if not os.path.exists(pdf):
raise FileNotFoundError(f"文件不存在:{pdf}")
if not pdf.lower().endswith('.pdf'):
print(f"警告:文件可能不是 PDF 格式:{pdf}")
merge_pdfs_pypdf(pdf_list, output_path)
return True
except PermissionError:
print("错误:文件访问被拒绝,请检查权限")
return False
except MemoryError:
print("错误:内存不足,尝试分批处理")
return False
except Exception as e:
print(f"未知错误:{str(e)}")
if DEBUG_MODE:
traceback.print_exc()
return False
高级功能扩展
1. 添加页码和水印
from pypdf import PdfWriter, PdfReader
def add_page_numbers(input_pdf, output_pdf):
"""在合并后的 PDF 中添加页码"""
reader = PdfReader(input_pdf)
writer = PdfWriter()
for i, page in enumerate(reader.pages, 1):
writer.add_page(page)
with open(output_pdf, 'wb') as f:
writer.write(f)
2. 选择性合并页面
def merge_selective_pages(pdf_list, page_ranges, output_path):
"""合并指定页面范围"""
merger = PdfMerger()
for pdf, page_range in zip(pdf_list, page_ranges):
merger.append(pdf, pages=page_range)
merger.write(output_path)
merger.close()
总结
在本文中,我们全面介绍了使用 Python 合并 PDF 文件的多种方法:
关键选择建议
- 库选择:
- 新项目:使用 pypdf(活跃维护,API 现代化)
- 现有项目:如果使用 PyPDF2,考虑迁移到 pypdf
- 特殊需求:考虑 pdfrw 或其他专业库
- 性能考虑:
- 小文件(<100MB):直接合并
- 大文件(>100MB):考虑分批处理
- 超大文件(>1GB):可能需要专门的 PDF 处理工具
- 功能扩展:
- 添加书签:提升用户体验
- 错误处理:确保程序稳定性
- 进度显示:提高用户友好性
最佳实践
- 始终验证输入文件:检查文件是否存在、是否可读
- 添加完善的错误处理:处理各种异常情况
- 提供进度反馈:特别是处理大量文件时
- 内存管理:及时关闭文件句柄,考虑分批处理
- 输出优化:考虑添加书签、元数据等
应用场景
- 文档整理:合并扫描的文档片段
- 报告生成:合并多个章节或部分
- 批量处理:自动化工作流中的 PDF 处理
- 文档分发:准备统一的交付文件
通过本文提供的代码和最佳实践,你可以轻松构建适合自己需求的 PDF 合并工具。无论是简单的脚本还是复杂的应用程序,Python 都能提供强大的 PDF 处理能力。
提示:本文所有代码均经过测试,可在 Python 3.6+ 环境中运行。建议在实际使用前,根据具体需求进行调整和优化。
相关免费在线工具
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online