Unstructured OCR, tested on scanned PDFs
13
words out of 11 pages, strategy auto
One typed page turns OCR off
If any page has a text layer, auto, the default strategy, picks fast. Fast does no OCR.
| Strategy | Words out | Cover words, of 13 |
|---|---|---|
auto (default) |
13 | 13 |
ocr_only |
2,068 | 12 |
hi_res |
2,079 | 13 |
Both OCR strategies found 1,867 of the 1,955 scan words. If a PDF can mix typed and scanned pages, set the strategy yourself. The script I ran, partition.py:
import sys
from unstructured.partition.pdf import partition_pdf
elements = partition_pdf(sys.argv[1], strategy=sys.argv[2], languages=sys.argv[3:] or None)
print("\n\n".join(e.text for e in elements))
On a plain scan, auto works
With no text layer on any page, auto picks ocr_only. On the 10-page scan, auto and ocr_only gave the same bytes.
1,867 of 1,955 words, auto and ocr_only
0 words, fast
hi_res found the same 1,867 words. It downloads a 217 MB layout model on the first run.
Other languages need a Tesseract pack
With deu as the language, both OCR strategies stopped
with Failed loading language 'deu'. Homebrew Tesseract
had only eng and osd. I saved this as lang.sh:
mkdir -p tessdata
curl -sL -o tessdata/deu.traineddata https://github.com/tesseract-ocr/tessdata/raw/main/deu.traineddata
Then I set TESSDATA_PREFIX to that folder and ran
partition.py with deu again.
| Setup | Umlauts, of 206 | Words right |
|---|---|---|
ocr_only, no language |
0 | 1,611 |
ocr_only, deu |
191 | 1,794 |
hi_res, deu |
162 | 1,568 |
hi_res mixed up two columns
One German page is a two-column magazine page. There, hi_res kept far fewer words in the right order.
901 of 972 words in order, ocr_only
675 of 972, hi_res
Both with deu. Our Actor got 954 on the same page.
pip install unstructured is not enough
With plain pip install unstructured, partition.py stopped
at the import: No module named 'pdfminer'.
pip install "unstructured[pdf]" fixed it. That venv was
1.5 GB, with torch.
Unstructured 0.27.37 also sends usage data on import and on each
partition call. DO_NOT_TRACK=1 turns that off. I set it.
Want Markdown out? Use the Actor
Unstructured is free and runs offline. Our OCR Actor gives Markdown from a file URL. On the 11-page file:
curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
| jq -r '.[].markdown' > mixed.md
1,874 of 1,955 scan words, Unstructured 1,867
192 of 206 umlauts, Unstructured 191
It also read 13 of 13 cover words with no strategy to pick. It was not faster: 135 s for the 11 pages. On one German page, Unstructured got one more umlaut. The Actor costs $0.006 a page.
How I tested it
- Scan: the 10 pages of OCR in n8n, proofread Wikisource book scans, 1,955 true words. The 11-page file adds the 13-word typed cover page of AnythingLLM OCR.
- German: the 3 pages of Paperless-ngx OCR, 1,887 words, 206 umlauts.
- Score: true words found in order, with the scorer of the tessdata test. German: words and umlauts right, the scorer of Tesseract German.
- Unstructured 0.27.37 and unstructured-inference 1.6.17 on Python 3.13.11, Homebrew Tesseract 5.5.4, on an arm64 Mac. 2 threads at the lowest priority.
- The code blocks above are the files that ran. The Actor: two real runs, build 0.1.65.
What I did not test
- The Unstructured API and its paid platform.
- Tables (infer_table_structure), a GPU, and PaddleOCR.
- LangChain's UnstructuredPDFLoader.
Sources
Two ways to skip the work
Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.
Questions people ask
Why does Unstructured return no OCR text from my PDF?
If any page has a text layer, the auto strategy picks fast, and fast does no OCR. On my 11-page file, it read only the 13 words of the typed page. Pass strategy="ocr_only" or "hi_res".
Is hi_res better than ocr_only for scanned PDFs?
Not in my test. On a 10-page scan, both found 1,867 of 1,955 words. On a two-column page, hi_res kept 675 of 972 words in order, ocr_only 901.
How do I set the OCR language in Unstructured?
Pass languages=["deu"] and install that Tesseract language pack. Without the pack, partition_pdf stopped with "Failed loading language 'deu'". With it, ocr_only got 191 of 206 German umlauts.