Compare
pdfplumber vs AI document parsers
Model-based document parsers promise to read any PDF the way a person would. On born-digital files, pdfplumber is often just as accurate at a small fraction of the time and memory; on scans and messy layouts, the models earn their cost. We measured both cases.
Short answer
| pdfplumber 0.11.10 | docling 2.146.0 | MarkItDown 0.1.9 | |
|---|---|---|---|
| Text, 15 born-digital pages | 1.6 s | 66 s (92 s first run) | 1.5 s |
| Peak memory | 193 MB | 3.4 GB | 159 MB |
| Two-column reading order | 27.7% (default) | 99.6% | 98.9% |
| Tables, three test cases | Exact on all three with settings | Exact on two, 74% of rows on the third | Not compared |
| Scanned invoice, words recovered | 0% alone; 97% after OCRmyPDF | 85% | 0% |
| Install size | 58.7 MB | 1.7 GB | 242.7 MB |
Use pdfplumber for born-digital PDFs where you know the layout or can write a rule for it. Use docling for scans and for documents too varied to write rules for, if you can afford the time and memory. MarkItDown is a convenience wrapper: for PDFs it uses pdfplumber and pdfminer.six underneath.
What we compared
- docling (IBM, MIT licence) runs layout and table-structure models, plus OCR, on each page and returns a structured document you can export to Markdown, HTML or JSON. We used its default pipeline on CPU, which uses RapidOCR (3.10.0) for text in images.
- MarkItDown (Microsoft, MIT) converts many file types to Markdown for language models. It is not model-based for PDFs, and we include it because it is often mentioned in the same breath.
- pdfplumber, with default settings, and where noted with the settings from this site’s guides.
We did not test marker or commercial cloud services. Every figure on this page comes from our runs on one 4-core machine without a GPU (Intel(R) Xeon(R) Processor @ 2.10GHz); docling is much faster with a GPU. Scripts: lab/bench/run_bench.py and lab/bench/scan_parsers.py.
Born-digital PDFs: speed and memory
- pdfplumber1.6 s
- MarkItDown1.5 s
- docling65.5 s 92 s on first run
Source: our benchmark, 2026-11-04, Intel(R) Xeon(R) Processor @ 2.10GHz, 4 cores, no GPU; median of repeated runs after imports
docling took about 41 times as long as pdfplumber and peaked at 3.4 GB. The first run is slower still, because it loads the models. That is the price of running neural networks on every page, and it is why model-based parsing is usually done once, offline, with the result stored.
Reading order
On our two-column article, docling reproduced the reading order almost perfectly (99.6%), and so did every other library (98.9% for pdfminer.six, PyMuPDF and the rest), mostly by following the order in which this PDF draws its text. pdfplumber’s default extract_text() scored 27.7%, because it reads each line straight across both columns. pdfplumber gets the order right with a few lines of column-aware code (multi-column layouts) or with pdfminer’s layout analysis (pdfminer comparison), but you have to ask for it; docling does it unprompted on any layout.
Tables
| Table | pdfplumber, defaults | pdfplumber, guide settings | docling |
|---|---|---|---|
| Ruled (invoice) | 100% rows exact | 100% | 100% |
| Rows ruled, no column lines (bank statement) | No table | 100% | 74% (94% of cells present) |
| Borderless (quarterly results) | No table | 100% | 100% |
docling found all three tables with no settings at all, and was exact on two. On the bank statement it found almost every cell but placed some in the wrong row. pdfplumber found only the ruled table by default, and all three exactly once given the column positions, which takes a few lines per layout (borderless tables). If you process thousands of statements from one bank, those few lines are written once; if every document looks different, docling saves you writing them at all.
Scanned pages
The scanned invoice is an image of the born-digital invoice, so we know exactly what it says. We counted how many of the original’s words each approach recovered, in any order and format, and how many of the table’s cells appear verbatim:
- pdfplumber alone0 % 0% of cells, 0.1 s
- OCRmyPDF, then pdfplumber97 % 95% of cells, 3.5 s
- markitdown0 % 0% of cells, 0.7 s
- docling (default OCR)85 % 100% of cells, 32.1 s
Source: our measurement, 2026-11-04: OCRmyPDF 17.13.0 with Tesseract (--deskew --oversample 300), docling 2.146.0 default pipeline with RapidOCR 3.10.0, MarkItDown 0.1.9; times include start-up and, for docling, model loading; script lab/bench/scan_parsers.py
The two approaches that read the scan made different kinds of mistakes, and they matter differently depending on what you need:
- docling reproduced the line-item table perfectly, every cell in the right place. But it dropped the small print at the bottom (payment terms and IBAN), and it separated the invoice’s label–value pairs: its Markdown reads
Due date INV-2026-0417and lists the dates below, so which date is the due date is lost. It took 32.1 s, including start-up and loading its models; the first time we ran the script, with nothing in the disk cache, the same step took over two minutes. - OCRmyPDF then pdfplumber kept every line where it was on the page, labels next to values, so the crop and search techniques work on it unchanged. Its errors were character-level:
1.5Afor 1.5 A,1mfor 1 m,FeldstraRefor Feldstraße, and a few stray|characters from table rules. Those two unit spacings are the two cells it missed.
docling: | HP-5001 | Stepper motor NEMA 17, 1.5 A | 8 | 14.75 | 118.00 |
OCRmyPDF + pdfplumber: HP-5001 Stepper motor NEMA 17, 1.5A 8 14.75 118.00
pdfplumber and MarkItDown read nothing from a scan on their own: there is no text layer, and neither runs OCR. Adding an OCR step in front of pdfplumber (scanned PDFs) recovered more words than docling here, in a fraction of the time. docling, in turn, kept the table’s structure intact on its own.
MarkItDown is pdfplumber underneath
For PDFs, MarkItDown 0.1.9 opens the file with pdfplumber. Pages that look like tables or forms get its table logic; if no page does, it extracts the whole document with pdfminer.six’s extract_text() instead. It closes each page as it goes, the memory pattern we recommend. Its PDF support is an optional extra, pip install "markitdown[pdf]", which requires pdfplumber 0.11.9 or newer.
So MarkItDown’s PDF output is close to what pdfplumber and pdfminer.six give you directly, already in Markdown. It is a good default when you convert many file types at once; for PDFs alone, using pdfplumber directly gives you control over headings, tables and cropping (PDF to Markdown).
Which to choose
- Born-digital PDFs with known layouts (invoices from your suppliers, statements from your bank, reports from one system): pdfplumber. Fast, exact once tuned, small, deterministic.
- Scans: OCR plus pdfplumber if you need text and fields; docling if you need tables and structure without writing rules.
- Many layouts you cannot write rules for, reading order matters, time and hardware available: docling or another model-based parser.
- Mixed file types into Markdown for an LLM: MarkItDown, knowing that for PDFs it is pdfplumber and pdfminer.six.
A common production setup uses both: try pdfplumber first, check the result (row counts, totals, empty pages), and send only the pages that fail to a model-based parser.