← Any document → text: all libraries · Reader board
Kreuzberg vs markitdown: which one reads more real-world documents?
Kreuzberg 4.10.4 and markitdown 0.1.9 on the same 477 odd real-world files from Apache Tika test documents (bug reports, fuzzers, broken exporters). Measured, not opinion; re-tested by itself on every new PyPI release. Updated 06 November 2026.
Kreuzberg got text from more files: 374 vs 253 of 477. Kreuzberg broke (error, hang or crash) on 6 files that markitdown read; markitdown broke on 3 files that Kreuzberg read.
| Library | Text from | 🔴 Fails where the other reads | 🟡 Empty where the other reads | ⏱ Hangs or crashes | Format not supported | Median ms/file | Total seconds |
|---|---|---|---|---|---|---|---|
| Kreuzberg 4.10.4 | 374 | 6 | 1 | 0 | 4 | <1 | 8.7 |
| markitdown 0.1.9 | 253 | 3 | 4 | 1 | 149 | 125 | 388.2 |
By file type: files read with text
| Type | Kreuzberg | markitdown |
|---|---|---|
| .docx 71 files | 60/71 7 empty 4 failed | 57/71 10 empty 4 failed |
| .pdf 71 files | 45/71 22 empty 4 failed | 44/71 21 empty 6 failed |
| .doc 49 files | 37/49 5 empty 7 failed | not supported |
| .rtf 44 files | 41/44 3 empty | not supported |
| .html 38 files | 34/38 2 empty 2 failed | 36/38 2 empty |
| .xlsx 35 files | 22/35 9 empty 4 failed | 20/35 9 empty 6 failed |
| .txt 30 files | 30/30 | 30/30 |
| .xls 30 files | 18/30 8 empty 4 failed | 20/30 8 empty 2 failed |
| .pptx 29 files | 22/29 6 empty 1 failed | 22/29 6 empty 1 failed |
| .ppt 22 files | 16/22 6 empty | not supported |
| .odt 17 files | 15/17 2 failed | not supported |
| .msg 14 files | 14/14 | 14/14 |
| .eml 9 files | 9/9 | not supported |
| .odp 4 files | not supported | not supported |
| .ods 4 files | 3/4 1 empty | not supported |
| .csv 3 files | 3/3 | 3/3 |
| .epub 3 files | 3/3 | 3/3 |
| .json 2 files | 0/2 2 failed | 2/2 |
| .md 2 files | 2/2 | 2/2 |
Files only Kreuzberg reads (128)
…and 88 more: see each library's page.
Files only markitdown reads (7)
| tika-encoding-detector-icu4j/test_ignore_IBM420.html | Kreuzberg: ParsingError: ParsingError: HTML conversion failed: Invalid input: binary data detected (excess NUL bytes) |
| tika-parser-microsoft-module/jxl.xls | Kreuzberg: ParsingError: ParsingError: Failed to parse Excel file: Xls error: Cfb error: Codepage 5212 not found |
| tika-parser-microsoft-module/testEXCEL_4.xls | Kreuzberg: ParsingError: ParsingError: Failed to parse Excel file: Xls error: Cfb error: Invalid OLE (signature \x09\x04\x06\x00\x00\x00\x10\x00 invalid, not an office doc |
| tika-parser-microsoft-module/testPPT_charts.pptx | Kreuzberg: no text |
| tika-parser-text-module/test_ignore_IBM420.html | Kreuzberg: ParsingError: ParsingError: HTML conversion failed: Invalid input: binary data detected (excess NUL bytes) |
| tika-pipes-json/test-with-unpack.json | Kreuzberg: ParsingError: ParsingError: Failed to parse JSON: trailing characters at line 16 column 1 |
| tika-pipes-json/test.json | Kreuzberg: ParsingError: ParsingError: Failed to parse JSON: trailing characters at line 9 column 1 |
Don't want to run it yourself? I also run PDF & Document to Markdown as a pay-per-use API on Apify (it uses pdfplumber and pypdfium2 for PDFs, markitdown for Office files). Disclosure: that tool is mine; the rankings above come only from the measurements.
What this does not tell you
This compares not breaking: whether each library gets any text out of each file. It does not rank text quality (reading order, tables, headings), and speed is one GitHub-hosted runner, default settings, no OCR switched on. If you need structure for RAG, also look at the output on your own files.
How this is measured
- Each library runs a short recipe from its own docs (board/targets.py):
open the file, get all text. Installed with the extras the docs recommend for reading
(
pypdf[full]: AES decryption, Brotli), nothing else. One worker process per CPU; per file 60 s / 120 s / 180 s / 300 s / 600 s at most (the any-document table allows longer: some tools load models), killed above the memory limit. - Fails where another reads: the library raised an error, hung or crashed on a file from which another library in the same table got text. Files that every library fails on (encrypted, truncated, fuzzed) are not counted against anyone.
- Empty where another reads: no error, but no text. This is often the recipe, not the library: for example
python-docx's paragraphs+tables recipe skips headers, text boxes and nested tables. Check the file list. - Any document → text: a file type the tool's docs don't list, or that the tool itself rejects as unsupported, counts as "not supported", not as a failure (lists in board/libs.json). Returning a file's raw RTF source counts as empty, for every tool. Each tool runs with default settings: no OCR switched on by hand.
- OCR: each image comes with the human-written ground-truth text published with its dataset. Words found is the share of its words that appear anywhere in the engine's output (reading order doesn't matter); an image that fails or comes back empty counts as 0. The ground truth of receipts and document pages covers only their annotated text, so a character error rate would punish engines for reading more than was annotated; it is in the raw results but not ranked. English models, CPU, default settings.
- Speech: each clip comes with its dataset's reference transcript. Both texts go through Whisper's English text normalizer (case, punctuation, "twenty" = "20"); word error rate is all substituted, missing and extra words over all reference words, the usual corpus-level WER. A clip that fails or comes back empty counts all its words as errors. The three Whisper implementations use the same base.en model; each other engine its documented default model. English, CPU, default decoding settings.
- Language detection: each text's language is known from Tatoeba. An answer counts as correct if the library's code matches after mapping variants to ISO 639-1 (zh-cn → zh, nb → no, iw → he). Close languages count as different: Croatian detected as Bosnian or Serbian, or Norwegian as Danish, is wrong. One call per text with default settings and all languages enabled.
- PDF → Markdown: pages from AI2's olmOCR-bench (ODC-BY), scored with its own checks (vendored, Apache-2.0): a sentence must be present, a page header or footer absent, two passages in the right order, a table cell next to the right neighbour. Checks passed is all passed checks over all checks; a page that fails or comes back empty passes none. Math pages are left out (their checks need a browser to render formulas). Default settings: whatever OCR a tool does on its own, it does.
- Text quality (order, tables, layout) is not ranked in the other tables; they are about not breaking. The raw results have every output's size and hash.
- Same files for every version: the test collections are pinned to one commit. GitHub-hosted runner, so times are comparable within one table, roughly.