Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Document Text Extractor - PDF, DOCX & HTML to Text/Markdown

Run it on Apify: apify.com/clearfetch/document-text-extractor

Turn documents into text you can actually use. Give this Actor links to PDF, DOCX or HTML files and it returns plain text with the real line breaks intact, a markdown version with headings, the text of each PDF page separately, and the document's own metadata. $0.005 per document, plus $0.0005 per page beyond the first 20. Documents it cannot fetch are free.

Why the output is different

Most PDF extraction hands back one long run-on paragraph, because the underlying library returns positioned text fragments rather than lines, and the naive fix is to join them with spaces. This Actor rebuilds the lines from the fragment positions: fragments sharing a baseline become one line, and a vertical gap noticeably larger than the page's usual line spacing becomes a paragraph break. Headings are then inferred by comparing each line's font size with the document's own body text, so a title becomes # and a section becomes ##.

It also tells you when a PDF is a scan. An image-only PDF has pages but no text layer, and returning an empty string for it looks like a bug. Here hasTextLayer is false and a note explains that the file needs OCR.

What data you get

  • Plain text with line and paragraph breaks preserved.
  • Markdown with headings, and with lists, links, quotes, code blocks and tables for DOCX and HTML.
  • Per-page text for PDFs, so you can cite or chunk by page.
  • Metadata: title, author, subject, keywords, creator, producer and creation and modification dates.
  • Counts: pages, words, characters and file size, which is what you need to budget an LLM pipeline.
  • Type detected from the file's own bytes, not its name or the server's content type, so a .docx link that actually serves a PDF is handled correctly.

How to use

  1. Paste document links into Documents, one per line.
  2. Leave the defaults, or set Maximum pages if you only need the start of long files.
  3. Run it. Each document is one row, exportable as JSON, CSV or Excel, or readable from the API.

Input

Field Type Default Description
urls array Links to PDF, DOCX, HTML or text files. Also accepts url and startUrls.
maxPages integer 0 Stop after this many pages of a PDF. 0 reads the whole document.
includeText boolean true Include the plain text.
includeMarkdown boolean true Include the markdown version.
includePages boolean true Include per-page text for PDFs. Turn off for smaller output.
maxConcurrency integer 5 Documents processed in parallel.
timeoutSecs integer 60 Download timeout per document.
proxyConfiguration object off Optional. Not needed for most hosts.

Output example

A 15-page PDF read with maxPages: 6, trimmed here for readability:

{
  "url": "https://arxiv.org/pdf/1706.03762",
  "finalUrl": "https://arxiv.org/pdf/1706.03762",
  "ok": true,
  "type": "pdf",
  "statusCode": 200,
  "title": null,
  "text": "Provided proper attribution is provided, Google hereby grants permission to\nreproduce the tables and figures in this paper solely for use in journalistic or\nscholarly works.\n\nAttention Is All You Need\n\n∗ ∗ ∗ ∗\nAshish Vaswani Noam Shazeer Niki Parmar Jakob Uszkoreit\nGoogle Brain Google Brain Google R …",
  "markdown": "### Provided proper attribution is provided, Google hereby grants permission to\n\nreproduce the tables and figures in this paper solely for use in journalistic or\n\nscholarly works.\n\n# Attention Is All You Need\n\n∗ ∗ ∗ ∗\n\nA …",
  "pages": [
    {
      "page": 1,
      "text": "Provided proper attribution is provided, Google hereby grants permission to\nreproduce the tables and figures in this paper solely for use in journalistic or\nsch …"
    }
  ],
  "pageCount": 15,
  "pagesRead": 6,
  "hasTextLayer": true,
  "characters": 18356,
  "words": 2932,
  "meta": {
    "creator": "LaTeX with hyperref",
    "producer": "pdfTeX-1.40.25",
    "createdAt": "2024-04-10T21:11:43Z",
    "modifiedAt": "2024-04-10T21:11:43Z"
  },
  "notes": [
    "Only the first 6 of 15 pages were read because of the \"maxPages\" setting."
  ],
  "bytes": 2215244,
  "elapsedMs": 509,
  "extractedAt": "2026-09-06T09:41:34.763Z"
}

A document that cannot be fetched is reported and costs nothing:

{
  "url": "https://this-domain-does-not-exist-12345.com/file.pdf",
  "ok": false,
  "statusCode": null,
  "error": "getaddrinfo ENOTFOUND this-domain-does-not-exist-12345.com",
  "errorCode": "ENOTFOUND",
  "elapsedMs": 1111,
  "extractedAt": "2026-09-06T09:41:35.366Z"
}

Pricing

  • $0.005 per document, whatever its format.
  • $0.0005 per page beyond the first 20 of a PDF, because long documents genuinely cost more to read. A 15-page paper is $0.005. A 75-page report is $0.0325. A 200-page book is $0.095.
  • Documents that fail to download are free.

Use cases

  • RAG and LLM pipelines: get clean text and per-page chunks with page numbers you can cite.
  • Contract and report processing: pull text out of filings, tenders and statements at scale.
  • Search indexing: index the contents of PDFs you link to, not just their titles.
  • Migration: convert a library of DOCX files to markdown for a static site or wiki.
  • Research: turn a reading list of papers into plain text for analysis.
  • AI agents: a tool that reads a document and returns its text, with the page count and a warning when the file is a scan.

Integrations

curl -X POST "https://api.apify.com/v2/acts/clearfetch~document-text-extractor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://arxiv.org/pdf/1706.03762"], "includePages": true}'
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("clearfetch/document-text-extractor").call(
    run_input={"urls": ["https://arxiv.org/pdf/1706.03762"]}
)

for doc in client.dataset(run["defaultDatasetId"]).iterate_items():
    if not doc["hasTextLayer"]:
        print(doc["url"], "needs OCR")
        continue
    for page in doc["pages"] or []:
        print(f'--- page {page["page"]} ---')
        print(page["text"][:200])

Works with the Apify integrations for n8n, Make, Zapier, Google Sheets, Slack and webhooks, with scheduled runs, and with AI agents through the Apify MCP server.

FAQ

Does it do OCR? No. Scanned pages need optical character recognition, which is a different and far more expensive job. This Actor detects those files and says so, with hasTextLayer: false, instead of returning an empty string and letting you find out later.

Which formats are supported? PDF, DOCX, HTML and plain text. Legacy .doc, .pptx and .xlsx are not supported yet; ask if you need one.

How faithful is the markdown? For DOCX and HTML it follows the real tags, so headings, lists, links, quotes, code and tables come through. For PDF there are no tags at all, so headings are inferred from font size relative to body text. That works well for papers, reports and books, and less well for heavily designed brochures.

Are password-protected PDFs supported? No. They fail with a clear error rather than returning nothing.

Do I need a proxy? No. A proxy input exists for hosts that block datacenter traffic.

Is this legal? It downloads documents you point it at and extracts their text. Whether you may use a given document is between you and its licence; this Actor does not change that.

Changelog

  • 1.0.0 (2026-09) — first release: PDF, DOCX, HTML and text; line and paragraph reconstruction for PDFs; markdown with inferred headings; per-page text; metadata; scanned-PDF detection.

About

Extract clean text and markdown from PDF, DOCX and HTML documents, with per-page text, metadata and real line breaks.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages