The Cost of Reading PDFs by Hand
Businesses deal with a constant stream of PDFs—contracts, reports, research papers, and financial statements. Manually reading, summarizing, and extracting key information from these documents is incredibly time-consuming.
By combining Claude API's Vision capabilities with Extended Thinking, you can build an application that directly analyzes PDF page images and generates high-quality, deeply reasoned summaries. Below, a working PDF analysis pipeline is built up in Python piece by piece, from page rendering to the final summary.
For Claude API fundamentals, check out our Claude API Chatbot Complete Guide. For details on Extended Thinking, see our Extended Thinking Explainer.
Prerequisites and Setup
What You'll Need
- Python 3.10 or later
- Anthropic API key (get one at console.anthropic.com)
anthropicPython SDK (v0.40+)pdf2image(for PDF-to-image conversion)- Poppler (dependency for pdf2image)
Environment Setup
# Install required packages
pip install anthropic pdf2image Pillow
# On macOS, install Poppler
brew install poppler
# On Ubuntu/Debian
# sudo apt-get install poppler-utilsArchitecture Overview
The application works in three stages:
- PDF → Image Conversion: Convert each PDF page to a PNG image using
pdf2image - Vision Analysis: Use Claude API's Vision to extract text content from each page image
- Extended Thinking Summarization: Analyze the extracted text with Extended Thinking to produce structured, deeply reasoned summaries
This design handles scanned PDFs, complex layouts, charts, and diagrams—capturing visual information that traditional text extraction libraries often miss.
Step 1: Convert PDF Pages to Images
First, let's create a function that converts each PDF page into a base64-encoded image.
import base64
from io import BytesIO
from pdf2image import convert_from_path
from PIL import Image
def pdf_to_base64_images(pdf_path: str, dpi: int = 200) -> list[str]:
"""
Convert each page of a PDF to a base64-encoded PNG image.
Args:
pdf_path: Path to the PDF file
dpi: Image resolution (default: 200)
Returns:
List of base64-encoded image strings
"""
# Convert PDF to list of images
pages = convert_from_path(pdf_path, dpi=dpi)
base64_images = []
for page in pages:
# Convert image to byte stream
buffer = BytesIO()
page.save(buffer, format="PNG")
buffer.seek(0)
# Base64 encode
img_base64 = base64.b64encode(buffer.read()).decode("utf-8")
base64_images.append(img_base64)
return base64_images
# Usage example
# images = pdf_to_base64_images("report.pdf")
# print(f"Total pages: {len(images)}")
# Expected output: Total pages: 12A DPI of 200 offers a good balance between text readability and image size. For PDFs with detailed charts or small tables, bump this up to 300.
Step 2: Extract Page Content with Vision
Now let's use Claude's Vision capability to analyze each page image.
import anthropic
client = anthropic.Anthropic() # Uses ANTHROPIC_API_KEY env variable
def extract_page_content(base64_image: str, page_number: int) -> str:
"""
Extract text content from a PDF page image using Claude Vision.
Args:
base64_image: Base64-encoded page image
page_number: Page number for logging
Returns:
Extracted text content
"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": base64_image,
},
},
{
"type": "text",
"text": (
f"Extract all content from page {page_number} of this PDF. "
"Include all text, tables, figure captions, and equations. "
"Reproduce tables in Markdown format. "
"Describe the content of any figures or charts."
),
},
],
}
],
)
return response.content[0].text
def extract_all_pages(base64_images: list[str]) -> list[dict]:
"""Extract content from all pages."""
results = []
for i, img in enumerate(base64_images, start=1):
print(f"Processing page {i}/{len(base64_images)}...")
text = extract_page_content(img, i)
results.append({"page": i, "content": text})
return results
# Usage example
# pages = extract_all_pages(images)
# print(pages[0]["content"][:200])Sonnet 4.6 provides excellent cost-performance for Vision tasks. For maximum accuracy on complex documents, you can switch to claude-opus-4-6.
Step 3: Generate Deep Summaries with Extended Thinking
def summarize_with_extended_thinking(
pages: list[dict],
thinking_budget: int = 10000,
summary_language: str = "en",
) -> dict:
"""
Generate a deep summary using Extended Thinking.
Args:
pages: List of page content dictionaries
thinking_budget: Maximum thinking tokens
summary_language: Output language ("en" or "ja")
Returns:
Dict with "thinking" and "summary" keys
"""
# Combine all page text
full_text = "\n\n".join(
f"--- Page {p['page']} ---\n{p['content']}" for p in pages
)
lang_instruction = (
"Summarize in English." if summary_language == "en"
else "日本語で要約してください。"
)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=16000,
thinking={
"type": "enabled",
"budget_tokens": thinking_budget,
},
messages=[
{
"role": "user",
"content": (
f"Below is the content of a PDF document ({len(pages)} pages).\n\n"
f"{full_text}\n\n"
"Create a structured summary in the following format. "
f"{lang_instruction}\n\n"
"1. **Executive Summary** (3-5 sentences covering the key takeaways)\n"
"2. **Key Points** (5-10 bullet points)\n"
"3. **Important Data & Figures** (notable numbers and statistics)\n"
"4. **Conclusions & Recommendations** (the document's main conclusions)\n"
"5. **Caveats & Limitations** (easily overlooked details)"
),
}
],
)
thinking_text = ""
summary_text = ""
for block in response.content:
if block.type == "thinking":
thinking_text = block.thinking
elif block.type == "text":
summary_text = block.text
return {"thinking": thinking_text, "summary": summary_text}
# Usage example
# result = summarize_with_extended_thinking(pages, thinking_budget=10000)
# print(result["summary"])Setting thinking_budget to 10,000 tokens provides ample reasoning time for most documents. For academic papers or complex financial reports, consider increasing it to 20,000 for deeper analysis.
Putting It All Together
Here's the complete pipeline combining all three steps:
import sys
import json
from pathlib import Path
def analyze_pdf(pdf_path: str, output_path: str = None) -> dict:
"""
Analyze a PDF file and generate a structured summary.
Args:
pdf_path: Path to the target PDF
output_path: Where to save results (prints to stdout if omitted)
Returns:
Analysis results dictionary
"""
print(f"📄 Loading PDF: {pdf_path}")
# Step 1: PDF → Images
images = pdf_to_base64_images(pdf_path)
print(f"✅ Converted {len(images)} pages to images")
# Step 2: Vision text extraction
pages = extract_all_pages(images)
print(f"✅ Text extraction complete")
# Step 3: Extended Thinking summary
result = summarize_with_extended_thinking(pages)
print(f"✅ Summary generation complete")
# Structure output
output = {
"source": pdf_path,
"total_pages": len(pages),
"pages": pages,
"summary": result["summary"],
"thinking_process": result["thinking"],
}
# Save to file
if output_path:
Path(output_path).write_text(
json.dumps(output, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"💾 Results saved to: {output_path}")
return output
if __name__ == "__main__":
pdf_file = sys.argv[1] if len(sys.argv) > 1 else "document.pdf"
analyze_pdf(pdf_file, output_path="analysis_result.json")Running the Pipeline
# Set your API key
export ANTHROPIC_API_KEY="YOUR_API_KEY"
# Analyze a PDF
python pdf_analyzer.py quarterly_report.pdf
# Expected output:
# 📄 Loading PDF: quarterly_report.pdf
# ✅ Converted 12 pages to images
# Processing page 1/12...
# ...
# ✅ Text extraction complete
# ✅ Summary generation complete
# 💾 Results saved to: analysis_result.jsonCommon Errors and Solutions
Poppler Not Found
pdf2image.exceptions.PDFInfoNotInstalledError
This occurs when Poppler isn't installed. Run brew install poppler (macOS) or apt-get install poppler-utils (Ubuntu) to fix it.
Image Too Large
Claude API has image size limits. Lower the DPI or add resizing logic:
def resize_if_needed(image: Image.Image, max_size: int = 2048) -> Image.Image:
"""Resize image if it exceeds the maximum dimension."""
w, h = image.size
if max(w, h) > max_size:
ratio = max_size / max(w, h)
new_size = (int(w * ratio), int(h * ratio))
return image.resize(new_size, Image.LANCZOS)
return imageExtended Thinking Token Limits
If thinking_budget is too low, the reasoning may be truncated, leading to shallow summaries. Increase the budget if quality feels lacking—but keep in mind that costs scale proportionally.
Summary
In this guide, we built a PDF analysis and summarization application that combines Claude API's Vision capabilities with Extended Thinking. The Vision-based approach handles scanned PDFs, complex layouts, and visual elements that traditional text extraction tools struggle with.
By layering Extended Thinking on top, we get summaries that go beyond simple text conversion—they demonstrate genuine understanding of document structure, key arguments, and nuances. Try integrating this pipeline into your document management or research workflows to dramatically speed up information processing.