Extract multi-column PDFs and financial tables into clean JSON with MinerU
Standard PDF parsers mangle two-column layouts, footnotes, and merged financial tables. Run MinerU (magic-pdf) with a post-processing LLM validator to output clean Markdown and typed JSON.
Why default setups fall short
When building contract review, invoice auditing, or research RAG tools, naive PyPDF text extraction scrambles reading order across multi-column pages and destroys table cell alignment. Indie builders across Chinese and global AI communities have standardized on MinerU (magic-pdf) to detect layout bounding boxes first, convert tables into structured HTML/Markdown, and then pass only the clean table blocks to a low-cost reasoning model for JSON extraction.
Prerequisites before starting
- Docker or Python 3.10+ environment for MinerU (magic-pdf)
- Sample multi-column PDFs (invoices, 10-K filings, or academic papers)
- OpenAI-compatible API key (DeepSeek V3, GPT-4o-mini, or Gemini Flash)
Step-by-step implementation
1. Run MinerU in auto/ocr mode to separate reading order from headers
MinerU segments each page into title blocks, body columns, footnotes, formulas, and table regions, stripping repeating page headers and footers that normally pollute RAG vector chunks.
magic-pdf -p ./input-contracts/ -o ./parsed-output/ -m auto
2. Preserve extracted HTML table blocks alongside Markdown prose
MinerU outputs a *_content_list.json file alongside the Markdown document, preserving exact row-span and col-span HTML table structures so numbers never shift columns.
# Filter table blocks from MinerU content_list.json
tables = [blk for blk in content_list if blk.get("type") == "table"]
3. Validate numeric totals with a deterministic Python checksum
After prompting your LLM to convert a parsed table into typed JSON line items, run an automatic Python sum check against the stated invoice or balance-sheet total and flag any discrepancy > $0.01.
calculated = round(sum(item["amount"] for item in data["line_items"]), 2) assert abs(calculated - data["stated_total"]) <= 0.01, "Checksum mismatch"
4. Chunk by heading hierarchy with parent-section breadcrumbs
Prepend each vector chunk with its document title and H1/H2 breadcrumb path (e.g., [MSA-2026 > Section 4: Liability Cap]) so retrieval never loses context.