{"post":{"slug":"how-to-parse-a-pdf-2026","title":"How to parse a PDF in 2026","excerpt":"PDF parsing remains surprisingly tricky — text extraction, table detection, and scanned-image handling all need different approaches. This guide maps the right tool to your use case.","category":"code","date":"February 25, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">P</span>DF is one of the most common file formats in the wild and one of the most frustrating to programmatically extract data from. The format was designed for printing, not data interchange — text may be stored as individual character glyphs with no word boundaries, tables are usually floating boxes with no semantic structure, and \"scanned\" PDFs are just images with no text layer at all. In 2026, the right extraction approach depends entirely on the PDF type.\n\n## What changed in 2026\n\n- **LLM vision (Claude 3.5 Sonnet, GPT-4o)** is now the most reliable approach for extracting tables, forms, and complex layouts — more accurate than rule-based parsers for anything non-trivial.\n- **`pymupdf` (PyMuPDF) 1.24** significantly improved table detection via its built-in `find_tables()` API.\n- **AWS Textract and Azure Document Intelligence** are production-grade managed OCR+structure services that handle handwriting and complex forms.\n- **`pdfjs-dist`** released a Node-compatible ESM build, removing the CommonJS wrapper workaround needed in earlier versions.\n- **LlamaParse** (LlamaIndex's hosted parser) became a popular choice for RAG pipelines — it returns clean Markdown from PDFs for LLM ingestion.\n\n## PDF types and their parsers\n\n| PDF type | Description | Right approach |\n| --- | --- | --- |\n| Digital text PDF | Text is embedded as characters | `pdf-parse`, `pdfjs-dist`, `pymupdf` |\n| Scanned / image PDF | Pages are rasterised images | OCR (Tesseract, Textract) |\n| Complex layout | Tables, columns, forms | LLM vision or `pymupdf.find_tables()` |\n| Password-protected | Encrypted | Must decrypt first (`qpdf`) |\n| Portfolio / embedded | PDFs inside PDFs | Unsupported by most libraries |\n\n## Node.js: basic text extraction\n\n```bash\nnpm install pdf-parse\n```\n\n```ts\nimport fs from 'fs/promises'\nimport pdfParse from 'pdf-parse'\n\nasync function extractText(filePath: string): Promise<string> {\n  const buffer = await fs.readFile(filePath)\n  const result = await pdfParse(buffer)\n  return result.text\n}\n\nconst text = await extractText('./document.pdf')\nconsole.log(`Pages: ${result.numpages}`)\nconsole.log(text.slice(0, 500))\n```\n\n## Node.js: page-by-page with `pdfjs-dist`\n\n```ts\nimport { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'\n\nasync function extractPages(filePath: string) {\n  const pdf = await getDocument(filePath).promise\n  const pages: string[] = []\n\n  for (let i = 1; i <= pdf.numPages; i++) {\n    const page = await pdf.getPage(i)\n    const content = await page.getTextContent()\n    const text = content.items\n      .map((item: any) => ('str' in item ? item.str : ''))\n      .join(' ')\n    pages.push(text)\n  }\n\n  return pages\n}\n```\n\n## Python: fast extraction with pymupdf\n\n```python\nimport fitz  # pip install pymupdf\n\ndef extract_text(path: str) -> str:\n    doc = fitz.open(path)\n    return \"\\n\".join(page.get_text() for page in doc)\n\ndef extract_tables(path: str):\n    doc = fitz.open(path)\n    for page in doc:\n        tabs = page.find_tables()\n        for tab in tabs:\n            print(tab.to_pandas())   # pip install pandas\n```\n\n## LLM vision for complex PDFs\n\n```ts\nimport Anthropic from '@anthropic-ai/sdk'\nimport fs from 'fs/promises'\n\nconst client = new Anthropic()\n\nasync function extractWithVision(pdfPath: string, prompt: string) {\n  const pdfBytes = await fs.readFile(pdfPath)\n  const base64 = pdfBytes.toString('base64')\n\n  const response = await client.messages.create({\n    model: 'claude-opus-4-5',\n    max_tokens: 2048,\n    messages: [{\n      role: 'user',\n      content: [\n        {\n          type: 'document',\n          source: { type: 'base64', media_type: 'application/pdf', data: base64 },\n        },\n        { type: 'text', text: prompt },\n      ],\n    }],\n  })\n\n  return response.content[0].type === 'text' ? response.content[0].text : ''\n}\n\nconst tables = await extractWithVision(\n  './invoice.pdf',\n  'Extract all line items as a JSON array with fields: description, qty, unit_price, total'\n)\n```\n\n## OCR for scanned PDFs\n\n```python\n# pip install pytesseract Pillow pdf2image\nfrom pdf2image import convert_from_path\nimport pytesseract\n\ndef ocr_pdf(path: str) -> str:\n    images = convert_from_path(path, dpi=300)\n    return \"\\n\".join(pytesseract.image_to_string(img) for img in images)\n```\n\nFor production accuracy, prefer managed services: AWS Textract (`boto3.client('textract')`) or Azure Document Intelligence — they handle multi-column layouts and tables significantly better than Tesseract.\n\n## How to pick an approach\n\n| Need | Tool |\n| --- | --- |\n| Simple text extraction, Node.js | `pdf-parse` |\n| Page-level control, Node.js | `pdfjs-dist` |\n| Fast bulk extraction, Python | `pymupdf` |\n| Table detection, Python | `pymupdf.find_tables()` |\n| Scanned PDF | Tesseract, AWS Textract |\n| Complex tables / forms | LLM vision (Claude, GPT-4o) |\n| RAG pipeline input | LlamaParse |\n\n## Common mistakes\n\n**Assuming text order is reading order** — PDF character positions are absolute coordinates; `pdf-parse` concatenates left-to-right but columns and footnotes often produce garbled output. Use `pymupdf` with `sort=True` for better reading order.\n\n**Ignoring encoding issues** — some PDFs use custom font encodings that map characters incorrectly. If text looks garbled, it is likely a custom encoding; LLM vision or `pymupdf` with `flags=fitz.TEXT_PRESERVE_LIGATURES` can help.\n\n**Running OCR on digital PDFs** — unnecessary and slow. Check if `pdf-parse` returns real text first; only fall back to OCR if it returns empty or garbled output.\n\n**No rate limiting on LLM vision** — vision API calls are expensive (~$0.01–0.03 per page depending on model). Cache results and avoid re-processing unchanged documents.\n\n## What to skip\n\n- **`PyPDF2`** — largely superseded by `pymupdf` for speed and accuracy; still fine for simple extraction but not the best choice for new projects.\n- **Regex-based field extraction** on raw text — PDF text whitespace is unreliable; structured extraction with an LLM is far more robust.\n- **Processing password-protected PDFs without the key** — strip the password first with `qpdf --decrypt`, then parse.\n\n## FAQ\n\n**Why does the extracted text look scrambled?**\nThe most common causes are: multi-column layout (text from column 2 interleaved with column 1), custom font encoding, or right-to-left text. Use `pymupdf` with sorting enabled, or LLM vision.\n\n**Can I extract images from PDFs?**\nYes. `pymupdf`: `page.get_images(full=True)` returns a list of embedded images which you can save via `doc.extract_image(xref)`.\n\n**What about form fields (fillable PDFs)?**\n`pymupdf`: `page.widgets()` returns form fields with their names and values. `pdfjs-dist` also supports AcroForm field extraction.\n\n**How do I handle very large PDFs (500+ pages)?**\nProcess page-by-page and stream results. Avoid loading the entire document text into memory. For OCR, split into batches and process in parallel workers.\n\n## Where to go next\n\n- [How to generate a PDF in 2026](/blog/how-to-generate-a-pdf-2026)\n- [How to build a RSS feed in 2026](/blog/how-to-build-a-rss-feed-2026)\n- [How to add search to a site in 2026](/blog/how-to-add-search-to-a-site-2026)","hue":305,"metaTitle":"How to Parse a PDF in 2026 (Node.js and Python)","metaDescription":"Complete 2026 guide to PDF parsing. Extract text, tables, and metadata in Node.js and Python — plus when to use LLM vision or OCR for complex PDFs.","keywords":["how to parse a pdf 2026","pdf parsing node.js 2026","pdf text extraction javascript","pdf-parse npm 2026","pdfjs-dist node","python pdf parsing 2026","pymupdf tutorial","ocr pdf 2026","llm pdf extraction 2026","extract tables from pdf"],"tldr":["**`pdf-parse` or `pdfjs-dist`** for plain text extraction in Node — both are zero-dependency and handle most digital PDFs well.","**LLM vision for complex layouts** — tables, multi-column text, and form fields extracted via Claude or GPT-4o Vision are more accurate than rule-based parsers.","**`pymupdf` (Python) is the fastest library** for bulk PDF processing — ~10× faster than `PyPDF2` for text extraction.","**OCR with Tesseract or AWS Textract** for scanned/image PDFs — pure text extraction returns nothing useful on a scanned document.","**Skip:** writing a custom PDF parser — the format is a labyrinth; even dedicated libraries struggle with edge cases."],"readTime":"5 min read","wordCount":1004},"related":[{"slug":"how-to-parse-json-2026","title":"How to parse JSON in 2026: Python, validation, and large-file tricks","excerpt":"JSON parsing in Python is a one-liner until the data is malformed, huge, or deeply nested. This 2026 guide covers the standard library, schema validation, streaming, and the fastest libraries for large payloads.","category":"code","date":"April 16, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":311},{"slug":"how-to-generate-a-pdf-2026","title":"How to generate a PDF in 2026","excerpt":"PDF generation covers invoices, reports, and certificates — each with different tooling tradeoffs. This guide picks the right library for your use case and walks through working examples.","category":"code","date":"March 12, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":306},{"slug":"how-to-make-a-discord-bot-2026","title":"How to Make a Discord Bot in 2026: a beginner walkthrough","excerpt":"Making a Discord bot in 2026 takes an app registration, a token, a library, and a few lines of code. Here is the full beginner walkthrough and pitfalls.","category":"code","date":"May 11, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":205}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-organize-cables-2026","title":"How to organize cables in 2026: a clutter-free setup that actually stays tidy","excerpt":"Cable chaos costs you time and sanity. This practical 2026 guide covers every method — from zip ties to wireless upgrades — so your setup stays clean for good.","category":"guides","date":"February 25, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":155},"older":{"slug":"how-to-pay-off-a-car-loan-2026","title":"How to pay off a car loan faster in 2026: strategies that actually work","excerpt":"Car loans are straightforward debt, but the interest adds up fast. These concrete 2026 strategies cut your payoff time and total interest paid.","category":"finance","date":"February 25, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":296}}