Library choice
A common modern option is pypdf (successor of PyPDF2). Older tutorials use PdfMerger, which newer versions of pypdf have removed. Use PdfWriter as below.
Merge PDFs
from pypdf import PdfWriter
writer = PdfWriter()
writer.append("a.pdf")
writer.append("b.pdf")
with open("merged.pdf", "wb") as f:
writer.write(f)
writer.close()Split a PDF
from pypdf import PdfReader, PdfWriter
reader = PdfReader("input.pdf")
for i, page in enumerate(reader.pages, start=1):
writer = PdfWriter()
writer.add_page(page)
with open(f"page_{i}.pdf", "wb") as f:
writer.write(f)Notes
- encrypted PDFs need extra handling
- layout/text extraction is a different problem (next page)
Merging with the current API
PdfMerger was deprecated and removed in recent pypdf releases, and PdfWriter now does everything. This version works on current pypdf, and lets you pick pages.
from pathlib import Path
from pypdf import PdfWriter
writer = PdfWriter()
for name in ["a.pdf", "b.pdf"]:
writer.append(name) # adds every page
writer.append("c.pdf", pages=(0, 2)) # only pages 1 and 2 of c.pdf
with Path("merged.pdf").open("wb") as f:
writer.write(f)
writer.close()pages=(0, 2) is a range of zero-based page numbers, where the end is not included, like range. So (0, 2) means the first two pages. If your installed pypdf is old and lacks PdfWriter.append, upgrade it with pip install -U pypdf.
Splitting by page range
The split example writes one file per page. Often you want a section instead, such as pages 3 to 5.
from pypdf import PdfReader, PdfWriter
reader = PdfReader("input.pdf")
writer = PdfWriter()
for index in range(2, 5): # pages 3, 4, 5
writer.add_page(reader.pages[index])
with open("section.pdf", "wb") as f:
writer.write(f)Check len(reader.pages) first. Asking for a page that does not exist raises IndexError.
Other useful operations
- Rotate.
page.rotate(90)turns a page clockwise before you add it to the writer. - Metadata.
reader.metadatagives the title and author, andwriter.add_metadata({...})sets them. - Bookmarks.
writer.add_outline_item("Chapter 1", page_number=0)adds a navigation entry. - Encrypted files. Call
reader.decrypt("password")before reading pages.
Common mistakes
- Merging in the wrong order. Files sorted with
sorted(Path(".").glob("*.pdf"))go in name order.10.pdfsorts before2.pdf, so use zero-padded names. - Writing the output into the folder you are globbing. The next run merges the old merged file into the new one.
- Confusing page numbers. Humans count from 1,
pypdffrom 0. - Not closing the file. Use
withfor the output file. - Overwriting an original. Always write to a new name.
Adapting it
Merge every PDF in a folder into one report, split a scanned book into chapters, or extract the first page of many invoices into a summary file. Combine it with the previous lesson to extract text from each part.
Check your understanding
You want the first three pages of a PDF. What page indexes do you use with pypdf?
pypdf uses zero-based indexes, so the first three pages are reader.pages[0], [1] and [2], the same as range(0, 3).
Show answer
B — 0, 1, 2 — pypdf uses zero-based indexes, so the first three pages are reader.pages[0], [1] and [2], the same as range(0, 3).
Files named 1.pdf, 2.pdf and 10.pdf are merged in sorted string order. What is the result order?
Strings compare character by character, so 10.pdf comes before 2.pdf. Use names like 01.pdf, 02.pdf, 10.pdf, or sort by the number.
Show answer
B — 1, 10, 2 — Strings compare character by character, so 10.pdf comes before 2.pdf. Use names like 01.pdf, 02.pdf, 10.pdf, or sort by the number.
Why write the merged file into a different folder from the one you glob for inputs?
A glob for *.pdf also matches your own output. Keep inputs and outputs apart, or exclude the output name.
Show answer
B — The next run would pick up the merged file as an input and include it again — A glob for *.pdf also matches your own output. Keep inputs and outputs apart, or exclude the output name.