Extract text from PDFs in bulk, with OCR for scanned pages

Getting text out of a PDF is easy for one file and surprisingly fiddly for a thousand: some have a text layer, some are scans, some are encrypted or not PDFs at all, and a RAG pipeline wants chunks, not one long string. This guide shows the free command-line way with pdftotext and Tesseract, tested on real files, and PDF Text Extractor: PDF to Text, Pages & OCR, an Apify actor that takes a list of PDF links and returns text, per-page text, chunks and metadata, reading scanned pages with OCR only where needed.

Run it on Apify Opens the actor's page on apify.com. A free Apify account is needed to run it.

Do it yourself: pdftotext and Tesseract

Poppler's pdftotext reads the text layer. Tested on 2026-10-12 with the Bitcoin white paper:

curl -sL -o bitcoin.pdf https://bitcoin.org/bitcoin.pdf
pdfinfo bitcoin.pdf | grep -E "Pages|Producer"     # Pages: 9, Producer: OpenOffice.org 2.4
pdftotext -layout bitcoin.pdf - | head -3          # "Bitcoin: A Peer-to-Peer Electronic Cash System"
pdftotext bitcoin.pdf - | wc -w                    # 3568 words

A scanned page has no text layer, so pdftotext returns nothing. Render it and run Tesseract. OCRmyPDF's skew.pdf test file:

pdftotext skew.pdf - | wc -w                      # 0 words: no text layer
pdftoppm -r 300 -gray -png skew.pdf skew           # render at 300 dpi
tesseract skew-1.png - | head -2                   # "The LinnSequencer" / "32 Track MIDI Sequence Recorder"
tesseract skew-1.png - | wc -w                     # 729 words

In Python, pypdf or pypdfium2 do the same. What a script has to add for a real batch:

  • Deciding per page whether OCR is needed (OCR-ing everything is slow and makes text worse on clean pages).
  • Telling apart broken downloads, HTML error pages served as .pdf, encrypted files and empty scans.
  • Cutting text into chunks at paragraph or sentence boundaries with overlap for embeddings.
  • Reading PDF metadata dates such as D:20240306081813-05'00' into ISO dates.
  • Installing Tesseract and its language packs wherever the job runs.

Extract text from many PDFs without code

  1. Open PDF Text Extractor: PDF to Text, Pages & OCR on Apify.
  2. Paste direct PDF links, up to 10,000.
  3. Choose One row per PDF or One row per page, and optionally a Chunk size (for example 1,500 characters with 200 overlap) for RAG.
  4. Leave OCR scanned pages on and pick the languages (English, German, French, Spanish, Italian, Portuguese, Dutch).
  5. Click Start and export JSON, CSV or Excel, or call it from your pipeline through the API.

Text comes from PDFium, the PDF engine in Chrome. Only pages with fewer than 20 non-space characters are rendered at 300 dpi and read with Tesseract 5. A row from a real run on 2026-10-12, text shortened:

{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "status": "ok",
  "pageCount": 6,
  "pagesExtracted": 6,
  "pagesOcr": 0,
  "wordCount": 6516,
  "title": "Form W-9 (Rev. March 2024)",
  "subject": "Request for Taxpayer Identification Number and Certification",
  "producer": "Designer 6.5",
  "creationDate": "2024-03-06T08:18:13-05:00",
  "pdfVersion": "1.7",
  "text": "Form W-9\n(Rev. March 2024)\nRequest for Taxpayer\nIdentification Number and Certification …"
}

Worked examples from our test runs

Run (2026-10-12)Result
Default input: Bitcoin white paper, arXiv 1706.03762 ("Attention Is All You Need"), IRS Form W-93 PDFs, 30 pages, no scans, 1.3 seconds
Per-page rows with 1,000-character chunks, 4 inputs2 PDFs read into 8 page rows; an HTML page returned not_pdf and a missing file http_error, both free
OCRmyPDF's image-only skew.pdf on Apify1 page read with OCR, 740 words, 8 seconds; body text clean, a skewed heading split oddly

The local Tesseract run above found 729 words on the same scan, so the actor's OCR matches a plain Tesseract call; the gain is doing it only where needed and across thousands of files.

What each row contains

FieldWhat it holds
url, finalUrl, status, httpStatusWhat was requested and what happened; status is ok or why the file was not read
text, textLength, wordCountFull text, pages separated by a blank line
pageCount, pagesExtracted, pagesOcr, pagesWithoutTextPage statistics
title, author, subject, keywords, creator, producerDocument information
creationDate, modificationDate, pdfVersion, fileSizeBytesISO dates with the PDF's time zone, version, size
pages, chunksOptional per-page texts and RAG chunks
page, pageText, pageOcrIn one-row-per-page mode

