Compare
Choosing a Python PDF library
We ran nine PDF libraries on the same files and scored them against the data those files were generated from. No library wins everything: the fastest ones cannot find tables, the best table readers need Java or tuning, and the AI parser is about forty times slower. Here are the numbers, the method, and a chooser.
Short answer
Pick by job, not by popularity:
- Tables, coordinates, regions in born-digital PDFs: pdfplumber (MIT). Exact on ruled tables; with the right settings it got all three of our test tables right.
- Fast plain text at volume: pypdfium2 or PyMuPDF, 28–54× faster than pdfplumber in our test. PyMuPDF is AGPL unless you buy a licence.
- Merging, splitting, filling and writing PDFs: pypdf. pdfplumber is read-only.
- Tables with good defaults and Java available: tabula-py got all three of our tables right without tuning.
- Markdown for LLMs, scanned pages, irregular layouts: docling, if you can afford 1.7 GB of dependencies and seconds per page.
Answer five questions in the chooser for a recommendation.
The contenders
| Library | What it is | Licence | Writes PDFs | Tables | Installed size* |
|---|---|---|---|---|---|
| pdfplumber 0.11.10 | Geometry and tables on top of pdfminer.six | MIT | No | Yes, tunable | 58.7 MB |
| PyMuPDF 2.1 | Python bindings to the MuPDF C library | AGPL-3.0 or commercial | Yes | Yes | 282.9 MB |
| pypdf 6.21.4 | Pure-Python reader/writer (successor of PyPDF2) | BSD-3-Clause | Yes | No | 3.9 MB |
| pdfminer.six 20260107 | Low-level parser and layout analysis | MIT | No | No | 27.7 MB |
| pypdfium2 5.14.0 | Bindings to Google’s PDFium | Apache-2.0 / BSD-3 | Limited | No | 8.8 MB |
| Camelot 2.0.0 | Table extraction (lattice / stream) | MIT | No | Yes | 356.3 MB |
| tabula-py 2.10.0 | Python wrapper for tabula-java | MIT | No | Yes | 159.5 MB + Java |
| markitdown 0.1.9 | File-to-Markdown converter (uses pdfplumber and pdfminer.six for PDFs; measured with its [pdf] extra) |
MIT | No | No | 242.7 MB |
| docling 2.146.0 | Layout and table models (PyTorch) | MIT (code) | No | Yes, model-based | 1693.8 MB + models |
* Installed size of the package and its whole dependency tree on Linux, Python 3.13, measured with lab/bench/install_sizes.py. Docling also downloaded about 500 MB of model files on first use.
Speed and memory
All text from the 15-page Federal Register document in pdfplumber’s test suite (a real US government PDF), median of five runs, each library in its own process:
- pypdfium20.03 s
- pymupdf0.06 s
- pypdf0.17 s
- pdfminer.six0.56 s
- markitdown1.51 s
- pdfplumber1.6 s
Source: our benchmark, 2026-11-04, Intel(R) Xeon(R) Processor @ 2.10GHz, 4 cores, Python 3.13.18
pdfplumber is the slowest of the classic extractors: it is pure Python on top of pdfminer.six (itself 0.56 s), and it builds a full object model of every page, which is what makes cropping, tables and coordinates possible. markitdown is about as slow for a simple reason: for PDFs it runs pdfplumber itself (details). docling took 65.5 s for the same document once warm (92 s on the first call, which loads its models), so it is not on the chart.
- pypdfium226 MB
- pdfminer.six42 MB
- pypdf55 MB
- pymupdf55 MB
- markitdown159 MB
- pdfplumber193 MB
Source: same runs; maximum resident set size of each worker process, including Python and imports
docling peaked at 3,516 MB. For pdfplumber on long documents, memory grows with the pages you keep open; the performance guide shows how page.close() keeps it flat.
Reading order on a two-column page
The two-column article, scored as word-level similarity to its source text in reading order (100% = identical; running headers and footers cost every library a little):
- docling99.6 %
- pymupdf98.9 %
- pypdf98.9 %
- pdfminer.six98.9 %
- pypdfium298.9 %
- markitdown98.9 %
- pdfplumber27.7 %
Source: our benchmark, 2026-11-04; script lab/bench/run_bench.py
This is the clearest weakness of pdfplumber’s defaults. extract_text() sorts words by position across the whole page, so the two columns are interleaved line by line. The other libraries follow the order in which the PDF draws its text, which on this file is the reading order. With a crop per column, pdfplumber reproduces the source text exactly (shown on multi-column layouts).
Tables
Three tables that represent the common cases, each scored against the data it was generated from. Rows exact is the share of rows reproduced cell for cell; cell recall (in brackets) is the share of cells found anywhere in the output.
| Library | ruled (invoice.pdf) | rows ruled only (bank-statement.pdf, p.1) | no lines (borderless-table.pdf) |
|---|---|---|---|
| pdfplumber (defaults) | 100% (100%) | no table | no table |
| pdfplumber (settings from our guides) | 100% (100%) | 100% (100%) | 100% (100%) |
| PyMuPDF (defaults) | 0% (28%) | 4% (5%) | 0% (3%) |
| PyMuPDF (strategy="text") | 0% (28%) | 0% (5%) | 0% (3%) |
| Camelot lattice | 100% (100%) | no table | no table |
| Camelot stream | 0% (0%) | 0% (0%) | 100% (100%) |
| tabula-py (defaults) | 100% (100%) | 100% (100%) | 100% (100%) |
| docling | 100% (100%) | 74% (94%) | 100% (100%) |
What the numbers say:
- Defaults matter more than libraries. pdfplumber’s defaults only see drawn lines; with the settings shown in our guides (rows-only, no lines) it reproduced all three tables exactly. Camelot needs the right mode per table: lattice for ruled, stream for borderless.
- tabula-py had the best defaults: all three exact, with no settings. It needs a Java runtime and offers less control when it does go wrong.
- PyMuPDF 2.1 found the table structure but garbled the cell text in our runs: spaces dropped and punctuation moved to separate lines (
'HP1042\n-','1840\n.'). We saw the same on the FBI table from pdfplumber’s test suite. Earlier and later versions may differ; check on your files. - docling read the ruled and borderless tables exactly and recovered 94% of the bank-statement cells, merging some rows. It is the only option here that also handles scanned tables, at a large cost in speed and size.
Which one should you use?
Recommendation
Based on our measurements below and each project's licence. Always test on a sample of your own files.