In almost every office there comes a time when you need to combine ten PDFs into a single file, split a hundred-page document into many separate files or straighten a scan that was fed in upside down. There are online services that do this, but uploading contracts or identity documents to an unknown website is not a good idea. With Python and the pypdf library the same operations run on your own computer, in a repeatable way and with no limit on the number of files.
Installation and first steps
pypdf is a library written entirely in Python, with no external dependencies for the basic operations. You install it with:
pip install pypdf
There are two main objects: PdfReader, which opens an existing file and exposes its pages, and PdfWriter, which builds a new document. Almost every operation consists of reading pages from one or more readers and adding them to a writer.
from pypdf import PdfReader
reader = PdfReader("contract.pdf")
print(len(reader.pages), "pages")
Merging several PDFs into one file
The most common case: a folder of attachments that must be delivered as a single document. Order matters, so it is best to sort them explicitly, for example by name.
from pathlib import Path
from pypdf import PdfWriter
writer = PdfWriter()
for pdf in sorted(Path("attachments").glob("*.pdf")):
writer.append(str(pdf))
with open("bundle.pdf", "wb") as f:
writer.write(f)
The append method adds all the pages of the file and, in recent versions of the library, also keeps its bookmarks. If the files are numbered 1.pdf, 2.pdf, 10.pdf, alphabetical sorting will put 10 before 2: the fix is to use names with leading zeros (01, 02, 10) or to sort by the number extracted from the name.
Splitting a PDF into separate files
The reverse operation is needed, for instance, when the scanner produces a single PDF with all the month's invoices. To create one file per page:
from pypdf import PdfReader, PdfWriter
reader = PdfReader("monthly_scan.pdf")
for number, page in enumerate(reader.pages, start=1):
writer = PdfWriter()
writer.add_page(page)
with open(f"page_{number:03d}.pdf", "wb") as f:
writer.write(f)
Often, however, documents are longer than one page. In that case you define ranges: say pages 1-3 for the first document, 4-4 for the second, 5-9 for the third. A small list of tuples, perhaps read from a spreadsheet filled in by a colleague, makes the script reusable without touching the code.
ranges = [("order_A", 1, 3), ("order_B", 4, 4), ("order_C", 5, 9)]
for name, start, end in ranges:
writer = PdfWriter()
for i in range(start - 1, end):
writer.add_page(reader.pages[i])
with open(f"{name}.pdf", "wb") as f:
writer.write(f)
Note the start - 1: people count pages from 1, Python from 0. It is the most frequent mistake in scripts like these.
Rotating crooked pages
Hurried scans often contain upside-down or sideways pages. Every page has a rotate method that accepts multiples of 90 degrees:
for i, page in enumerate(reader.pages):
if i in {2, 5}:
page.rotate(180)
writer.add_page(page)
The rotation is an instruction to the viewer, not a reprocessing of the image: the file loses no quality and the operation is instantaneous even on heavy documents.
Extracting, reordering and deleting pages
With the same logic you can build a new document by picking only some pages or changing their order. Removing the blank final page that many scanners add amounts to copying every page except the last. Moving the cover to the end means adding the pages from the second onwards first and then the first one.
| Operation | Basic idea |
|---|---|
| Merge | append several files into the same writer |
| Split | A new writer for each range |
| Rotate | rotate(90), rotate(180) on the page |
| Delete | Copy every page except the unwanted ones |
| Reorder | Add the pages in the desired order |
Password-protected PDFs
Some PDFs are encrypted. PdfReader reports this through the is_encrypted attribute; if you know the password, you can unlock the file with reader.decrypt("password") and then carry on as usual. This applies to documents whose password you legitimately have, such as statements downloaded from your own bank: the library is not meant, and must not be used, to get around other people's protections. Likewise, PdfWriter can encrypt the output with the encrypt method, which is useful when the bundle contains personal data and has to travel by email.
Good practices
In my work I always keep originals and results apart: the script reads from an originals folder and writes to a processed folder, never overwriting the source files. Other useful precautions:
- check that the final page count is what you expect by printing
len(writer.pages); - give the output files names that contain a date and a description, so they are easy to find;
- handle damaged files with a
tryblock, otherwise they stop the whole run; - remember that
pypdfmanipulates pages, not text: to change the content of a page you need other tools.
In short
pypdflets you merge, split, rotate and reorder PDFs locally, without uploading them anywhere.PdfReaderreads the pages,PdfWriterbuilds the new document.- Watch the numbering: people start from 1, Python from 0.
- Rotation does not degrade quality and is instantaneous.
- Keep originals and processed files apart and check the final page count.