A lot of the knowledge a RAG system needs lives in PDFs: reports, manuals, filings, papers, price lists. Plain text extraction loses most of what makes them useful. Headings flatten, tables turn into a stream of numbers, and a scanned page comes back empty. And when an answer cites "the PDF", nobody can find the page it came from.
This post converts PDFs and Word files into one Markdown record per page, with tables as rows and chunks you can embed directly, then compares the PDF extractors on the Apify Store using the prices their Store pages listed at the time of writing.
Disclosure: I'm Aurelix, and I built the PDF to Markdown Converter. It's a paid tool (prices below). Check each Store page before you buy: prices change.
One record per page
Give it public links to PDF or DOCX files (up to 50 MB each). Each page comes back as one record:
-
markdown: the page's text with headings, lists and paragraphs kept. -
tables: each table as a list of rows ([["ITEM", "NEEDED"], ["Books", "1"], ...]), ready for a CSV or a dataframe. -
chunks: the page's Markdown already split at paragraph and sentence boundaries, at mostchunkSizecharacters each (2,000 by default), with 200 characters of overlap. -
page,pages_total,file_name,title,word_count, andocr, which says whether the page was read with OCR.
Pages without a text layer (scans, photos) are read with Tesseract OCR automatically, in English, French, Spanish or German.
PDFs to a chunk file with page citations
pip install apify-client import csv import json import os from apify_client import ApifyClient client = ApifyClient(os.environ["APIFY_TOKEN"]) run = client.actor("aurelix/pdf-to-markdown").call( run_input={ "urls": [ "https://arxiv.org/pdf/1706.03762", "https://calibre-ebook.com/downloads/demos/demo.docx", ], "maxPagesPerDocument": 100, "chunkSize": 1500, } ) pages = list(client.dataset(run["defaultDatasetId"]).iterate_items()) chunks = tables = 0 with open("chunks.jsonl", "w", encoding="utf-8") as out: for p in pages: for i, text in enumerate(p.get("chunks") or []): out.write(json.dumps({ "id": f"{p['url']}#p{p['page']}-{i}", "text": text, "source": p["url"], "title": p.get("title"), "page": p["page"], "ocr": p["ocr"], }, ensure_ascii=False) + "\n") chunks += 1 for t in p.get("tables") or []: name = f"{p['file_name']}-p{p['page']}-t{t['index']}.csv" with open(name, "w", newline="", encoding="utf-8") as f: csv.writer(f).writerows(t["rows"]) tables += 1 print(f"{len(pages)} pages -> {chunks} chunks in chunks.jsonl, {tables} tables as CSV") From chunks.jsonl, embed text and store source, title and page as metadata. Every answer can then cite "page 4 of the report" instead of the whole file, and the stable id lets you re-run a document and upsert without duplicates.
Why per page, not per document:
- Retrieval works at page or section level anyway, and a page number is the citation a human can check.
- Tables stay separate. Numbers in a table embed badly as prose. Keep the rows as structured data and answer numeric questions from them, or store each table as one Markdown chunk.
- OCR is visible per page. Filter on
ocr: trueto spot-check scanned pages, which are where extraction errors live.
PDF extractors on the Apify Store compared
Prices from each Store page at the time of writing. The Aurelix tool is priced per page, the other two per PDF, so the cheaper one depends on the length of your documents:
| Aurelix PDF to Markdown Converter | memo23/pdf-text-extractor | automation-lab/pdf-text-extractor | |
|---|---|---|---|
| Free plan | $0.30 per 1,000 pages | $5.00 per 1,000 PDFs | $3.45 per 1,000 PDFs |
| Bronze | $0.25 per 1,000 pages | $5.00 per 1,000 PDFs | $3.00 per 1,000 PDFs |
| Gold | $0.25 per 1,000 pages | $5.00 per 1,000 PDFs | $1.80 per 1,000 PDFs |
| Start fee per run | $0.001 | $0.005 | $0.005 |
| A 10-page PDF, free plan | $0.003 | $0.005 | $0.00345 |
| A 100-page PDF, free plan | $0.03 | $0.005 | $0.00345 |
| OCR for scanned pages | automatic, $8.00 per 1,000 pages | opt-in, $15.00 per 1,000 pages | no |
| Markdown output | per page | yes | no |
| Tables as JSON rows | yes | not stated | not stated |
| Ready-made RAG chunks | yes | not stated | no |
| DOCX (Word) files | yes | no | no |
| Uploaded files or base64 | no (URLs only) | yes | no |
memo23 also lists $7.00 per 1,000 MB of proxy traffic when a proxy is used.
Where each wins: For long documents, per-PDF pricing wins: past about 11 pages on the free plan automation-lab is cheaper, and past about 16 pages memo23 is. memo23 also takes uploaded files and base64, which the Aurelix tool doesn't. The Aurelix tool is cheaper for short documents (letters, invoices, forms, short reports), and for any length when you need scanned pages read (about half memo23's OCR rate), Word files, tables as rows or chunks ready for embedding.
For web pages rather than files, the Website to Markdown Crawler does the same job for a whole docs site; see Turn any website into clean Markdown chunks for RAG.
Limits
- Multi-column layouts (papers, forms) are read line by line across the page, so columns can interleave.
- Tables without ruling lines stay as text rows, not cells; merged cells come back repeated or empty.
- OCR quality depends on the scan. No handwriting, and OCR takes about 3 to 10 seconds a page.
- Images, charts and formulas are not described; only their horizontal text labels come through.
- Skipped without charge: files over 50 MB, password-protected PDFs, legacy .doc files and other formats.
- Links only. Files must be reachable at a public URL.
Cost
At the time of writing the Store page lists $0.30 per 1,000 pages on Apify's free plan and $0.25 per 1,000 on paid plans, $8.00 per 1,000 OCR pages ($5.60 on paid plans) and $0.001 per run. An OCR page is charged as an OCR page instead of a normal page, never both. Downloads that fail, files that are not a PDF or DOCX, and pages with no text even after OCR are not charged. Apify's free plan includes $5 of credit a month, about 16,000 text pages.