Honest limits

  • Tables become text lines, not cells, and no Markdown headings are generated. For table extraction, use a layout-aware tool such as Camelot.
  • Multi-column layouts usually come out column by column, following the PDF's text objects.
  • Encrypted PDFs are reported as encrypted and skipped; handwriting is not supported; OCR quality depends on the scan.
  • Files must be at a public URL (no uploads, no logins); 50 MB per file by default, 200 MB at most.
  • Downloads follow each site's robots.txt and do not get around bot protection.

All input fields

Generated from the actor's live input schema (build 1.0.4).

FieldTypeDefaultWhat it does
PDF URLs required
urls
arrayDirect links to PDF files, one per line. Up to 10,000 per run.
Rows
outputMode
stringdocumentOne row per PDF (full text in 'text'), or one row per page (text in 'pageText', with the PDF's metadata repeated).
Options: document, pages
Add per-page texts to each PDF row
includePageTexts
booleanfalseAdds 'pages': [{page, text, ocr}] in 'One row per PDF' mode.
Chunk size for RAG (characters)
chunkSize
integer00 = off. Otherwise adds 'chunks' (or 'pageChunks' per page): pieces of at most this many characters, cut at paragraph, sentence or word boundaries.
Chunk overlap (characters)
chunkOverlap
integer200Characters repeated between consecutive chunks (at most half the chunk size).
OCR scanned pages
ocr
booleantruePages with fewer than 20 characters of embedded text are rendered at 300 dpi and read with Tesseract OCR.
OCR languages
ocrLanguages
array["eng"]Languages in the scanned documents. More languages are slower.
Options: eng, deu, fra, spa, ita, por, nld
Maximum pages per PDF
maxPagesPerPdf
integer500Pages after this are skipped (the row says so).
Advanced
Maximum file size (MB)
maxFileSizeMb
integer50Larger files are reported as 'too_large' and not charged.
Parallel downloads
maxConcurrency
integer4PDFs downloaded and read at the same time. At most 2 requests run against the same host.
Request timeout (seconds)
requestTimeoutSecs
integer60How long to wait for each download.
Retries
maxRetries
integer2Retries for network errors and HTTP 429/5xx answers, with backoff. Retry-After is honoured up to 30 s.

Call it from code

Start a run and get the results in one HTTP request with the Apify API (your token is under Console, Settings, API & Integrations):

curl -X POST "https://api.apify.com/v2/acts/adamhaleworks~pdf-text-extractor-ocr/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://bitcoin.org/bitcoin.pdf"],"chunkSize":1000}'

The run's Output tab offers 2 ready-made views; every view exports to CSV, Excel, JSON or XML:

  • Overview: url, status, pageCount, pagesOcr, wordCount, title, author, creationDate, text, warning, error
  • Pages (one row per page mode): url, page, pageCount, pageOcr, pageTextLength, pageText

What it costs

Free to run right now. The actor charges no fee of its own today. Apify bills only the compute a run uses against your account's platform credit, and the free Apify plan includes $5 of usage every month. For scale: building all five of our actors and test-running each once on its default input used $0.03 of credit in total (2026-10-12).

Planned: pay-per-event pricing of $0.002 per PDF read ($2 per 1,000), $0.003 per scanned page read with OCR ($3 per 1,000) once the account's payout setup is finished. This page and the Apify page will show the change, and Apify notifies existing users before a price change takes effect.

At the planned prices, the 3-PDF default run would cost $0.006 and the scanned page another $0.003. Failed downloads, non-PDFs, encrypted or broken files, PDFs with no text and OCR that finds nothing would not be charged.

FAQ

How do I extract text from a PDF for free?

For one file, pdftotext from Poppler (pdftotext file.pdf out.txt) or a Python library such as pypdf. For scans, render pages with pdftoppm and read them with Tesseract. The commands on this page were tested on real files.

How does it know which pages need OCR?

A page with fewer than 20 non-space characters in its text layer is treated as scanned. Only those pages are rendered at 300 dpi and read with Tesseract, so normal PDFs stay fast and are not charged for OCR.

Can I use the output for RAG?

Yes. Set a chunk size and overlap and each PDF (or page) gets chunks cut at paragraph, sentence or word boundaries, ready to embed.

Which languages does OCR support?

English, German, French, Spanish, Italian, Portuguese and Dutch; you can combine several.

Can I upload PDFs instead of links?

Not directly. Put the files anywhere that gives a public download link without a login, and paste those links.

Try it on your own data

Paste PDF links, get clean text, per-page text, RAG-ready chunks and metadata (title, author, dates, page count). Scanned pages are read with Tesseract OCR in 7 languages. Failed, encrypted and empty files are free. Free while in launch; planned price $2 per 1,000 PDFs.

Open PDF Text Extractor: PDF to Text, Pages & OCR Opens the actor's page on apify.com. A free Apify account is needed to run it.