Hacker News ★ 93 4 min

Lightweight PDF parser with layout, tables, formulas and bounding boxes

🔗 https://github.com/beatrizalmeidaf/papero-pdf-text-extractor

📌 不靠ML模型,papero用幾何法解析PDF版面

TL;DR:papero用純幾何運算還原PDF的閱讀順序、表格與公式,CPU就能跑,適合RAG資料前處理。

把PDF裡的文字挖出來很容易,難的是把「這是第幾欄、這段是不是表格、公式長在哪裡」這些結構資訊找回來——而papero宣稱做到這件事完全不需要ML模型。

🤔 抽出文字容易,抽出結構才是RAG真正需要的東西

README指出,讓輸出對RAG、搜尋與LLM真正有用的關鍵,是結構而不是純文字:哪一欄先讀、哪些行其實是一張表格、公式畫在什麼位置。papero選擇用純幾何運算(plain geometry)處理這個問題,因此可以在一般筆電的CPU上跑,不需要GPU或ML模型。

🧩 靠bounding box撐起的六項核心能力

根據README,papero的設計圍繞著幾個具體能力展開:閱讀順序(reading order)方面,能處理雙欄、三欄論文的逐欄閱讀順序,並把頁首、頁尾、頁碼、重複的logo另外標記出來;表格方面,能識別有框線、無框線以及LaTeX booktabs風格的表格,還原成列與欄(含跨行的多行儲存格),並可匯出成CSV或Excel;公式方面,能辨識指數、下標、堆疊分數與手繪根號,輸出成LaTeX格式(例如\frac{5}{12}、\sqrt{2}),並附上該公式區域的裁切圖;每一個區塊都帶有bounding box位置資訊,可用於在RAG回答中精確引用原文位置、或在頁面上疊加標記;圖片與向量圖表會被裁切成PNG,並連同標題、座標軸標籤、圖例一併保留;匯出Word時則盡量保留原始版面,包括欄位、對齊、縮排、行距與字體粗細,雙欄版面匯出後仍是雙欄。另外README也提到幾個細節處理:LaTeX PDF裡被拆成獨立字符畫出的重音符號會被還原(例如「Computa¸ca˜o」還原成「Computação」)、表單產生器常用的隱藏白色文字會被捨棄、掃描頁面會自動跑OCR,DOCX/PPTX/XLSX/EPUB/HTML等格式則透過Apache Tika讀取。

怎麼用:pip install papero-extract後,用extract("paper.pdf")取得文件物件,可呼叫.to_markdown()、.tables、.formulas、.figures等屬性取出對應內容;也提供CLI指令(如papero-extract extract paper.pdf -o paper.md --images)、批次模式(papero-extract batch ./documents -o ./dataset可把一整個資料夾的PDF轉成RAG資料集,連同切好的chunks.jsonl與逐份文件的fidelity報告一起輸出)、以及用Docker compose一次拉起API、Apache Tika與Tesseract服務的REST API模式。

💡 fidelity報告拿PDF本身當對照,而不是標準答案

README特別說明,batch模式產出的fidelity報告是拿每份文件去跟它自己的PDF原文比對(沒有外部ground truth),檢查像是PDFium讀到的文字是否都出現在輸出中、閱讀順序有沒有跳欄錯讀、每個「Table N」標題底下是否真的抓到對應表格等訊號——因此作者提醒這份報告該被當成「該去哪裡複查」的指引,而不是精確度分數。瀏覽器版應用也內建同樣的檢查,並提供Compare分頁讓使用者逐頁比對PDF原文與抽取結果。

🎯 實務啟示

如果你的RAG流程正在為「表格被拆散」「公式變成亂碼」「多欄論文讀錯順序」所苦,papero提供的bounding box與chunk溯源(每個chunk都記錄來自哪一頁、哪個區塊)值得拿來實測;而且因為全部在CPU上跑、PDF不必上傳到任何服務,對於需要本機處理敏感文件的場景也多一個選項。批次模式附帶的fidelity報告,則可以當成資料集上線前的自動化複查清單,先挑出有問題的文件人工複核,而不是整批盲目信任抽取結果。

🔗 來源

#PDFParsing #RAG #DocumentAI #OCR #DataExtraction #OpenSource #LaTeX #NLP #MachineLearning #DeveloperTools

