Python Working with PDFs
Table of Contents + β
In the last lesson you learned Web Scraping Basics. There you pulled data out of web pages. PDFs are the same kind of problem in a different shape. A report, an invoice, a bank statement, a contract. They all arrive as PDFs. And the moment you have more than a handful of them, doing things by hand gets slow and boring.
π€ Why Work with PDFs in Code?
Here is the pain. Say Riya gets fifty invoices every month as PDFs. She needs the total from each one. Opening fifty files by hand and copying numbers is a whole afternoon gone. Or Alex has ten separate report pages and the boss wants them as one single file to email.
Python fixes this. You can read the text out of a PDF. You can search it. You can join many PDFs into one. All of it takes just a few lines. The tool most people reach for is pypdf, a small library made just for this.
It is a third-party library, so it does not come with Python. You install it with pip first.
pip install pypdfThat one command downloads pypdf and makes it ready to import in your code.
π Reading a PDF and Pulling Out Text
Think of a PDF like a stack of printed pages. pypdf lets you pick up the stack, count the pages, and read the words off any page you want.
Here is the smallest useful example. We open a file called report.pdf and print how many pages it has.
from pypdf import PdfReader
reader = PdfReader("report.pdf")print(f"This PDF has {len(reader.pages)} pages.")Output
This PDF has 3 pages.Let me walk through what each line does.
from pypdf import PdfReaderbrings in thePdfReaderclass. That is the tool that opens and reads a PDF.PdfReader("report.pdf")opens that file and hands you back a reader object.reader.pagesis a list-like thing holding every page, solen(...)counts them.
Now the part you actually came for. Getting the text. Each page has an extract_text() method that returns the words on that page as a normal string.
This reads the first page and prints its text.
from pypdf import PdfReader
reader = PdfReader("report.pdf")
first_page = reader.pages[0]text = first_page.extract_text()
print(text)Output
Quarterly Sales ReportQ1 2026
Total revenue grew by 12% compared to the previous quarter.The strongest region was North America with 4.2M in sales.Note
The output above is example output. Real PDFs hold all sorts of layouts, so the exact text and spacing you get back will depend on the file you open. Scanned PDFs are pictures of pages, so they hold no real text at all. For those, extract_text() returns an empty string.
reader.pages[0] grabs the first page. Counting starts at zero, so page one is index 0. Then extract_text() reads the words off it. The thing is, PDFs were built for printing, not for clean data. So you often get extra spaces or odd line breaks. That is normal. You clean it up afterwards like any other messy string.
π Reading Every Page at Once
One page is rarely enough. Usually you want the text from the whole document. So you loop over reader.pages and join it together.
This goes through every page, pulls the text, and combines it into one big string.
from pypdf import PdfReader
reader = PdfReader("report.pdf")
all_text = ""for page in reader.pages: all_text += page.extract_text() + "\n"
print(all_text)Output
Quarterly Sales ReportQ1 2026
Total revenue grew by 12% compared to the previous quarter.The strongest region was North America with 4.2M in sales.
Regional BreakdownEurope held steady at 2.1M.Asia Pacific climbed to 3.4M, a new record.
OutlookWe expect continued growth into Q2.We start with an empty string all_text. Then for each page we add its text plus a newline \n so pages do not run into each other. After the loop, all_text holds the whole document. Now it is ready to search or save.
Want to find something specific? Once the text is in a string, plain Python does the rest. This checks whether the word βrevenueβ shows up anywhere in the PDF.
if "revenue" in all_text.lower(): print("Found a mention of revenue.")else: print("No mention of revenue.")Output
Found a mention of revenue.We lowercase the text first with .lower() so the search ignores capital letters. Then the simple in check tells us if the word is there.
π§· Merging Several PDFs into One
Now the other common job. Joining files. Say Alex has chapter1.pdf, chapter2.pdf, and chapter3.pdf and wants one combined book.pdf. pypdf has a PdfWriter for exactly this.
Think of PdfWriter as an empty binder. You add pages into it, then save the binder as a new file.
This takes three PDFs and stitches all their pages into a single output file.
from pypdf import PdfWriter
writer = PdfWriter()
for name in ["chapter1.pdf", "chapter2.pdf", "chapter3.pdf"]: writer.append(name)
with open("book.pdf", "wb") as output_file: writer.write(output_file)
print("Merged into book.pdf")Output
Merged into book.pdfHere is what is happening, step by step.
PdfWriter()makes the empty binder.writer.append(name)adds every page of that file to the binder. Looping over the list does all three in turn.open("book.pdf", "wb")opens a new file for writing in binary mode. The"wb"matters. PDFs are binary files, not plain text.writer.write(output_file)saves everything into it.
Tip
writer.append() takes the whole file. If you only want some pages, use writer.append("report.pdf", pages=(0, 2)) to add just pages one and two. The range stops before the second number, the same way slicing works in Python.
π§° A Real Example: Pulling Invoice Totals
Let us tie it together. Riya has a folder of invoice PDFs and wants the total line from each. We read every PDF in the folder, find the line with βTotalβ, and print it next to the file name.
This loops through a list of invoice files and prints the total from each one.
from pypdf import PdfReader
invoices = ["invoice_001.pdf", "invoice_002.pdf", "invoice_003.pdf"]
for filename in invoices: reader = PdfReader(filename) text = reader.pages[0].extract_text()
for line in text.split("\n"): if "Total" in line: print(f"{filename}: {line.strip()}")Output
invoice_001.pdf: Total: 1,240.00invoice_002.pdf: Total: 890.50invoice_003.pdf: Total: 2,015.75Note
This is example output. The real lines depend on how each invoice is written. The idea stays the same though. Read the page, split it into lines, and keep the one you care about.
For each invoice we read the first page and split the text into separate lines with .split("\n"). Then we look at each line and keep only the one holding the word βTotalβ. .strip() trims off extra spaces so the result looks clean. What used to be an afternoon of copying is now a script that runs in a second.
βοΈ Creating PDFs from Scratch
pypdf reads and joins PDFs, but it does not draw new ones from nothing. When you need to build a fresh PDF, say a generated receipt or a report, reach for reportlab. It is a separate third-party library made for drawing PDFs.
You install it the same way.
pip install reportlabHere is a tiny taste. This creates a brand-new PDF with one line of text on it.
from reportlab.pdfgen import canvas
c = canvas.Canvas("hello.pdf")c.drawString(100, 750, "Hello from Python!")c.save()
print("Created hello.pdf")Output
Created hello.pdfcanvas.Canvas("hello.pdf") opens a blank page. drawString(100, 750, ...) writes text at an x,y spot on the page, measured in points from the bottom-left corner. c.save() writes the file. We will keep reportlab short here. Just know it is the go-to when you need to make PDFs, not just read them.
π§± Which Library for Which Job
A quick map so you grab the right tool.
| You want to⦠| Use |
|---|---|
| Read text from a PDF | pypdf (PdfReader) |
| Merge or split PDFs | pypdf (PdfWriter) |
| Create a new PDF from scratch | reportlab |
| Read a scanned (image) PDF | an OCR tool like pytesseract |
β οΈ Common Mistakes
A few things trip people up early on.
- Forgetting binary mode when writing. PDFs are binary files. Opening with
"w"instead of"wb"corrupts the output.
# β Avoid: text mode breaks the PDFwith open("book.pdf", "w") as f: writer.write(f)
# β
Good: binary modewith open("book.pdf", "wb") as f: writer.write(f)- Expecting text from a scanned PDF. If the PDF is really a photo of a page,
extract_text()gives you an empty string. There is no text in there to read, only an image. You need OCR for that. - Assuming clean formatting. Extracted text often has odd spacing and line breaks. Always clean the string before you trust it.
- Using the wrong page number. Pages start at index
0.reader.pages[1]is the second page, not the first.
β Best Practices
Small habits that keep PDF code tidy.
- Check
len(reader.pages)before reading a page so you never ask for a page that is not there. - Wrap file reads in
try/exceptsince a missing or damaged PDF will raise an error. - Strip and clean extracted text before searching or saving it.
- Pick the right tool. Use pypdf to read and merge, and reportlab to create.
- Always open output PDFs in binary mode with
"wb".
π§© What Youβve Learned
A quick recap of what you can now do.
- β Install and import pypdf, the go-to library for reading PDFs.
- β
Open a PDF with
PdfReaderand count its pages. - β
Pull text off a page with
extract_text(), and loop over every page. - β
Search the extracted text with plain Python like
inand.split(). - β
Merge several PDFs into one with
PdfWriterand.append(). - β Know that reportlab is the tool for creating PDFs from scratch.
- β Spot common traps like text mode, scanned PDFs, and zero-based page numbers.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Which class from pypdf do you use to open and read an existing PDF?
Why: PdfReader opens an existing PDF so you can count and read its pages; PdfWriter is for building a new one.
- 2
What does extract_text() return for a scanned PDF that is really a picture of a page?
Why: A scanned PDF has no real text inside, only an image, so extract_text() returns an empty string and you need OCR instead.
- 3
Why must you open the output file with "wb" when writing a merged PDF?
Why: PDFs are binary, not plain text, so writing them in text mode "w" corrupts the file; "wb" writes the bytes correctly.
- 4
Which page does reader.pages[0] give you?
Why: Page numbering starts at index 0, so reader.pages[0] is the first page and reader.pages[1] is the second.
π Whatβs Next?
You can now read, search, and join PDFs. That is real automation you will use again and again. Next up, learn how to make your scripts run on their own, on a schedule, with no one watching.