Python合并多個(gè)PDF文件的完整指南與實(shí)踐
概述
在日常工作中,我們經(jīng)常需要將多個(gè)PDF文件合并為一個(gè),無論是整理報(bào)告、合并掃描文檔,還是準(zhǔn)備演示材料。手動(dòng)使用PDF編輯器雖然可行,但當(dāng)文件數(shù)量多或需要自動(dòng)化處理時(shí),使用Python腳本是更高效的選擇。本文將詳細(xì)介紹幾種使用Python合并PDF文件的方法,并提供完整的實(shí)踐代碼。
常用Python PDF處理庫介紹
在處理PDF文件時(shí),Python社區(qū)提供了多個(gè)優(yōu)秀的庫,各有特點(diǎn):
| 庫名 | 維護(hù)狀態(tài) | 特點(diǎn) | 推薦指數(shù) |
|---|---|---|---|
| pypdf | ★★★★★ | PyPDF2的繼任者,活躍維護(hù),API現(xiàn)代化 | ????? |
| PyPDF2 | ★★★☆☆ | 經(jīng)典庫,但已停止維護(hù) | ??? |
| PyPDF4 | ★★★★☆ | PyPDF2的分支,仍在維護(hù) | ???? |
| pdfrw | ★★★★☆ | 專注于PDF讀寫,性能較好 | ???? |
建議:對(duì)于新項(xiàng)目,推薦使用 pypdf,它是目前最活躍維護(hù)的庫。
方法一:使用PyPDF2
PyPDF2是Python中處理PDF的經(jīng)典庫,雖然已停止維護(hù),但在許多現(xiàn)有項(xiàng)目中仍在使用。
安裝
pip install PyPDF2
基礎(chǔ)實(shí)現(xiàn)
import PyPDF2
import os
def merge_pdfs_pypdf2(pdf_list, output_path):
"""
使用PyPDF2合并PDF文件
參數(shù):
pdf_list: PDF文件路徑列表
output_path: 輸出文件路徑
"""
if not pdf_list:
print("錯(cuò)誤:PDF文件列表為空")
return
# 創(chuàng)建PDF合并器
pdf_merger = PyPDF2.PdfFileMerger()
try:
for pdf_path in pdf_list:
if not os.path.exists(pdf_path):
print(f"警告:文件不存在 {pdf_path}")
continue
print(f"添加: {os.path.basename(pdf_path)}")
pdf_merger.append(pdf_path)
# 寫入合并后的文件
with open(output_path, 'wb') as output_file:
pdf_merger.write(output_file)
print(f"\n? 合并完成!輸出文件: {output_path}")
print(f"?? 文件大小: {os.path.getsize(output_path):,} 字節(jié)")
except Exception as e:
print(f"? 合并失敗: {str(e)}")
finally:
pdf_merger.close()
# 使用示例
if __name__ == "__main__":
pdf_files = ["report1.pdf", "report2.pdf", "report3.pdf"]
merge_pdfs_pypdf2(pdf_files, "merged_reports.pdf")
優(yōu)缺點(diǎn)分析
- ? 優(yōu)點(diǎn):簡單易用,社區(qū)資源豐富
- ? 缺點(diǎn):已停止維護(hù),對(duì)新版PDF支持有限
方法二:使用pypdf(推薦)
pypdf是PyPDF2的繼任者,API更現(xiàn)代化,持續(xù)維護(hù)更新。
安裝
pip install pypdf
基礎(chǔ)實(shí)現(xiàn)
from pypdf import PdfMerger
import os
import glob
def merge_pdfs_pypdf(folder_path=None, pdf_list=None, output_path="merged.pdf"):
"""
使用pypdf合并PDF文件
參數(shù):
folder_path: 包含PDF的文件夾路徑
pdf_list: PDF文件路徑列表
output_path: 輸出文件路徑
"""
merger = PdfMerger()
processed_files = 0
try:
# 確定輸入源
if folder_path and os.path.isdir(folder_path):
# 從文件夾獲取所有PDF文件
pdf_files = sorted(glob.glob(os.path.join(folder_path, "*.pdf")))
print(f"?? 從文件夾讀取 {len(pdf_files)} 個(gè)PDF文件")
elif pdf_list:
pdf_files = pdf_list
else:
raise ValueError("必須提供文件夾路徑或PDF文件列表")
# 逐個(gè)處理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} 個(gè)文件")
print(f"?? 文件大小: {os.path.getsize(output_path):,} 字節(jié)")
except Exception as e:
print(f"? 合并過程出錯(cuò): {str(e)}")
finally:
merger.close()
# 使用示例
if __name__ == "__main__":
# 方式1:合并指定文件列表
files = ["chapter1.pdf", "chapter2.pdf", "appendix.pdf"]
merge_pdfs_pypdf(pdf_list=files, output_path="complete_book.pdf")
# 方式2:合并文件夾內(nèi)所有PDF
# merge_pdfs_pypdf(folder_path="./documents", output_path="all_docs.pdf")
進(jìn)階功能:帶書簽的合并
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):
# 添加PDF文件
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"已創(chuàng)建帶書簽的PDF: {output_path}")
方法三:使用PyPDF4
PyPDF4是PyPDF2的一個(gè)分支,仍在維護(hù)中。
安裝
pip install PyPDF4
實(shí)現(xiàn)代碼
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
完整實(shí)踐案例
下面是一個(gè)功能完整的PDF合并工具,包含命令行界面和多種實(shí)用功能:
#!/usr/bin/env python3
"""
PDF合并工具 - 功能完整的實(shí)現(xiàn)
支持:批量合并、文件夾掃描、進(jìn)度顯示、錯(cuò)誤處理
"""
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:
# 嘗試其他擴(kuò)展名
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):
"""計(jì)算總頁數(shù)"""
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("錯(cuò)誤:沒有找到有效的PDF文件")
return False
self.start_time = datetime.now()
merger = PdfMerger()
processed = 0
print(f"?? 找到 {len(pdf_files)} 個(gè)PDF文件")
self.total_pages = self.calculate_total_pages(pdf_files)
print(f"?? 總頁數(shù): {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:,} 字節(jié)")
try:
# 添加PDF文件
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)
# 統(tǒng)計(jì)信息
elapsed = datetime.now() - self.start_time
output_size = os.path.getsize(output_path)
print("\n" + "=" * 50)
print("?? PDF合并完成!")
print("=" * 50)
print(f"?? 統(tǒng)計(jì)信息:")
print(f" 處理文件數(shù): {processed}/{len(pdf_files)}")
print(f" 輸出文件: {output_path}")
print(f" 輸出大小: {output_size:,} 字節(jié)")
print(f" 耗時(shí): {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? 合并過程中出錯(cuò): {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='輸出文件名 (默認(rèn): merged.pdf)'
)
parser.add_argument(
'-b', '--no-bookmarks',
action='store_true',
help='不添加書簽'
)
parser.add_argument(
'-v', '--verbose',
action='store_true',
help='顯示詳細(xì)輸出'
)
args = parser.parse_args()
# 處理輸入?yún)?shù)
input_source = ' '.join(args.input)
# 獲取PDF文件列表
pdf_files = self.get_pdf_list(input_source)
if not pdf_files:
print("錯(cuò)誤:未找到PDF文件")
sys.exit(1)
# 執(zhí)行合并
success = self.merge_pdfs(
pdf_files,
args.output,
add_bookmarks=not args.no_bookmarks
)
sys.exit(0 if success else 1)
def main():
"""主函數(shù)"""
print("=" * 60)
print("?? PDF合并工具 v1.0")
print("=" * 60)
tool = PDFMergerTool()
# 檢查是否通過命令行調(diào)用
if len(sys.argv) > 1:
tool.run_from_cli()
else:
# 交互式模式
print("\n?? 請(qǐng)選擇輸入方式:")
print("1. 輸入文件列表(用空格分隔)")
print("2. 輸入文件夾路徑")
print("3. 使用通配符(如 *.pdf)")
print("4. 從文本文件讀取文件列表")
choice = input("\n請(qǐng)選擇 (1-4): ").strip()
if choice == '1':
files = input("請(qǐng)輸入PDF文件路徑(空格分隔): ").split()
pdf_files = [f.strip() for f in files]
elif choice == '2':
folder = input("請(qǐng)輸入文件夾路徑: ").strip()
pdf_files = tool.get_pdf_list(folder)
elif choice == '3':
pattern = input("請(qǐng)輸入通配符模式(如 *.pdf): ").strip()
pdf_files = tool.get_pdf_list(pattern)
elif choice == '4':
txt_file = input("請(qǐng)輸入文本文件路徑: ").strip()
pdf_files = tool.get_pdf_list(txt_file)
else:
print("無效選擇")
return
output_file = input("輸出文件名 (默認(rèn): merged.pdf): ").strip()
if not output_file:
output_file = "merged.pdf"
add_bookmarks = input("添加書簽? (y/n, 默認(rèn): y): ").strip().lower()
add_bookmarks = add_bookmarks != 'n'
tool.merge_pdfs(pdf_files, output_file, add_bookmarks)
if __name__ == "__main__":
main()
性能優(yōu)化與注意事項(xiàng)
1. 內(nèi)存優(yōu)化
處理大型PDF文件時(shí),內(nèi)存使用是關(guān)鍵考慮因素:
def merge_large_pdfs(pdf_list, output_path, chunk_size=100):
"""
分批處理大型PDF文件以減少內(nèi)存使用
參數(shù):
chunk_size: 每次處理的文件數(shù)
"""
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)
# 保存臨時(shí)文件
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
merger.write(temp_file.name)
merger.close()
temp_files.append(temp_file.name)
# 合并所有臨時(shí)文件
final_merger = PdfMerger()
for temp_file in temp_files:
final_merger.append(temp_file)
final_merger.write(output_path)
final_merger.close()
finally:
# 清理臨時(shí)文件
for temp_file in temp_files:
try:
os.unlink(temp_file)
except:
pass
2. 常見問題與解決方案
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 加密PDF無法合并 | 文件受密碼保護(hù) | 先解密或使用merger.append(pdf, password='密碼') |
| 中文字符亂碼 | 編碼問題 | 確保使用正確的編碼打開文件 |
| 內(nèi)存不足 | 文件太大 | 使用分批處理或增加內(nèi)存 |
| 權(quán)限錯(cuò)誤 | 文件被占用或無權(quán)限 | 檢查文件權(quán)限,確保文件未被其他程序打開 |
3. 錯(cuò)誤處理最佳實(shí)踐
def safe_merge_pdfs(pdf_list, output_path):
"""帶有完善錯(cuò)誤處理的合并函數(shù)"""
import traceback
try:
# 參數(shù)驗(yàn)證
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}")
# 執(zhí)行合并
merge_pdfs_pypdf(pdf_list, output_path)
return True
except PermissionError:
print("錯(cuò)誤:文件訪問被拒絕,請(qǐng)檢查權(quán)限")
return False
except MemoryError:
print("錯(cuò)誤:內(nèi)存不足,嘗試分批處理")
return False
except Exception as e:
print(f"未知錯(cuò)誤: {str(e)}")
if DEBUG_MODE:
traceback.print_exc()
return False
高級(jí)功能擴(kuò)展
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):
# 這里可以添加自定義的頁碼繪制邏輯
# 實(shí)際實(shí)現(xiàn)需要使用reportlab等庫繪制文本
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):
# page_range格式: "1-3,5,7-9"
merger.append(pdf, pages=page_range)
merger.write(output_path)
merger.close()
總結(jié)
在本文中,我們?nèi)娼榻B了使用Python合并PDF文件的多種方法:
關(guān)鍵選擇建議
庫選擇:
- 新項(xiàng)目:使用 pypdf(活躍維護(hù),API現(xiàn)代化)
- 現(xiàn)有項(xiàng)目:如果使用PyPDF2,考慮遷移到pypdf
- 特殊需求:考慮pdfrw或其他專業(yè)庫
性能考慮:
- 小文件(<100MB):直接合并
- 大文件(>100MB):考慮分批處理
- 超大文件(>1GB):可能需要專門的PDF處理工具
功能擴(kuò)展:
- 添加書簽:提升用戶體驗(yàn)
- 錯(cuò)誤處理:確保程序穩(wěn)定性
- 進(jìn)度顯示:提高用戶友好性
最佳實(shí)踐
- 始終驗(yàn)證輸入文件:檢查文件是否存在、是否可讀
- 添加完善的錯(cuò)誤處理:處理各種異常情況
- 提供進(jìn)度反饋:特別是處理大量文件時(shí)
- 內(nèi)存管理:及時(shí)關(guān)閉文件句柄,考慮分批處理
- 輸出優(yōu)化:考慮添加書簽、元數(shù)據(jù)等
應(yīng)用場(chǎng)景
- 文檔整理:合并掃描的文檔片段
- 報(bào)告生成:合并多個(gè)章節(jié)或部分
- 批量處理:自動(dòng)化工作流中的PDF處理
- 文檔分發(fā):準(zhǔn)備統(tǒng)一的交付文件
通過本文提供的代碼和最佳實(shí)踐,你可以輕松構(gòu)建適合自己需求的PDF合并工具。無論是簡單的腳本還是復(fù)雜的應(yīng)用程序,Python都能提供強(qiáng)大的PDF處理能力。
提示:本文所有代碼均經(jīng)過測(cè)試,可在Python 3.6+環(huán)境中運(yùn)行。建議在實(shí)際使用前,根據(jù)具體需求進(jìn)行調(diào)整和優(yōu)化。
以上就是Python合并多個(gè)PDF文件的完整指南與實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Python合并多個(gè)PDF文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python安裝后測(cè)試連接MySQL數(shù)據(jù)庫方式
這篇文章主要介紹了Python安裝后測(cè)試連接MySQL數(shù)據(jù)庫方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
Python中判斷subprocess調(diào)起的shell命令是否結(jié)束
這篇文章主要介紹了Python中判斷subprocess調(diào)起的shell命令是否結(jié)束的方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04
Python使用pytest高效編寫和管理單元測(cè)試的完整指南
在 Python 開發(fā)生態(tài)中,測(cè)試的重要性不言而喻,對(duì)于初學(xué)者來說,pytest 最直觀的優(yōu)勢(shì)在于代碼量的減少,下面小編就和大家詳細(xì)講講它的具體使用吧2026-01-01

