pytesseract PDF to text: Tesseract cannot read a PDF, so do this
Tesseract does not read PDF files, so pytesseract cannot either. Turn
each page into an image first, then pass the image to pytesseract, and
tell it the dpi. That last part is the trap. On my scanned test
invoice, all 3 libraries I tried gave a page image with no dpi in it.
Without --dpi, Tesseract read 0 of 36 numbers. With
--dpi 300, it read 23 to 25.
The error you get
Give pytesseract the PDF's path, and it fails:
pytesseract.image_to_string("scan.pdf")
pytesseract.pytesseract.TesseractError: (1, 'Error in pixReadStream: Pdf reading is not supported Leptonica Error in pixRead: pix not read: /private/tmp/pytess-pdf-work/invoice-scan.pdf Error during processing.')
The command line says the same thing:
$ tesseract invoice-scan.pdf -
Error in pixReadStream: Pdf reading is not supported
Leptonica Error in pixRead: pix not read: invoice-scan.pdf
Error during processing.
Opening the PDF with Pillow first does not help:
Image.open("scan.pdf") gave
PIL.UnidentifiedImageError: cannot identify image file.
This is not a bug in your install. The Tesseract docs on input formats say it plainly: "Tesseract does not support reading PDF files." It reads images through the Leptonica library: PNG, JPEG, TIFF, JPEG 2000, GIF, WebP, BMP and PNM. The same page adds that Tesseract "does support PDF as an output format", which is why a PDF can come out of it but not go in.
Check for a text layer first
Many PDFs already hold their text. OCR on those only adds mistakes. This check took about a second:
import pymupdf
with pymupdf.open("scan.pdf") as doc:
text = "".join(page.get_text() for page in doc)
if not text.strip():
print("No text layer: OCR it")
On the digital copy of my test invoice, it found 566 characters and all 36 numbers, with no OCR at all. On the scanned copy, it found 0 characters. Only that one needs Tesseract.
The fix: render each page, then OCR it
Any of these 3 works. Each code block below ran on my scanned invoice
exactly as printed. Use the same number in the render and in
--dpi.
pdf2image. It calls Poppler's pdftoppm,
so install Poppler first: brew install poppler on a Mac,
the poppler-utils package on Linux. On Windows, the
pdf2image README
says you must build or download Poppler yourself. MIT license.
import pytesseract
from pdf2image import convert_from_path
pages = convert_from_path("scan.pdf", dpi=300)
text = "\n".join(pytesseract.image_to_string(p, config="--dpi 300") for p in pages)
PyMuPDF. No Poppler, just
pip install pymupdf. Its license is AGPL, or a paid
license from Artifex, so
read the terms
before you ship it in a closed product.
import pymupdf
import pytesseract
from PIL import Image
parts = []
with pymupdf.open("scan.pdf") as doc:
for page in doc:
pix = page.get_pixmap(dpi=300)
img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
parts.append(pytesseract.image_to_string(img, config="--dpi 300"))
text = "\n".join(parts)
pypdfium2. No Poppler either, just
pip install pypdfium2. Apache 2.0 or BSD 3 Clause. Its
scale counts in 72 dpi steps, so 300 dpi is
300 / 72.
import pypdfium2 as pdfium
import pytesseract
pdf = pdfium.PdfDocument("scan.pdf")
text = "\n".join(
pytesseract.image_to_string(page.render(scale=300 / 72).to_pil(), config="--dpi 300")
for page in pdf
)
Always pass --dpi
I rendered the same scanned page with each library and read it twice:
once with no config, and once with --dpi set to the
render dpi. A number counts only when its line reads exactly right.
| Render | No --dpi |
With --dpi |
|---|---|---|
| pdf2image, its default (200 dpi) | 0 of 36 | 24 of 36 |
pdf2image, dpi=300 |
0 of 36 | 23 of 36 |
PyMuPDF, dpi=300 |
0 of 36 | 24 of 36 |
pypdfium2, scale=300/72 |
0 of 36 | 25 of 36 |
Why 0? All 4 images came back with no dpi in them. The
Tesseract manual
says that without --dpi, "the resolution is read from the
metadata included in the image", and if there is none, "Tesseract
tries to guess it". I saved the 200 dpi page as a PNG and ran plain
tesseract on it. It printed
Estimating resolution as 509, more than twice the truth,
and read the table as noise.
Two smaller things from the same run:
- pdf2image renders at 200 dpi unless you say otherwise. The Tesseract docs say it "works best on images which have a DPI of at least 300 dpi". On this page, 300 did not beat 200 (23 against 24), because the scan itself was 300 dpi. Start at 300 and test your own pages.
- The 3 libraries drew the same page a little differently, and got 23, 24 and 25. That is one page, so do not pick a library on that gap. Pick on license and on Poppler.
The 11 to 13 numbers still wrong at 300 dpi are a Tesseract accuracy problem, not a PDF problem. See how to improve Tesseract accuracy and Tesseract digits only.
Or make a searchable PDF
If you want the PDF back with text you can select and search, not a string, pytesseract can write one page at a time:
import pytesseract
from pdf2image import convert_from_path
pages = convert_from_path("scan.pdf", dpi=300)
pdf = pytesseract.image_to_pdf_or_hocr(pages[0], extension="pdf", config="--dpi 300")
with open("scan-searchable.pdf", "wb") as f:
f.write(pdf)
That made a 604,467 byte PDF. Its text layer, read back with
pdftotext -layout, had 23 of 36 numbers on the right
line, the same as the plain text. It is the same OCR, in a different
wrapper. For a PDF with many pages, the Tesseract docs point to
OCRmyPDF, which I tested on
scanned PDF to text.
What I did not test
- PDFs with more than one page, other scans, and photos of paper. This is one page we made, so real pages will score differently.
- Windows, and pdf2image's
poppler_pathsetting. - PyMuPDF's own Tesseract OCR call.
- Speed you can trust. The Mac was busy with other work, and one page took 1.9 to 2.7 seconds from PDF to text.
For many files, or from your code
PDF OCR to Markdown is our Apify Actor. It takes the PDF as it is, checks each page for a text layer, renders the pages that need OCR at 300 dpi, and returns Markdown with the tables kept as tables. On this same scanned invoice, its build 0.1.24 code read 36 of 36 numbers (details on Tesseract digits only).
- $0.006 a page, plus $0.00005 a run at the default 1 GB of memory. Pages that fail are free.
- The Apify free plan lets you try 20 pages a run and 50 a month.
- Pages are read inside the run. They are not sent to any AI service.
Want Markdown from a Python library instead? See PDF to Markdown in Python, where I timed 5 of them on the same invoice.
Questions people ask
Can Tesseract read a PDF?
No. The Tesseract docs say: "Tesseract does not support reading PDF files." Turn each page into an image first. Tesseract can write a PDF, but it cannot read one.
Why does pytesseract say Pdf reading is not supported?
pytesseract hands a file path straight to Tesseract, and Tesseract reads images through the Leptonica library, which does not read PDFs. Render each page to an image with pdf2image, PyMuPDF or pypdfium2, then pass the image.
Do I need Poppler to OCR a PDF with pytesseract?
Only if you use pdf2image, which calls Poppler's pdftoppm. PyMuPDF and pypdfium2 install with pip alone and need no Poppler.
Why does pytesseract read almost nothing from my PDF page?
A page rendered from a PDF carries no dpi, so Tesseract guesses one.
On our 200 dpi test page it guessed 509 and read 0 of 36 numbers. Add
config="--dpi 300" (your real render dpi) and it read 23
to 25.
How do I make a searchable PDF with pytesseract?
Call
pytesseract.image_to_pdf_or_hocr(image, extension="pdf")
on each page image and write the bytes to a file. For a whole PDF in
one step, the Tesseract docs point to OCRmyPDF.
How this was made: Tack, our AI personality, ran Tesseract 5.5.3 (Homebrew, eng) through pytesseract 0.3.13 on a Mac on October 30, 2026, with pdf2image 1.17.0 and Poppler 26.03.0, PyMuPDF 2.0, pypdfium2 5.14.0 and Pillow 13.0.0, and copied every count above from that run. The script and its output are in our research notes. Facts about the tools come from their own pages, all read October 30, 2026: the Tesseract docs on input formats, image quality and command line use, the Tesseract manual, the pytesseract and pdf2image READMEs, the PyMuPDF Page docs and license, and the pypdfium2 page. We made the test invoice. We make the Apify Actor. Tesseract, pytesseract, pdf2image, Poppler, PyMuPDF and pypdfium2 are tools we do not make.