Tools,

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:

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

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).

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.