The Best Ways to Extract Text From PDF Python Offers Compared

Posted on | Last updated on
The Best Ways to Extract Text From PDF Python Offers Compared

Python gives you four realistic ways to extract text from a PDF, and choosing between them comes down to two questions: does the PDF already contain text, and do you need the layout back or just the words.

Key takeaways

  • Check for a text layer first, since a scan and a text PDF need different tools entirely.
  • The licence row decides more often than the feature rows, because PyMuPDF is AGPL.
  • Use pdfplumber when position carries meaning, and pay for it in speed.
  • Reading order, ligatures, encryption and form fields are what break real documents.
  • Route on pdfinfo or a character count so only scans reach the recognition path.
pypdf pdfplumber PyMuPDF Hosted API
Install size tiny small ~50 MB none
Speed, 100 pages slow slowest fastest network bound
Layout and coordinates no yes, best yes no
Table extraction no yes basic no
Scanned pages no no no with OCR
Licence BSD MIT AGPL or commercial commercial
Runs where anywhere anywhere anywhere any language

The licence row decides it more often than the feature rows. PyMuPDF is the fastest and most capable of the three libraries, and it is AGPL, which means a commercial product either buys a licence or picks something else.

Check whether it is a text problem at all

What recognition does, as distinct from reading an existing text layer, is set out in the OCR task reference.

Before comparing anything, find out which kind of PDF you have. A PDF either carries a text layer or is a picture of a page, and the two need completely different tools.

import fitz  # pymupdf

doc = fitz.open("document.pdf")
chars = sum(len(page.get_text()) for page in doc)
print("text layer" if chars > 50 else "scanned, needs OCR")

Roughly speaking, anything produced by software has a text layer, and anything that went through a scanner or a phone camera does not. Mixed documents are common: a contract with a signed final page is often twenty text pages and one image.

Getting this wrong wastes effort in both directions. Running OCR over a text-layer PDF gives you a slow, error-prone version of text that was already exact. Running an extractor over a scan gives you an empty string and no error.

pypdf

The one to reach for when the requirement is small and the dependency budget is zero.

pip install pypdf
from pypdf import PdfReader

reader = PdfReader("document.pdf")
text = "\n".join(page.extract_text() or "" for page in reader.pages)

The or "" matters. extract_text() returns None on a page it cannot read, and joining None raises rather than skipping.

Pure Python, no system dependencies, installs in seconds. It also handles the neighbouring jobs people usually need next, merging, splitting and reading metadata, which is why it survives despite being the weakest extractor of the three.

What it does not do is layout. Text comes back in the order the PDF stores it, which for a two-column academic paper means the columns interleave line by line. For prose it is fine, and for anything with structure it is not.

pdfplumber

The one to reach for when position matters.

pip install pdfplumber
import pdfplumber

with pdfplumber.open("invoice.pdf") as pdf:
    page = pdf.pages[0]
    text = page.extract_text()
    tables = page.extract_tables()
    words = page.extract_words()      # each with x0, x1, top, bottom

extract_words returning coordinates is what makes it different. When you need the value to the right of a label rather than the next word in reading order, that is the call that gets you there:

label = next(w for w in words if w["text"] == "Total")
value = min(
    (w for w in words if w["x0"] > label["x1"] and abs(w["top"] - label["top"]) < 3),
    key=lambda w: w["x0"],
)

extract_tables works genuinely well on ruled tables and struggles on tables held together by whitespace, which describes most invoices. Budget time for tuning table_settings rather than assuming the defaults will hold.

The cost is speed. pdfplumber builds a full object model per page and is the slowest of the three by a wide margin, which is fine for one invoice and painful for a nightly batch.

PyMuPDF

The fastest, the most capable, and the one with a licence question.

pip install pymupdf
import fitz

doc = fitz.open("report.pdf")
text = "".join(page.get_text() for page in doc)
blocks = doc[0].get_text("blocks")     # position-aware
data = doc[0].get_text("dict")         # full structure, fonts and sizes

It is an order of magnitude faster than the other two on large documents, handles damaged files that make pypdf give up, and renders pages to images when you need to fall back to recognition.

The licence is AGPL. For internal tooling that is usually fine. For a product you distribute or offer as a service, the AGPL’s source obligations extend to the surrounding application, and the answer is either a commercial licence from Artifex or a different library. Decide this before it is embedded in forty modules.

The hosted option

Extraction as a request rather than a dependency, which matters most when the extracting is not happening in Python at all, or when scanned pages are in scope.

With Filestack, a stored file is addressed by handle and the conversion is a task in the URL:

import requests

handle = "jLoemkNMSiyxhKHxo4G0"
text = requests.get(f"https://cdn.filestackcontent.com/output=format:txt/{handle}").text

