0%

65- Working with PDF Files

Extract text, merge documents, split pages, and create PDFs programmatically. Automate document processing workflows.

PDF (Portable Document Format) is everywhere. Invoices, reports, contracts, forms, ebooks. Unlike CSV or JSON, PDFs are designed for printing, not for data extraction. Reading from PDFs and writing to PDFs requires specialized tools.
Python’s PyPDF2 (or its modern fork pypdf) is the standard library for basic PDF manipulation. You can merge PDFs, split pages, rotate pages, extract text, and add metadata. For more advanced tasks (creating PDFs from scratch, adding images, complex layouts), you need reportlab.
This lesson covers both. You will learn to extract text from PDFs, merge multiple PDFs, split a PDF into separate pages, rotate and crop pages, and create simple PDFs from scratch. These skills are essential for document processing automation.

🕯️ Magic Note

PDF is a complex format. It can contain text, images, vector graphics, fonts, annotations, forms, and more. Extracting text from PDFs is not always reliable because text may be stored as glyphs without Unicode mapping. For scanned PDFs, you need OCR (Optical Character Recognition) with pytesseract and pdf2image.

Installing PyPDF2 / pypdf
Install pypdf (the modern, actively maintained fork of PyPDF2).

Bash

# Install pypdf (recommended over PyPDF2)

pip install pypdf

# For creating PDFs from scratch

pip install reportlab

Python

from pypdf import PdfReader, PdfWriter

Extracting Text from a PDF
Read a PDF file and extract all text content.

Python

from pypdf import PdfReader

def extract_text_from_pdf(pdf_path):

“””Extract all text from a PDF file.”””

reader = PdfReader(pdf_path)

# Get number of pages

num_pages = len(reader.pages)

print(f”PDF has {num_pages} pages”)

# Extract text from all pages

full_text = “”

for page_num, page in enumerate(reader.pages, 1):

text = page.extract_text()

full_text += f”\n— Page {page_num} —\n{text}”

return full_text

# Usage

text = extract_text_from_pdf(“document.pdf”)

print(text[:500]) # Print first 500 characters

⚠️ Text extraction from PDFs is not perfect. Scanned PDFs (images) will return empty text. PDFs with custom fonts may return garbled text. For reliable extraction, use pdfplumber or OCR tools.
Merging Multiple PDFs
Combine several PDF files into one document.

Python

from pypdf import PdfWriter, PdfReader

def merge_pdfs(pdf_list, output_path):

“””Merge multiple PDF files into one.”””

writer = PdfWriter()

for pdf_path in pdf_list:

reader = PdfReader(pdf_path)

for page in reader.pages:

writer.add_page(page)

print(f”Added: {pdf_path}”)

# Save merged PDF

with open(output_path, “wb”) as f:

writer.write(f)

print(f”Merged {len(pdf_list)} files into {output_path}”)

# merge_pdfs([“doc1.pdf”, “doc2.pdf”, “doc3.pdf”], “merged.pdf”)

🕯️ Magic Note

Merging PDFs is useful for combining scanned documents, consolidating reports, or creating portfolios. You can merge specific pages from each PDF, not just entire documents.

Splitting a PDF into Separate Pages
Extract each page of a PDF as an individual file.

Python

from pypdf import PdfReader, PdfWriter

from pathlib import Path

def split_pdf(input_pdf, output_folder):

“””Split a PDF into separate files, one per page.”””

reader = PdfReader(input_pdf)

output_path = Path(output_folder)

output_path.mkdir(exist_ok=True)

base_name = Path(input_pdf).stem

for page_num, page in enumerate(reader.pages, 1):

writer = PdfWriter()

writer.add_page(page)

output_file = output_path / f”{base_name}_page_{page_num:03d}.pdf”

with open(output_file, “wb”) as f:

writer.write(f)

print(f”Saved: {output_file}”)

print(f”Split {len(reader.pages)} pages into {output_folder}”)

# split_pdf(“large_document.pdf”, “pages”)

Extracting Specific Pages
Extract a range of pages from a PDF.

Python

from pypdf import PdfReader, PdfWriter

def extract_pages(input_pdf, output_pdf, start_page, end_page=None):

“””Extract a range of pages from a PDF.”””

reader = PdfReader(input_pdf)

writer = PdfWriter()

# Convert to 0-based indexing

start_idx = start_page – 1

end_idx = end_page if end_page else len(reader.pages)

for i in range(start_idx, end_idx):

writer.add_page(reader.pages[i])

with open(output_pdf, “wb”) as f:

writer.write(f)

print(f”Extracted pages {start_page}-{end_page or len(reader.pages)} to {output_pdf}”)

