Python批量合并PDF文件的方法詳解
?周一早上九點(diǎn),你的郵箱里躺著十幾封郵件,每封都帶了一個(gè)PDF附件——上周會(huì)議的簽到表、各小組的報(bào)告、財(cái)務(wù)的報(bào)銷憑證。領(lǐng)導(dǎo)在微信群里發(fā)了一條消息:“把這些文件合并成一個(gè),發(fā)到公司群里。”
你打開第一個(gè)PDF,又打開第二個(gè),鼠標(biāo)拖來(lái)拖去,發(fā)現(xiàn)Adobe Acrobat彈出一個(gè)窗口:“您的試用期已結(jié)束。”同事推薦了在線合并工具,你上傳完所有文件,等待進(jìn)度條走到100%,頁(yè)面彈出五個(gè)字:“請(qǐng)付費(fèi)解鎖。”
這不是你的錯(cuò)。PDF合并這件事看起來(lái)簡(jiǎn)單,真上手才發(fā)現(xiàn)處處是坑。在線工具要么限制頁(yè)數(shù),要么加水印,要么悄悄把你的文件傳到不明服務(wù)器。桌面軟件不是收費(fèi)就是臃腫得像個(gè)航空母艦。
你需要的其實(shí)很簡(jiǎn)單:一個(gè)能批量處理、不用花錢、跑起來(lái)就完事的工具。Python剛好能做到,而且代碼少到你不用是程序員也能看懂。
先把你需要的工具裝好
Python合并PDF這件事,全靠一個(gè)叫PyPDF2的庫(kù)。它專門用來(lái)處理PDF,能讀、能寫、能合并、能拆分。
安裝就一行命令:
pip install PyPDF2
如果你用的是Python 3,可能需要安裝PyPDF2的升級(jí)版PyPDF4,用法幾乎一樣。這里用PyPDF2演示,兼容性最好。
裝完之后,打開你的代碼編輯器,新建一個(gè)Python文件,就叫merge_pdf.py。
最簡(jiǎn)單的合并:把所有PDF放在一起
假設(shè)你的桌面上有一個(gè)文件夾叫“待合并”,里面放了三個(gè)PDF文件:報(bào)告1.pdf、報(bào)告2.pdf、報(bào)告3.pdf。你想把它們按順序拼成一個(gè)總報(bào)告。
代碼長(zhǎng)這樣:
import PyPDF2
import os
# 創(chuàng)建一個(gè)空的PDF寫入器
merger = PyPDF2.PdfMerger()
# 定義文件夾路徑
folder_path = r"C:\Users\你的用戶名\Desktop\待合并"
# 獲取文件夾里所有PDF文件,并按文件名排序
pdf_files = [f for f in os.listdir(folder_path) if f.endswith('.pdf')]
pdf_files.sort()
# 逐個(gè)添加到合并器
for pdf in pdf_files:
full_path = os.path.join(folder_path, pdf)
merger.append(full_path)
# 寫出合并后的文件
output_path = os.path.join(folder_path, "合并結(jié)果.pdf")
merger.write(output_path)
merger.close()
print(f"合并完成,文件保存在:{output_path}")
跑完這段代碼,你會(huì)發(fā)現(xiàn)“待合并”文件夾里多了一個(gè)“合并結(jié)果.pdf”,里面按順序包含了所有文件的內(nèi)容。
這段代碼的邏輯很簡(jiǎn)單:PyPDF2.PdfMerger()創(chuàng)建了一個(gè)空容器,相當(dāng)于一張白紙。append()方法把每個(gè)PDF一頁(yè)一頁(yè)地貼上去,就像在打印機(jī)上疊放紙張。最后write()把結(jié)果存下來(lái)。
文件順序亂了怎么辦
上面的代碼用了sort()來(lái)排序,但這只能保證按字母順序排列。如果文件名是“報(bào)告1”、“報(bào)告10”、“報(bào)告2”,sort()會(huì)把“報(bào)告10”排在“報(bào)告2”前面,因?yàn)樽址容^是一個(gè)字符一個(gè)字符來(lái)的。
解決辦法有兩種。
第一種,給文件命名的時(shí)候用數(shù)字編號(hào),比如001_報(bào)告、002_報(bào)告。這樣排序就是按數(shù)字順序,不會(huì)亂。
第二種,自己指定順序:
# 手動(dòng)指定順序
pdf_files = ["簽到表.pdf", "小組報(bào)告.pdf", "財(cái)務(wù)憑證.pdf"]
for pdf in pdf_files:
full_path = os.path.join(folder_path, pdf)
if os.path.exists(full_path):
merger.append(full_path)
else:
print(f"警告:找不到文件 {pdf}")
加一個(gè)判斷,文件不存在的時(shí)候跳過(guò)并給出提示,防止程序崩潰。
只合并某些頁(yè)面
有時(shí)候你不需要合并整個(gè)PDF,只需要其中的幾頁(yè)。比如一個(gè)20頁(yè)的報(bào)告,你只要第3到第7頁(yè)。
PyPDF2允許指定頁(yè)碼范圍:
# 只合并第3到第7頁(yè)(頁(yè)碼從0開始)
merger.append("報(bào)告.pdf", pages=(2, 7))
這里的pages參數(shù)接收一個(gè)元組,第一個(gè)數(shù)字是起始頁(yè)(從0開始),第二個(gè)數(shù)字是結(jié)束頁(yè)(不包含這一頁(yè))。所以(2, 7)表示第3頁(yè)到第7頁(yè),一共5頁(yè)。
如果你想單獨(dú)挑幾頁(yè),可以用另一種方式:
# 先讀取文件
reader = PyPDF2.PdfReader("報(bào)告.pdf")
# 只取第1頁(yè)、第3頁(yè)、第5頁(yè)
pages_to_take = [0, 2, 4]
for page_num in pages_to_take:
merger.append(reader.pages[page_num])
這種方式靈活度更高,你想怎么組合都行。
處理子文件夾里的PDF
真實(shí)的場(chǎng)景往往更復(fù)雜。你的文件可能分散在不同的子文件夾里:財(cái)務(wù)組一個(gè)文件夾、技術(shù)組一個(gè)文件夾、銷售組一個(gè)文件夾。你想把這些文件夾里的所有PDF都合并到一起。
這時(shí)候需要用os.walk來(lái)遍歷文件夾樹:
import PyPDF2
import os
merger = PyPDF2.PdfMerger()
root_folder = r"C:\Users\你的用戶名\Desktop\各部門報(bào)告"
# 遍歷所有子文件夾
for folder_path, subfolders, files in os.walk(root_folder):
for file in files:
if file.endswith('.pdf'):
full_path = os.path.join(folder_path, file)
merger.append(full_path)
print(f"已添加:{full_path}")
merger.write(os.path.join(root_folder, "全部合并.pdf"))
merger.close()
這段代碼會(huì)從根文件夾開始,一層一層往下找,把所有子文件夾里的PDF都翻出來(lái)合并。注意一個(gè)問題:這樣合并的順序是按os.walk的遍歷順序來(lái)的,不是按文件夾名字排序。如果你需要控制順序,可以在添加之前先收集所有文件路徑,排序之后再添加。
加上書簽和目錄
合并后的PDF如果頁(yè)數(shù)太多,翻起來(lái)很痛苦。加上書簽會(huì)友好很多。
你可以把每個(gè)文件的文件名作為書簽插入:
merger = PyPDF2.PdfMerger()
for pdf in pdf_files:
# 記錄當(dāng)前總頁(yè)數(shù),作為書簽的起始位置
merger.append(pdf)
# 獲取文件名(不含擴(kuò)展名)作為書簽名稱
bookmark_name = os.path.splitext(pdf)[0]
# 在最后添加的書簽位置插入書簽
# 這里需要先獲取當(dāng)前總頁(yè)數(shù),PyPDF2的add_bookmark方法需要知道頁(yè)碼
PyPDF2添加書簽的API稍微有點(diǎn)繞。更簡(jiǎn)單的方式是換一個(gè)庫(kù)——pypdf(PyPDF2的現(xiàn)代替代品),它的書簽功能更直觀。
安裝pypdf:
pip install pypdf
然后用pypdf合并并添加書簽:
from pypdf import PdfWriter, PdfReader
writer = PdfWriter()
for pdf in pdf_files:
reader = PdfReader(pdf)
# 記錄添加之前的頁(yè)數(shù)
start_page = len(writer.pages)
# 添加所有頁(yè)面
for page in reader.pages:
writer.add_page(page)
# 添加書簽,指向這個(gè)文件的第一頁(yè)
bookmark_name = os.path.splitext(pdf)[0]
writer.add_outline_item(bookmark_name, start_page)
with open("帶書簽的合并文件.pdf", "wb") as f:
writer.write(f)
這樣生成的PDF,左側(cè)書簽欄里會(huì)列出每個(gè)原文件的文件名,點(diǎn)擊就能跳轉(zhuǎn)到對(duì)應(yīng)位置。
處理加密的PDF
有些PDF設(shè)置了打開密碼,直接合并會(huì)報(bào)錯(cuò)。如果你知道密碼,可以在讀取時(shí)解密:
reader = PyPDF2.PdfReader("加密文件.pdf")
if reader.is_encrypted:
reader.decrypt("你的密碼")
然后把reader的頁(yè)面添加到merger里。
如果密碼不知道,那基本無(wú)解。PDF的加密算法是工業(yè)級(jí)的,直接破解不現(xiàn)實(shí)。你需要先找文件提供方要密碼,或者用專門工具移除密碼——但移除密碼也需要先輸入密碼。
大文件合并時(shí)的內(nèi)存問題
合并幾十個(gè)PDF、幾百頁(yè)文件,PyPDF2跑起來(lái)很快。但如果合并上千頁(yè)的大文件,或者一次合并上百個(gè)PDF,可能會(huì)遇到內(nèi)存不足。
PyPDF2是把所有內(nèi)容加載到內(nèi)存里再寫入的。解決方案是用pypdf的增量寫入模式,或者換用PDFtk這個(gè)命令行工具——它在底層是用C++實(shí)現(xiàn)的,處理大文件比Python高效得多。
如果你堅(jiān)持用Python,可以這樣優(yōu)化:分批次合并,先合并成幾個(gè)中間文件,再把中間文件合并成最終結(jié)果。
# 先每10個(gè)文件合并成一個(gè)臨時(shí)文件
batch_size = 10
temp_files = []
for i in range(0, len(pdf_files), batch_size):
batch = pdf_files[i:i+batch_size]
temp_writer = PyPDF2.PdfMerger()
for pdf in batch:
temp_writer.append(os.path.join(folder_path, pdf))
temp_name = f"temp_{i}.pdf"
temp_writer.write(temp_name)
temp_writer.close()
temp_files.append(temp_name)
# 再合并所有臨時(shí)文件
final_merger = PyPDF2.PdfMerger()
for temp in temp_files:
final_merger.append(temp)
final_merger.write("最終合并.pdf")
final_merger.close()
# 清理臨時(shí)文件
import os
for temp in temp_files:
os.remove(temp)
這種分段合并的方式,內(nèi)存占用會(huì)小很多。
給代碼加一個(gè)圖形界面
代碼寫好了,但每次運(yùn)行都要改文件夾路徑,同事想用又不會(huì)Python。這時(shí)候可以加一個(gè)簡(jiǎn)單的圖形界面,用tkinter(Python自帶的GUI庫(kù))實(shí)現(xiàn)。
import tkinter as tk
from tkinter import filedialog, messagebox
import PyPDF2
import os
def merge_pdfs():
# 讓用戶選擇文件夾
folder = filedialog.askdirectory()
if not folder:
return
# 獲取所有PDF文件
pdf_files = [f for f in os.listdir(folder) if f.lower().endswith('.pdf')]
if not pdf_files:
messagebox.showwarning("提示", "該文件夾中沒有PDF文件")
return
pdf_files.sort()
# 合并
merger = PyPDF2.PdfMerger()
for pdf in pdf_files:
full_path = os.path.join(folder, pdf)
merger.append(full_path)
output_path = os.path.join(folder, "合并結(jié)果.pdf")
merger.write(output_path)
merger.close()
messagebox.showinfo("完成", f"合并完成!\n文件保存在:{output_path}")
# 創(chuàng)建窗口
root = tk.Tk()
root.title("PDF合并工具")
root.geometry("300x150")
btn = tk.Button(root, text="選擇文件夾并合并PDF", command=merge_pdfs, height=3, width=25)
btn.pack(expand=True)
root.mainloop()
保存為pdf_merger_gui.py,雙擊運(yùn)行,會(huì)彈出一個(gè)窗口,點(diǎn)按鈕選擇文件夾,剩下的交給程序。你的同事雙擊就能用,不需要安裝Python環(huán)境嗎?還是需要的——不過(guò)你可以用pyinstaller打包成一個(gè)exe文件,發(fā)給誰(shuí)都能用。
處理掃描件和圖片型PDF
有一種PDF比較特殊:里面不是文字,而是掃描的圖片。這類PDF合并的時(shí)候,上面所有方法都適用,因?yàn)镻yPDF2處理的是頁(yè)面對(duì)象,不管里面是文字還是圖片。
但合并之后可能會(huì)遇到一個(gè)問題——文件特別大。圖片型PDF本來(lái)就大,合并之后更大。如果需要壓縮,可以用另外的庫(kù),比如pypdf的壓縮功能,或者用img2pdf重新生成。
簡(jiǎn)單壓縮的方法:
from pypdf import PdfWriter, PdfReader
reader = PdfReader("大文件.pdf")
writer = PdfWriter()
for page in reader.pages:
# 壓縮頁(yè)面內(nèi)容
page.compress_content_streams()
writer.add_page(page)
with open("壓縮后.pdf", "wb") as f:
writer.write(f)
這個(gè)壓縮力度有限,但聊勝于無(wú)。真正想大幅壓縮圖片型PDF,需要用OCR或?qū)iT的PDF壓縮工具。
遇到報(bào)錯(cuò)怎么辦
合并過(guò)程中最常見的報(bào)錯(cuò)是“PdfReadError: EOF marker not found”。意思是PDF文件可能損壞或不完整。解決辦法是在append之前先驗(yàn)證文件是否能正常讀?。?/p>
def is_valid_pdf(filepath):
try:
with open(filepath, 'rb') as f:
reader = PyPDF2.PdfReader(f)
# 嘗試獲取頁(yè)數(shù),如果出錯(cuò)說(shuō)明文件有問題
_ = len(reader.pages)
return True
except:
return False
# 只添加有效的PDF
for pdf in pdf_files:
full_path = os.path.join(folder_path, pdf)
if is_valid_pdf(full_path):
merger.append(full_path)
else:
print(f"跳過(guò)無(wú)效文件:{pdf}")
另一個(gè)常見報(bào)錯(cuò)是“Permission denied”,表示文件被其他程序打開(比如你在Adobe Acrobat里正看著這個(gè)文件)。關(guān)掉文件再運(yùn)行一次就行。
把合并過(guò)程寫成日志
如果你要合并的文件很多,想記錄哪些成功了、哪些失敗了,可以加一個(gè)日志功能:
import logging
logging.basicConfig(filename='merge_log.txt', level=logging.INFO,
format='%(asctime)s - %(message)s')
for pdf in pdf_files:
try:
full_path = os.path.join(folder_path, pdf)
merger.append(full_path)
logging.info(f"成功添加:{pdf}")
except Exception as e:
logging.error(f"添加失敗:{pdf},錯(cuò)誤信息:{str(e)}")
跑完之后打開merge_log.txt,一目了然。
回到那個(gè)周一的早晨
現(xiàn)在你手頭有了一段能用的Python代碼。你雙擊運(yùn)行,三秒鐘之后,“合并結(jié)果.pdf”出現(xiàn)在文件夾里。你把它發(fā)到群里,領(lǐng)導(dǎo)回了一個(gè)大拇指。
更重要的是,下次再有同事遇到同樣的問題,你不用再解釋“你試試那個(gè)在線工具、注意不要點(diǎn)廣告、合并完記得檢查順序”——直接把代碼扔過(guò)去,或者打包成exe發(fā)給他。
PDF合并這件事,Python幫你做了一次性投入、無(wú)限次復(fù)用的自動(dòng)化。那些浪費(fèi)在下載軟件、比較付費(fèi)方案、擔(dān)心文件泄露上的時(shí)間,都可以省下來(lái)了。
到此這篇關(guān)于Python批量合并PDF文件的方法詳解的文章就介紹到這了,更多相關(guān)Python合并PDF內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
pytorch中常用的乘法運(yùn)算及相關(guān)的運(yùn)算符(@和*)
pytorch是深度學(xué)習(xí)框架,而深度學(xué)習(xí)其實(shí)本質(zhì)就是一大堆矩陣乘法,最后用來(lái)模擬一個(gè)高維擬合函數(shù),下面這篇文章主要給大家介紹了關(guān)于pytorch中常用的乘法運(yùn)算及相關(guān)的運(yùn)算符(@和*)的相關(guān)資料,需要的朋友可以參考下2022-01-01
一鍵搞定python連接mysql驅(qū)動(dòng)有關(guān)問題(windows版本)
這篇文章主要介紹了對(duì)于mysql驅(qū)動(dòng)問題折騰了一下午,現(xiàn)共享出解決方案,需要的朋友可以參考下2016-04-04
django使用F方法更新一個(gè)對(duì)象多個(gè)對(duì)象字段的實(shí)現(xiàn)
這篇文章主要介紹了django使用F方法更新一個(gè)對(duì)象多個(gè)對(duì)象字段的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Python中pandas庫(kù)sort_values()方法的使用
最后去看了有關(guān)于 sort_values 的文檔,成功解決先把單詞出現(xiàn)頻次由高往低依次排序,再把頻次相同的情況下的單詞按照 MD5 值排序這個(gè)問題,下面通過(guò)本文講解下Python中pandas庫(kù)sort_values()方法的使用,感興趣的朋友一起看看吧2023-07-07
cmd運(yùn)行python文件時(shí)對(duì)結(jié)果進(jìn)行保存的方法
今天小編就為大家分享一篇cmd運(yùn)行python文件時(shí)對(duì)結(jié)果進(jìn)行保存的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
基于python實(shí)現(xiàn)計(jì)算兩組數(shù)據(jù)P值
這篇文章主要介紹了基于python實(shí)現(xiàn)計(jì)算兩組數(shù)據(jù)P值,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
Pandas中Series和DataFrame的索引實(shí)現(xiàn)
這篇文章主要介紹了Pandas中Series和DataFrame的索引實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
Python使用MySQLdb for Python操作數(shù)據(jù)庫(kù)教程
這篇文章主要介紹了Python使用MySQLdb for Python操作數(shù)據(jù)庫(kù)教程,詳細(xì)講述了MySQLdb的用法,針對(duì)Python操作MySQL數(shù)據(jù)庫(kù)程序設(shè)計(jì)具有很好的參考借鑒價(jià)值,需要的朋友可以參考下2014-10-10