That returns the text a PDF already carries. No API key goes in the URL and nothing has to be signed, since reading a file you already stored is not a secured operation.

Metadata comes back the same way, which is how you decide whether a document needs recognition before spending anything on it:

info = requests.get(f"https://cdn.filestackcontent.com/pdfinfo/{handle}").json()
print(info["pages"], info["encrypted"], info["hasform"])

For scanned pages the ocr task handles recognition, and that one is secured. It takes your API key and a policy signed with your app secret, and returns HTTP 403 without them. A signed request also returns 403 when the task is not included in your plan, and the document capture and data extraction page sets out which plans carry it. If the documents are a mix, pdfinfo and a character count tell you which route each file takes, and only the scans need signing at all.

What you are buying is the absence of a dependency: no AGPL question, no 50 MB in the image, no version pinning, and the same call from any language. What you are giving up is offline operation and per-page control over layout.

Which to choose

Text-layer PDFs, prose, small dependency budget. pypdf. It is enough, and it is pure Python with no system dependencies.

Invoices, forms, anything where position carries meaning. pdfplumber. The coordinates are the feature, and nothing else gives them to you as directly.

Large volumes, damaged files, internal tooling. PyMuPDF, with the licence understood.

Scanned documents, or extraction from somewhere that is not Python. A hosted ocr api, because the local libraries stop at the text layer and recognition is a different capability.

Many systems end up with two of these: a fast local extractor for the files that carry text, and a recognition path for the rest. Splitting on pdfinfo or a character count is how you route between them without paying for recognition you did not need.

Join the Filestack developer community on Discord

The failure modes that show up in production

Four things break extraction on real documents.

Ligatures and smart quotes. Text comes back with as one character and curly quotes where you expected straight ones. String comparisons against user input then fail for reasons that look impossible. Normalise with unicodedata.normalize("NFKD", text) before matching anything.

Reading order on multi-column pages. Every library returns text in the order the PDF stores it, which is not always the order a human reads it. pypdf interleaves columns, PyMuPDF’s blocks mode gets closer, and pdfplumber lets you sort by coordinates and reconstruct it yourself. If your documents have columns, test this specifically rather than assuming.

Encrypted and permission-locked files. A PDF can be readable but flagged against text extraction. Libraries either raise or silently return nothing depending on which one you use. pdfinfo reports both encrypted and protected, which is worth checking before deciding a document is a scan.

Forms. Field values in an AcroForm are not part of the page text at all. extract_text() returns the labels and blank space where the answers should be. Reading them means going after the form data specifically, through PdfReader.get_fields() in pypdf or page.widgets() in PyMuPDF.

A rough sense of cost

Absolute timings depend on the documents and the machine, but the ordering holds. On a 100-page text-layer PDF, PyMuPDF finishes fastest, pypdf takes several times longer, and pdfplumber is slower again because it builds an object model per page. Measure on your own documents before sizing a batch job.

That gap does not matter for one invoice arriving from a web form. It decides the architecture for a nightly job over fifty thousand documents, where pdfplumber’s per-page cost turns a batch that could run in minutes into one that runs overnight.

The pattern that avoids the choice is to run the fast extractor first and fall back to the detailed one only for pages where the fast result looks wrong, usually judged by character count against page area. Only the pages that fail that check reach the slow path.

Where this ends up

A text layer already contains exact words, so once you have confirmed a document has one, the open question is not which library reads it best but which job the output has to serve.

An archive search index only needs the words in a searchable form, not the invoice’s field positions or the contract’s column order. Invoice processing asks for the opposite: the total and the line items have to land in the right fields, not just appear somewhere in the output, which is why the invoice OCR approach leans on coordinate extraction rather than a flat text dump. A tax season intake pipeline adds a scheduling problem on top of an extraction one, since most of a year’s volume lands in a few weeks.

None of those four need recognition, because the words were never missing in the first place. What they need is a text-layer extractor, or a hosted call, that respects the shape the document already has.

FAQ

Why does my extractor return an empty string with no error?

The PDF is a scan rather than a text-layer document, so there is nothing for an extractor to read. None of the three libraries raise on this. Count the characters first and route anything below the threshold to recognition instead.

Which library should I use if I only need the words?

pypdf. It is pure Python, installs in seconds, and handles merging, splitting and metadata as well. You give up layout, which does not matter for prose and does matter the moment a document has columns.

Is PyMuPDF’s AGPL licence a problem for my product?

It depends on whether you distribute the application or offer it as a service, in which case the source obligations reach the surrounding code. Internal tooling is usually fine. Decide before it is embedded across the codebase, not after.

Why is the total on my invoice appearing in the wrong place?

Because text comes back in the order the PDF stores it, not reading order. Use pdfplumber’s extract_words and match on coordinates, taking the nearest word to the right of the label on the same line.

 

 

Read More →