原始資料 Hacker News · 收集於 2026-10-02
來源原標題
Lightweight PDF parser with layout, tables, formulas and bounding boxes
作者
beatrizalmeidaf
原始標籤
hackernews
來源訊號
HN points 86 HN 留言 7
原始連結
https://github.com/beatrizalmeidaf/papero-pdf-text-extractor

摘要原文

Document structure extraction without the heavyweight stack. PDF → Markdown · JSON · Word · Excel — with reading order, tables, formulas, figures and the position of every block. CPU only. No ML models. Runs in your browser, in Python, or as an API. ▶ Try it in your browser · Quick start · Benchmarks 30 seconds in the browser app: load a PDF, inspect any block, check tables and formulas, export to Word. Your PDF never leaves your machine. ( MP4 ) Why papero Getting the text out of a PDF is easy. Getting its structure back — which column comes first, which lines are a table, where the formula is — is what makes the output usable for RAG, search and LLMs. papero does that with plain geometry, so it stays fast on a laptop CPU. 📖 Reading order Two- and three-column papers read column by column. Headers, footers, page numbers and repeated logos are set aside. ▦ Real tables Ruled, borderless and LaTeX booktabs tables come back as rows and columns — multi-line cells included. Export to CSV or Excel. ∑ Formulas Exponents, indices, stacked fractions and drawn root signs are read as mathematics — 5/12 , 10√2 , CO₂(g) — and written as LaTeX ( \frac{5}{12} , \sqrt{2} ), plus a cropped image of the formula. 📍 Position of everything Every block has a bounding box — cite the exact spot in a RAG answer, draw over the page, or crop it. 🖼 Figures & charts Images and vector charts are cropped to PNG, with their caption, axis labels and legend kept together. 📝 Back to Word Columns, alignment, indents, line spacing, bold runs and fonts are kept, so a .docx or HTML export looks like the original page — two columns stay two columns. Also: accents drawn as separate glyphs in LaTeX PDFs ( Computa¸ca˜o → Computação ), invisible white text used by form generators is dropped, scanned pages go through OCR, and DOCX/PPTX/XLSX/EPUB/HTML are read through Apache Tika. Quick start pip install papero-extract from papero_extract import extract doc = extract ( "paper.pdf" ) print ( doc . to_markdown ()) Or skip the install: open the browser app , drop a PDF, export to the format you need. More Python — tables, formulas, positions, images, options from papero_extract import extract , extract_text doc = extract ( "paper.pdf" , images = True ) doc . tables [ 0 ]. rows # [["Model", "Accuracy"], ["Base", "0.81"], ...] doc . formulas [ 0 ]. latex # "E = mc^{2}" doc . figures [ 0 ]. image . data # PNG bytes for block in doc . pages [ 0 ]. blocks : # reading order, with positions print ( block . type , block . bbox , block . text [: 60 ]) doc . to_html () # keeps alignment and indents doc . to_dict () # the full JSON # Math inside paragraphs as LaTeX, in Markdown or plain text — found by what it is made of # (operators, functions, exponents, roots), even when it is set in the text font: doc . to_markdown ( math = "latex" ) # "Se $\operatorname{tg} x - \operatorname{cotg} x = 1$, então…" doc . to_text ( math = "latex" ) # "a) $1{,}035\cdot 10^{9}$ e $5{,}5\cdot 10^{7}$" extract ( "slides.pptx" ). to_markdown () # any format Apache Tika reads extract_text ( "contract.pdf" ). text # fastest: clean text only Option Default pages all "1-3,5,10-" images False crop figures, tables and formulas to PNG tables / formulas True detection on/off ocr "auto" "auto" (scanned pages only), "force" , "off" ocr_language "por+eng" Tesseract languages tika True False runs the layout engine alone (no Java) workers 1 processes for long documents CLI papero-extract extract paper.pdf -o paper.md --images # Markdown + images/ folder papero-extract extract paper.pdf -o paper.json # format from the extension papero-extract extract paper.pdf -f csv -o tables.csv # tables only papero-extract extract paper.pdf -p 1-5 -f html papero-extract extract paper.pdf --math latex -o p.md # math in the text as $…$ papero-extract extract paper.pdf --fast # clean text only papero-extract batch ./documents -o ./dataset # a whole folder, for RAG papero-extract serve --port 8000 # API + browser app Batch & fidelity report — a folder of PDFs to a RAG dataset, and which ones to review papero-extract batch ./documents -o ./dataset dataset/ ├── documents/ one .md and one .json per PDF (same sub-folders) ├── chunks.jsonl every chunk, cut at headings, tables kept whole ├── manifest.json per document: pages, tables, chunks, fidelity scores └── fidelity/ ├── report.json totals, signals and every document's issues ├── summary.html the same, to open in a browser └── problematic/ one .json per document with warnings or errors Each chunk knows where it came from, so a retrieval hit can be shown on the page: { "id" : " paper.pdf#12 " , "type" : " table " , "headings" : [ " 4 Results " ], "pages" : [ 6 , 6 ], "blocks" : [ " p6-b3 " ], "text" : " Table 2: Accuracy per model. \n\n | Model | Top-1 | … " } The report checks every document against its own PDF — no ground truth, so read it as where to look , not as accuracy: Signal What is checked text the words PDFium reads on each page are all in the output reading_order no block is read after one below it in the same column tables every "Table N" caption has its table; each table is a clean grid figures every "Figure N" caption has its figure formulas each formula has LaTeX and no unmapped glyph The browser app runs the same checks on the PDF you drop: the fidelity figure sits next to the page count, and the Compare tab puts each page beside what was extracted from it, marking the PDF text that is not in the output and the blocks that failed a check. from papero_extract import extract from papero_extract . batch import run_batch from papero_extract . chunks import chunk_document from papero_extract . fidelity import assess , reference_text run_batch ( "./documents" , "./dataset" , workers = 8 )[ "totals" ] # {"documents": …, "ok": …, "warning": …, "error": …} doc = extract ( "paper.pdf" ) chunk_document ( doc , document = "paper.pdf" , max_chars = 1500 ) assess ( doc , reference_text ( "paper.pdf" )). issues # [Issue(code="table_not_detected", pages=[5], …)] REST API & Docker docker compose up # API + Apache Tika + Tesseract + browser app on :8000 curl -F " file=@paper.pdf " " localhost:8000/v1/extract?format=markdown " curl -F " file=@paper.pdf " " localhost:8000/v1/extract?format=zip&images=true " -o paper.zip curl -F " file=@paper.pdf " " localhost:8000/v1/extract?per_page=true " # blocks + positions One endpoint, POST /v1/extract ; interactive docs at /docs . Parameter Default mode structured structured (layout + Tika) or fast (text only) format json json , markdown , text , html , csv , zip pages all 1-3,5,10- per_page false include pages, blocks and positions in the JSON images false crop figures, tables and formulas ocr auto auto , force , off Configuration through environment variables — see .env.example . What comes out Every block knows what it is and where it was: { "type" : " table " , "bbox" : [ 56.7 , 294.8 , 481.9 , 374.2 ], "rows" : [[ " Model " , " Accuracy " ], [ " Base " , " 0.81 " ]], "caption" : " Table 1: Comparison between models. " } Output Python · CLI · API Browser app Markdown, plain text, JSON ✓ ✓ HTML (keeps alignment and indents) ✓ ✓ CSV of the tables, ZIP with images ✓ ✓ Word .docx that keeps the page's look — ✓ Excel .xlsx , one sheet per table — ✓ Full JSON schema and block types { "schema" : " pdf-text-api/document@1 " , "engine" : " tika+pdfium " , "page_count" : 12 , "metadata" : { "title" : " ... " , "author" : " ... " , "language" : " en " }, "pages" : [{ "number" : 1 , "width" : 595.3 , "height" : 841.9 , "blocks" : [{ "id" : " p1-b4 " , "type" : " paragraph " , "bbox" : [ 74.0 , 217.0 , 522.0 , 275.0 ], "text" : " Atestamos que a estudante ... " , "style" : { "pt" : 11.0 , "font" : " Arial " , "bold" : false }, "format" : { "align" : " justify " , "first_line" : 42.7 , "line_spacing" : 1. (86 points, 7 comments on Hacker News)

由 claude-code/sonnet 自動生成