# Extract Text from PDF in Python A 2026 Guide

Source: https://www.digiparser.com/blog/extract-text-from-pdf-in-python

[See all posts](/blog)

Last updated on April 25, 2026

# Extract Text from PDF in Python A 2026 Guide

[![Pankaj Patidar](https://avatars.githubusercontent.com/u/17493609?v=4)

Pankaj Patidar

@thepantales



](https://x.com/thepantales)

![Extract Text from PDF in Python A 2026 Guide](https://cdnimg.co/676959fc-fff3-440b-8860-da6e53d455e3/663ae72e-b179-48ff-8541-3acd269bf7fb/extract-text-from-pdf-in-python-office-stationery.jpg)

You probably started with a script that looked almost too easy. Open the file, loop through pages, call `extract_text()`, save the output. It works on a sample PDF, then immediately falls apart on the first real invoice from a vendor, the first scanned delivery note from a warehouse, or the first bill of lading with tables packed into tight columns.

That's the key challenge with **extract text from pdf in python** work. The code is often simple. The documents aren't.

In production, PDF extraction stops being a toy problem and becomes a document triage problem. You need to know what kind of PDF you have, what data matters, where the layout will break, and when a custom script is still the right tool. This guide covers the practical stack commonly used: **pypdf** for straightforward native PDFs, **PyMuPDF plus Tesseract** for scanned pages, and **pdfplumber** when tables matter more than plain text.

# Why Basic PDF Text Extraction Often Fails

Most tutorials teach PDF extraction as if every document is a clean report with selectable text. That's not the workload most operations teams deal with. Invoices, purchase orders, delivery notes, and customs paperwork are full of tables, stamps, rotated text, mixed fonts, and low-quality scans.

A common mistake is assuming a PDF is a text document. Many PDFs are really just containers. Some contain a proper text layer. Others contain page images. Some mix both. Your Python code can only extract what's there.

## Native PDFs and scanned PDFs are different problems

A **digitally native PDF** usually contains text objects that libraries can read directly. A **scanned PDF** is often just an image per page. If there's no text layer, a text extraction library won't magically recover the words. You need OCR.

That distinction explains why a script can work perfectly on an internal report and fail on an emailed invoice from a supplier. The report was generated by software. The invoice may have been printed, stamped, scanned, compressed, and re-saved by three different systems.

> **Practical rule:** Before you write parsing logic, determine whether the file has a real text layer, an image layer, or both.

## Tables are where simple scripts break

Even when the PDF is native, layout is the next failure point. Introductory examples usually dump text linearly. Business documents don't read linearly. They use columns, floating labels, nested blocks, and line items arranged by position rather than reading order.

A Python discussion on extracting data from PDFs notes that existing tutorials focus heavily on simple text dumping and miss the harder problem of table extraction for invoices and purchase orders. In that gap, custom code can fail **40-60% on unstructured tables without manual cleanup**, which directly hurts ERP and TMS workflows, as discussed in this [Python PDF extraction thread](https://discuss.python.org/t/how-to-extract-data-from-a-pdf-i-didnt-make/51681).

That's why "it extracted text" is not the same as "it extracted usable data."

## What junior teams usually miss

When this work moves from a notebook to production, the failure modes become predictable:

*   **Wrong tool for the document:** Using plain text extraction on scanned pages.
*   **Lost structure:** Pulling line items as a single blob of text.
*   **Layout assumptions:** Hard-coding x/y or line-order rules that only work for one vendor format.
*   **Silent failure:** Writing empty strings to output and only discovering it later in downstream validation.

If your target documents are reports, contracts with clean text, or generated statements, basic extraction may be enough. If your target documents are operational PDFs, start with the assumption that extraction quality will depend on layout and document origin, not just the library you install.

# The Go-To Method for Simple Text Extraction with pypdf

For clean, digitally created PDFs, **pypdf** is where I'd start. It's mature, pure Python, easy to deploy, and good enough for a large class of text-heavy documents. The library traces back to the first **PyPDF2 release on February 3, 2010**, and its current documentation shows features for layout-preserving extraction modes and visitor functions in the [official pypdf extraction docs](https://pypdf.readthedocs.io/en/stable/user/extract-text.html).

![extract-text-from-pdf-in-python-python-code.jpg](https://cdnimg.co/676959fc-fff3-440b-8860-da6e53d455e3/f2d79f21-83b0-4f72-9ef4-8eeea6417ae6/extract-text-from-pdf-in-python-python-code.jpg)

## A production-safe starting script

Install it first:

`pip install pypdf`

Then use a script like this:

```python
from pathlib import Path
from pypdf import PdfReader

def extract_pdf_text(pdf_path: str) -> str:
    reader = PdfReader(pdf_path, strict=False)
    chunks = []

    for page_number, page in enumerate(reader.pages, start=1):
        try:
            text = page.extract_text() or ""
            chunks.append(f"\n--- PAGE {page_number} ---\n{text}")
        except Exception as exc:
            chunks.append(f"\n--- PAGE {page_number} ERROR ---\n{exc}")

    return "\n".join(chunks)

if __name__ == "__main__":
    pdf_file = "example.pdf"
    output_file = Path("output.txt")

    text = extract_pdf_text(pdf_file)
    output_file.write_text(text, encoding="utf-8")
    print(f"Saved extracted text to {output_file}")
```

This is intentionally boring. That's good. You want boring code in document pipelines.

## Why each part matters

`strict=False` is worth keeping. Real PDFs often contain minor structural issues, and you don't want the whole job to die because one file is slightly malformed.

The page loop lets you isolate failures. If page 7 is broken, you still get output from the rest of the file. That matters when teams process batches and need partial recovery instead of all-or-nothing behavior.

Writing with UTF-8 avoids some avoidable output issues when documents include non-Latin characters or mixed encodings.

## Where pypdf works well

The practical sweet spot is native PDFs with simple layout. A benchmark summary in Nutrient's guide states that **pypdf can achieve 85-95% accuracy on simple layouts**, but its failure rate on scanned documents can be **over 50%**, with common pitfalls including **empty text output in 40% of encrypted PDFs** and **garbled order in 30% of multi-column cases** according to their [Python PDF text extraction write-up](https://www.nutrient.io/blog/extract-text-from-pdf-using-python/).

That aligns with real implementation experience. pypdf is strong when the document is already text-first. It struggles when reading order depends on geometry.

> Don't judge extraction quality by whether the output file contains words. Judge it by whether a human can still follow the original reading order.

## Common fixes before you switch tools

If the output is bad, check these first:

*   **Encrypted files:** Some PDFs open visually in a viewer but still limit extraction behavior. Handle passwords explicitly when needed.
*   **Multi-column documents:** If paragraphs jump left-right unpredictably, the issue isn't missing text. It's ordering.
*   **Forms and invoices:** If field labels and values are split across different positions, plain extraction may flatten them into nonsense.
*   **Scans disguised as PDFs:** If every page returns little or nothing, inspect whether the page is an embedded image.

For native documents, pypdf is still the fastest way to get started. But if your sample set includes scans, dense tables, or vendor-specific layouts, treat this as the first filter, not the whole solution.

# Handling Scanned PDFs and Images with OCR

When a PDF is a scanned image, `extract_text()` won't save you. There's no text layer to read. The fix is **OCR**, which means rendering each page as an image and asking an OCR engine to recognize the text.

A practical stack for this is **PyMuPDF** to render pages and **pytesseract** to run Tesseract OCR. PyMuPDF is fast, lightweight, and very good at turning PDF pages into image data you can feed into OCR tools.

![extract-text-from-pdf-in-python-ocr-scanner.jpg](https://cdnimg.co/676959fc-fff3-440b-8860-da6e53d455e3/a57c481a-c20b-4bdd-9081-7759a0759421/extract-text-from-pdf-in-python-ocr-scanner.jpg)

## Install the OCR stack

You need both Python packages and the Tesseract binary.

Install the Python packages:

`pip install pymupdf pytesseract pillow`

Then install Tesseract on your machine using your OS package manager or installer. If you need a deeper walkthrough for setup details and OCR basics, this guide on [Python Tesseract OCR workflows](https://www.digiparser.com/blog/python-tesseract-ocr) is a useful reference.

## A working OCR pipeline

```python
import io
from pathlib import Path

import fitz
import pytesseract
from PIL import Image

def ocr_pdf(pdf_path: str) -> str:
    doc = fitz.open(pdf_path)
    pages_text = []

    for page_number in range(len(doc)):
        page = doc.load_page(page_number)
        pix = page.get_pixmap()
        img_bytes = pix.tobytes("png")
        image = Image.open(io.BytesIO(img_bytes))

        text = pytesseract.image_to_string(image)
        pages_text.append(f"\n--- PAGE {page_number + 1} ---\n{text}")

    doc.close()
    return "\n".join(pages_text)

if __name__ == "__main__":
    text = ocr_pdf("scanned.pdf")
    Path("ocr_output.txt").write_text(text, encoding="utf-8")
    print("OCR text saved to ocr_output.txt")
```

## What each tool is doing

PyMuPDF opens the PDF and renders each page with `get_pixmap()`. That converts page content into pixels. Tesseract doesn't read PDFs directly in this flow. It reads images.

`pytesseract.image_to_string()` performs the OCR step. If the page contains a blurry scan, a stamp over text, or a mobile phone photo embedded in a PDF, this is the stage where quality is won or lost.

PyMuPDF has broad adoption and rendering speed matters here. Verified data notes that **PyMuPDF has over 100 million PyPI downloads** and can render pages for OCR at **10-50 pages/second**, which is why it's so useful in high-throughput workflows processing large volumes of delivery documents, as described in this [PyMuPDF OCR overview video](https://www.youtube.com/watch?v=Ddk8bA6OWjQ).

## OCR quality depends on preprocessing

Raw OCR works, but document condition matters. When results are weak, don't jump straight to a different library. Improve the image first.

A few practical adjustments often help:

*   **Increase render quality:** Use a higher-resolution pixmap if the source is faint or compressed.
*   **Convert to grayscale:** This can reduce color noise from stamps and backgrounds.
*   **Threshold the image:** High-contrast black text on white background is easier for OCR engines.
*   **Deskew when needed:** Crooked scans cause line breaks and word fragmentation.
*   **Crop irrelevant regions:** Logos, signatures, and page borders can distract OCR.

> OCR is not a text extraction problem. It's an image quality problem that ends in text.

## Where OCR still disappoints

OCR is the right answer for scans, but it doesn't solve structure. It gets characters. It doesn't understand that a number belongs to a row total, or that the second column is quantity.

That's why scanned invoices often need a second stage after OCR: table reconstruction, field detection, or custom parsing. If you only need rough full-text search or archive indexing, OCR output may be enough. If you need reliable field-level data for accounting or logistics operations, OCR alone usually won't be the finish line.

# Extracting Structured Data like Tables with pdfplumber

If the value is in the table, plain text extraction is the wrong output format. You don't want a paragraph. You want rows, columns, headers, and line items that survive export into a CSV or DataFrame.

That's where **pdfplumber** earns its place. It's designed to work with the visual structure of a PDF, not just the text stream.

![extract-text-from-pdf-in-python-table-extraction.jpg](https://cdnimg.co/676959fc-fff3-440b-8860-da6e53d455e3/c3ba9a24-c24b-4877-a55d-673efb0d02c8/extract-text-from-pdf-in-python-table-extraction.jpg)

## Why table extraction needs a specialized library

Invoices and purchase orders often place meaning in alignment. Description, quantity, unit price, and amount are related because they share a row. A generic text extractor may preserve words but lose relationships.

That's why table-aware parsing matters. Verified data states that **pdfplumber achieves a 92% success rate on native PDF tables** and is benchmarked to be **5x faster than PyMuPDF for this task**, with integration into pandas for JSON or CSV export reducing AP errors by **up to 80%** for operations teams in the benchmark summary from this [pdfplumber table extraction article](https://dev.to/rishabdugar/pdf-extraction-retrieving-text-and-tables-together-using-python-14c2).

## A practical extraction example

Install the packages:

`pip install pdfplumber pandas`

Then start with this:

```python
import pdfplumber
import pandas as pd

def extract_tables(pdf_path: str):
    extracted = []

    with pdfplumber.open(pdf_path) as pdf:
        for page_number, page in enumerate(pdf.pages, start=1):
            tables = page.extract_tables()

            for table_index, table in enumerate(tables, start=1):
                if not table:
                    continue

                df = pd.DataFrame(table)
                extracted.append({
                    "page": page_number,
                    "table_index": table_index,
                    "dataframe": df
                })

    return extracted

if __name__ == "__main__":
    tables = extract_tables("invoice.pdf")

    for item in tables:
        page = item["page"]
        idx = item["table_index"]
        df = item["dataframe"]

        print(f"\nPage {page}, Table {idx}")
        print(df)

        df.to_csv(f"page_{page}_table_{idx}.csv", index=False)
```

This script gives you a structured result immediately. That's the key difference. Once the output is a DataFrame, you can clean headers, normalize columns, validate totals, and send data downstream.

## When the default settings aren't enough

Real documents often need more than `extract_tables()`. You may need line-based strategies, custom bounding boxes, or mixed text-plus-table extraction.

Useful adjustments include:

*   **Tune table settings:** `vertical_strategy` and related options help when cell borders are weak or inconsistent.
*   **Process page regions:** Crop to the line-item area if headers and footers confuse detection.
*   **Normalize after extraction:** Clean merged headers, repeated page labels, and blank rows in pandas.
*   **Validate before export:** Ensure columns line up before sending to ERP or accounting systems.

If you export these structures to JSON, pay attention to downstream parsers. Teams regularly turn a valid extraction into a broken payload because of malformed quoting, trailing commas, or inconsistent nesting. This short guide to [common JSON parse errors](https://serverscheduler.com/blog/json-parse-error) is worth bookmarking if your pipeline writes JSON after table extraction.

Later-stage workflows often build on the same pattern shown in this guide to [extract tables from PDF with structured output](https://www.digiparser.com/blog/extract-tables-from-pdf).

A visual walkthrough helps if you want to see how table extraction behaves in practice:

> Tables are not text with extra spaces. They are structure. Treat them that way from the start.

## Where pdfplumber still needs help

pdfplumber is strong on native tables. It gets harder when the document is a scan, when cells are merged unpredictably, or when a vendor changes layout from one week to the next. You can combine it with OCR, but that's also the point where custom code starts accumulating exceptions.

If your process depends on stable line-item extraction from messy business documents, table extraction should be designed as a first-class workflow, not bolted onto a generic text dump.

# Choosing Your Python PDF Extraction Toolkit

Developers often don't need one PDF library. They need a decision rule.

Use the simplest tool that matches the document you receive. If you choose based on a clean sample file, you'll end up rewriting the pipeline after deployment.

![extract-text-from-pdf-in-python-pdf-tools.jpg](https://cdnimg.co/676959fc-fff3-440b-8860-da6e53d455e3/b2836cdc-eec0-4f16-a355-52c421d95031/extract-text-from-pdf-in-python-pdf-tools.jpg)

## Python PDF Extraction Library Comparison

Library

Best For

Handles Scans (OCR)

Handles Tables

Dependencies

pypdf

Digitally born PDFs with simple text layouts

No

Limited

Python package only

PyMuPDF + Tesseract (OCR)

Scanned PDFs, image-heavy pages, documents without a text layer

Yes

Limited without extra parsing

Python packages plus Tesseract

pdfplumber

Native PDFs where structured table extraction matters

Not by itself

Yes

Python packages, often alongside pandas

## A practical selection framework

When choosing a stack, ask these questions in order:

1.  \*\*Can you select text in the PDF viewer?\*\*If yes, start with pypdf or pdfplumber. If not, start with OCR.
2.  \*\*Is the output mostly paragraphs or mostly fields in rows?\*\*Paragraphs favor pypdf. Rows and line items favor pdfplumber.
3.  \*\*Do vendors send inconsistent formats?\*\*If layouts vary constantly, expect custom parsing to become brittle fast.
4.  \*\*Do you need searchable text or validated business data?\*\*Search indexing is easier. Reliable extraction of totals, SKUs, and dates is harder.

## Production habits that matter more than library choice

A lot of extraction projects fail for operational reasons, not parser reasons.

*   **Classify documents early:** Separate native PDFs from scans before extraction.
*   **Log page-level failures:** Don't settle for one generic "parse failed" message.
*   **Keep original files:** You'll need them when debugging edge cases.
*   **Validate outputs:** Check for required fields, expected row counts, or non-empty text before publishing results.
*   **Batch carefully:** Process documents in queues and isolate failures so one bad file doesn't stop the run.

One more rule: build a fallback path. A healthy pipeline can say, "This file doesn't match our scripted assumptions. Route it elsewhere."

That's often the difference between a script that demos well and a system that survives contact with real documents.

# When Python Scripts Are Not Enough The Case for AI Extraction

There's a ceiling on rule-based PDF parsing. You hit it when the script starts collecting special cases faster than it collects value.

That usually happens with operational documents from many sources. One vendor puts the invoice number in the top right. Another hides it in a sidebar. One bill of lading is text-based. The next is a scan with handwritten notes. A script can handle each case individually. It just becomes expensive to maintain.

## The maintenance cost shows up first

pypdf has been around for a long time and processes **millions of PDFs annually**, but its no-template approach still requires custom code when you need smart field detection rather than raw extraction, as noted in the [pypdf documentation](https://pypdf.readthedocs.io/en/stable/user/extract-text.html). In logistics and finance workflows, modern AI platforms can reduce manual entry by **80-90%**, which is why teams eventually move beyond hand-built parsing logic for business-critical documents.

That shift isn't about abandoning Python. It's about using Python where it is still effective and stopping where it becomes a maintenance trap.

## Where dedicated AI tools make sense

A dedicated extraction platform becomes the better choice when:

*   **Layouts vary constantly:** The same field appears in different places across suppliers or carriers.
*   **You need consistent schemas:** Downstream systems expect stable JSON, CSV, or Excel structures.
*   **Accuracy matters operationally:** AP, procurement, and logistics teams can't absorb frequent correction work.
*   **The queue keeps growing:** The team shouldn't spend its week fixing parsers instead of handling exceptions.

If you're evaluating build-versus-buy in an AI-heavy workflow, infrastructure can become part of the discussion too. Teams exploring self-hosted models or document AI pipelines often compare compute options before committing, and this overview of [best cloud GPU providers for AI](https://www.fluence.network/blog/best-cloud-gpu-providers-ai/) is a practical starting point.

For the broader workflow design problem, this article on [document parsing for business operations](https://www.digiparser.com/blog/document-parsing) is a useful framing reference.

> The right time to stop coding is when your parser spends more effort adapting to layout variation than your team saves through automation.

Python is still excellent for preprocessing, routing, validation, and orchestration. But if the core business problem is extracting fields from messy, variable documents at scale, a dedicated AI extraction tool often becomes the more responsible engineering decision.

If your team is tired of writing brittle PDF parsers for invoices, purchase orders, bills of lading, receipts, or resumes, [DigiParser](https://www.digiparser.com/) is worth a look. It's built for operations-heavy document workflows, handles messy scans and layout variation, and returns structured data in formats your ERP, TMS, or accounting stack can directly use.

* * *

[See all posts](/blog)

Automate recurring documents next: [invoice parser](/solutions/invoice-parser), [purchase order parser](/solutions/purchase-order-parser), and [extract data from PDF](/solutions/extract-data-from-pdf) hub.

## Transform Your Document Processing

Start automating your document workflows with DigiParser's AI-powered solution.

[Start Free Trial](https://app.digiparser.com/auth/join)[Schedule Demo](/contact)