# extract_pages(“document.pdf”, “chapter1.pdf”, start_page=10, end_page=25)

Rotating Pages
Rotate pages in a PDF document.

Python

from pypdf import PdfReader, PdfWriter

def rotate_pages(input_pdf, output_pdf, rotation_degrees=90):

“””Rotate all pages in a PDF.”””

reader = PdfReader(input_pdf)

writer = PdfWriter()

for page in reader.pages:

page.rotate(rotation_degrees)

writer.add_page(page)

with open(output_pdf, “wb”) as f:

writer.write(f)

print(f”Rotated all pages by {rotation_degrees} degrees”)

# rotate_pages(“landscape.pdf”, “portrait.pdf”, rotation_degrees=90)

Adding Metadata to PDF
Set title, author, subject, and keywords for a PDF.

Python

from pypdf import PdfReader, PdfWriter

from datetime import datetime

def add_metadata(input_pdf, output_pdf, metadata):

“””Add metadata to a PDF.”””

reader = PdfReader(input_pdf)

writer = PdfWriter()

# Copy all pages

for page in reader.pages:

writer.add_page(page)

# Add metadata

writer.add_metadata(metadata)

with open(output_pdf, “wb”) as f:

writer.write(f)

print(“Metadata added successfully”)

# Metadata example

metadata = {

“/Title”: “Annual Report 2025”,

“/Author”: “Feloriya Analytics”,

“/Subject”: “Company Performance”,

“/Keywords”: “revenue, growth, metrics”,

“/Creator”: “Feloriya PDF Generator”,

“/Producer”: “Python pypdf”,

“/CreationDate”: f”D:{datetime.now().strftime(‘%Y%m%d%H%M%S’)}”

}

# add_metadata(“report.pdf”, “report_with_metadata.pdf”, metadata)

Creating PDFs with ReportLab
Generate PDFs from scratch using reportlab (more advanced than PyPDF2).

Python

from reportlab.lib.pagesizes import letter, A4

from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle

from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle

from reportlab.lib import colors

from reportlab.lib.units import inch

from datetime import datetime

def create_simple_pdf(output_path, title, content_lines):

“””Create a simple PDF document.”””

doc = SimpleDocTemplate(output_path, pagesize=letter)

styles = getSampleStyleSheet()

story = []

# Add title

title_style = styles[“Title”]

story.append(Paragraph(title, title_style))

story.append(Spacer(1, 0.25 * inch))

# Add date

date_style = ParagraphStyle(name=”Date”, parent=styles[“Normal”])

story.append(Paragraph(f”Generated: {datetime.now().strftime(‘%Y-%m-%d %H:%M:%S’)}”, date_style))

story.append(Spacer(1, 0.25 * inch))

# Add content

for line in content_lines:

story.append(Paragraph(line, styles[“Normal”]))

story.append(Spacer(1, 0.1 * inch))

# Build PDF

doc.build(story)

print(f”PDF created: {output_path}”)

# Usage

content = [

“This is a sample PDF generated with Python.”,

“ReportLab makes it easy to create professional-looking documents.”,

“You can add tables, images, charts, and more.”

]

create_simple_pdf(“sample.pdf”, “Python PDF Generation”, content)

🕯️ Magic Note

ReportLab is the industry standard for generating PDFs from Python. It can create complex layouts with tables, charts, images, and vector graphics. PyPDF2 can only manipulate existing PDFs; ReportLab creates them from scratch.

Creating PDF with Tables
Generate a PDF containing a data table.

Python

from reportlab.lib.pagesizes import letter

from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer

from reportlab.lib.styles import getSampleStyleSheet

from reportlab.lib import colors

from reportlab.lib.units import inch

def create_table_pdf(output_path, title, headers, data):

“””Create a PDF with a formatted table.”””

doc = SimpleDocTemplate(output_path, pagesize=letter)

styles = getSampleStyleSheet()

story = []

# Add title

story.append(Paragraph(title, styles[“Title”]))

story.append(Spacer(1, 0.25 * inch))

# Create table data (headers + rows)

table_data = [headers] + data

# Create table

table = Table(table_data)

# Style the table

table.setStyle(TableStyle([

(“BACKGROUND”, (0, 0), (-1, 0), colors.grey),

(“TEXTCOLOR”, (0, 0), (-1, 0), colors.whitesmoke),

(“ALIGN”, (0, 0), (-1, -1), “CENTER”),

(“FONTNAME”, (0, 0), (-1, 0), “Helvetica-Bold”),

(“FONTSIZE”, (0, 0), (-1, 0), 12),

(“BOTTOMPADDING”, (0, 0), (-1, 0), 12),

(“BACKGROUND”, (0, 1), (-1, -1), colors.beige),

(“GRID”, (0, 0), (-1, -1), 1, colors.black),

]))

story.append(table)

doc.build(story)

print(f”Table PDF created: {output_path}”)

# Usage

headers = [“Name”, “Age”, “City”]

data = [

[“Ali Rezaei”, “25”, “Tehran”],

[“Sara Mohammadi”, “30”, “Shiraz”],

[“Reza Karimi”, “28”, “Isfahan”]

]

create_table_pdf(“table_report.pdf”, “User Information”, headers, data)

Practical Example: Report Generator
Combine data from CSV and generate a formatted PDF report.

Python

import csv

from reportlab.lib.pagesizes import letter

from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle

from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle

from reportlab.lib import colors

from reportlab.lib.units import inch

from datetime import datetime

def generate_report_from_csv(csv_path, output_pdf):

“””Generate a PDF report from CSV data.”””

# Read CSV data

with open(csv_path, “r”, encoding=”utf-8″) as f:

reader = csv.DictReader(f)

rows = list(reader)

fieldnames = reader.fieldnames

if not rows:

print(“No data found”)

return

# Create PDF

doc = SimpleDocTemplate(output_pdf, pagesize=letter)

styles = getSampleStyleSheet()

story = []

# Title

story.append(Paragraph(f”Report: {csv_path}”, styles[“Title”]))

story.append(Spacer(1, 0.2 * inch))

story.append(Paragraph(f”Generated: {datetime.now().strftime(‘%Y-%m-%d %H:%M:%S’)}”, styles[“Normal”]))

story.append(Spacer(1, 0.2 * inch))

story.append(Paragraph(f”Total Records: {len(rows)}”, styles[“Normal”]))

story.append(Spacer(1, 0.3 * inch))

# Data table

headers = fieldnames

table_data = [headers]

for row in rows:

table_data.append([row.get(h, “”) for h in headers])

table = Table(table_data)

table.setStyle(TableStyle([

(“BACKGROUND”, (0, 0), (-1, 0), colors.grey),

(“TEXTCOLOR”, (0, 0), (-1, 0), colors.whitesmoke),

(“ALIGN”, (0, 0), (-1, -1), “CENTER”),

(“FONTNAME”, (0, 0), (-1, 0), “Helvetica-Bold”),

(“GRID”, (0, 0), (-1, -1), 1, colors.black),

]))

story.append(table)

doc.build(story)

print(f”Report generated: {output_pdf}”)

# generate_report_from_csv(“data.csv”, “report.pdf”)

Extracting Text from Scanned PDFs (OCR)
For scanned PDFs, use pdf2image and pytesseract (requires additional setup).

Python

# Note: Requires Tesseract OCR installed on system

# pip install pdf2image pytesseract pillow

from pdf2image import convert_from_path

import pytesseract

from PIL import Image

def ocr_scanned_pdf(pdf_path, dpi=300):

“””Extract text from a scanned PDF using OCR.”””

# Convert PDF pages to images

images = convert_from_path(pdf_path, dpi=dpi)

full_text = “”

for i, image in enumerate(images, 1):

# Perform OCR on each page

text = pytesseract.image_to_string(image, lang=”eng”)

full_text += f”\n— Page {i} —\n{text}”

print(f”Processed page {i}”)

return full_text

# text = ocr_scanned_pdf(“scanned_document.pdf”)

Common Mistakes with PDF Processing
  • Assuming text extraction always works (fonts, encoding issues)
  • Not handling password-protected PDFs (use PdfReader(stream, password=”…”))
  • Forgetting that some PDFs have no text (scanned images require OCR)
  • Loading large PDFs entirely into memory (process page by page)
  • Confusing PyPDF2 with pypdf (pypdf is the modern fork)
  • Expecting to edit existing PDFs easily (creating from scratch is easier)
Check Your Understanding
  • How do you extract text from a PDF?
  • Write code to merge two PDF files into one.
  • How do you split a PDF into separate pages?
  • What is the difference between pypdf and reportlab?
  • How do you handle scanned PDFs (images)?
  • Write a function that rotates all pages of a PDF by 90 degrees.

⚡ Whisper

PDFs are containers. They hold text, images, fonts, and metadata. The pypdf library lets you reach inside those containers. Extract text. Merge documents. Split pages. Rotate. Crop. Add metadata. reportlab lets you create new containers from nothing. Add paragraphs. Build tables. Draw shapes. Generate reports. Together, they give you complete control over PDF documents. Automate invoice generation. Process incoming contracts. Extract data from forms. Create weekly reports. The PDF format is complex but Python makes it manageable. Start with extraction. Then merging. Then creation. Your document workflows will thank you.

Related posts