戰(zhàn):RAG知識(shí)庫的圖文提取與工具選型)
1. 問題源頭PDF 為什么是 RAG 導(dǎo)入的第一道坎如果你搭過 RAG 知識(shí)庫你大概率經(jīng)歷過這種場(chǎng)景文檔明明導(dǎo)進(jìn)去了檢索的時(shí)候卻發(fā)現(xiàn)答非所問。查來查去問題出在解析環(huán)節(jié)——PDF 里的文字沒提出來或者提出來的是亂碼、是空字符、是一大團(tuán)擠在一起的文本塊。PDF 這個(gè)格式誕生三十多年它的設(shè)計(jì)哲學(xué)是“保證打印效果一致”而不是“方便提取文字”。這就導(dǎo)致了 PDF 內(nèi)部結(jié)構(gòu)的極端分裂有的 PDF 自帶文本層鼠標(biāo)一劃能選中文字有的 PDF 本質(zhì)是掃描圖片里面一個(gè)文字都沒有還有的 PDF 是電子簽名、表單、打印流生成的混合體每一頁結(jié)構(gòu)都不同。RAG 系統(tǒng)把 PDF 喂給向量模型之前必須先完成“從字節(jié)流到干凈文本”的轉(zhuǎn)換。這個(gè)環(huán)節(jié)沒做好后面的一切——切片、向量化、召回——都是在錯(cuò)誤的地基上蓋樓。我見過不少團(tuán)隊(duì)花大量時(shí)間調(diào) embedding 模型、調(diào) rerank 閾值最后發(fā)現(xiàn)問題的根源只是 PDF 解析器選錯(cuò)了。所以解析這件事值得單獨(dú)拿出一篇來說。這篇就聚焦兩塊圖文混合內(nèi)容的 OCR 解析以及 PDF 場(chǎng)景下的工具選型。2. 圖文解析的兩個(gè)方向傳統(tǒng) OCR 與多模態(tài)大模型2.1 傳統(tǒng) OCR 的技術(shù)棧與選型邏輯OCR光學(xué)字符識(shí)別解決的問題很簡(jiǎn)單把圖片里的文字變成可編輯的文本。但在 RAG 語境下OCR 的要求更高不只是“識(shí)別出字”還要“保留版面結(jié)構(gòu)”——標(biāo)題、段落、表格、圖片位置這些信息如果全部拍平成純文本語義就丟了。先看離線方案。PaddleOCR 目前是中文場(chǎng)景下綜合能力最穩(wěn)的開源方案PP-OCRv4 系列模型的識(shí)別精度和速度都經(jīng)過了大量工業(yè)場(chǎng)景驗(yàn)證。Tesseract OCR 是老牌開源工具支持上百種語言但中文識(shí)別精度一直不太理想尤其是遇到復(fù)雜排版和低清晰度掃描件時(shí)錯(cuò)誤率會(huì)明顯上升。如果做多語言文檔Tesseract 可以作為備選如果主力是中文PaddleOCR 更靠譜。在線 API 方案主要分兩類一類是通用云廠商的 OCR 接口另一類是專門的文檔解析服務(wù)。通用 OCR 接口的優(yōu)勢(shì)是開箱即用不需要自己部署模型但劣勢(shì)也很明顯——圖片里大段文字之外的版面信息標(biāo)題層級(jí)、段落邊界、表格結(jié)構(gòu)它不會(huì)幫你還原。而文檔解析服務(wù)比如各類 DocAI 類產(chǎn)品會(huì)額外輸出版面分析結(jié)果這一步對(duì) RAG 后續(xù)的切片策略影響很大。這里要特別提醒一個(gè)事情線上 OCR 服務(wù)對(duì)圖片格式和大小有嚴(yán)格限制超出限制會(huì)被拒。我在實(shí)際項(xiàng)目里遇到過拍照的合同文件因?yàn)?EXIF 旋轉(zhuǎn)信息異常直接被 OCR 接口報(bào)“file format error”的坑。處理方式是先用圖像庫統(tǒng)一做方向校正和重編碼再送進(jìn) OCR。2.2 多模態(tài)大模型解析圖片的實(shí)際用法傳統(tǒng) OCR 就是把像素變成文字多模態(tài)大模型則是把像素變成“理解”。兩者的核心差異在于OCR 只做字符識(shí)別不識(shí)語義多模態(tài)模型能識(shí)別字符、理解版面、描述圖表、提取關(guān)鍵信息甚至可以理解表格里的邏輯關(guān)系。在 RAG 場(chǎng)景里多模態(tài)大模型的價(jià)值主要體現(xiàn)在兩個(gè)地方。第一是高精度 OCR 補(bǔ)錯(cuò)先讓傳統(tǒng) OCR 出初稿再用多模態(tài)模型對(duì)關(guān)鍵區(qū)域二次校驗(yàn)?zāi)茱@著降低錯(cuò)誤率。第二是結(jié)構(gòu)化信息提取合同里的甲方、乙方、金額、日期發(fā)票里的發(fā)票號(hào)、稅額這些關(guān)鍵字段直接跳過 Fine-tuning用多模態(tài)大模型的 Prompt 就能抽出來。舉個(gè)實(shí)際例子。我處理過一批掃描版技術(shù)協(xié)議里面含蓋章、手寫批注、表格混排。PaddleOCR 能識(shí)別出文字但分不清哪些是條款正文、哪些是批注。換成多模態(tài)方案后通過一個(gè)簡(jiǎn)單的 Prompt 要求區(qū)分正文與批注、保留表格結(jié)構(gòu)輸出結(jié)果的質(zhì)量提升非常明顯。多模態(tài)模型選型上開源簡(jiǎn)單場(chǎng)景完全可以部署本地模型但要注意顯存占用和推理速度商用 API 的識(shí)別精度、速度都不錯(cuò)但成本需要評(píng)估。個(gè)人建議流程可以這樣設(shè)計(jì)優(yōu)先跑離線 OCR遇到結(jié)構(gòu)化復(fù)雜或識(shí)別置信度低的頁面才調(diào)用多模態(tài)接口兜底。這樣既有性能和成本的平衡又能保證整體解析質(zhì)量。2.3 表格與版面的特殊處理表格是圖文解析里最容易被低估的一塊。PDF 里的表格提取出來之后如果拍平成一串帶制表符的文本向量化的時(shí)候語義就散了——每一行被切開列與列之間的對(duì)應(yīng)關(guān)系全部丟失。推薦的做法是走“表格結(jié)構(gòu)識(shí)別 轉(zhuǎn) HTML/JSON”的路徑復(fù)雜表格轉(zhuǎn)成 Markdown 或 HTML 之后再做切片??梢栽囋囘@樣的思路先做版面分析把頁面切分成標(biāo)題區(qū)、正文區(qū)、表格區(qū)、圖片區(qū)。標(biāo)題區(qū)可以和下文合并成一個(gè)語義塊正文區(qū)按段落邊界切片表格區(qū)單獨(dú)走表格還原流程圖片區(qū)如果是信息圖或者流程圖調(diào)用多模態(tài)模型生成一段描述性文本再作為該區(qū)域的向量化內(nèi)容。這種分區(qū)處理方案比“整頁轉(zhuǎn)文本再切分”的召回效果要好得多。3. 九種 PDF 解析工具的橫向選型3.1 以語言生態(tài)分類Python 系與系統(tǒng)級(jí)工具PDF 解析工具的選型首先要看你的項(xiàng)目技術(shù)棧。Python 生態(tài)里PyPDF2 和 pdfplumber 是兩條相反的路線。PyPDF2 主打輕量適合快速提取文本和水印、合并拆分 PDF但它對(duì)復(fù)雜排版幾乎無能為力提取出來的文字經(jīng)常亂序。pdfplumber 則更像一個(gè)精工細(xì)作的工具箱它基于 PDFMiner 構(gòu)建能提取每個(gè)字符的坐標(biāo)信息可以精確還原版面。代價(jià)是速度慢、內(nèi)存占用高。PDFMiner.six 是另一個(gè)值得了解的名字。它是很多高級(jí)解析工具的內(nèi)核對(duì)文本提取的底層支持非常扎實(shí)如果你需要自定義解析邏輯直接在 PDFMiner 的基礎(chǔ)上做二次開發(fā)可控性是最好的。系統(tǒng)級(jí)工具里poppler-utils 是 Linux 服務(wù)器上的標(biāo)配。它的 pdftotext 命令保留了相對(duì)簡(jiǎn)單的版面布局pdfimages 可以批量導(dǎo)出 PDF 里的圖片。在 Linux 服務(wù)器上做批處理時(shí)這套工具比寫十幾行 Python 代碼更高效。但要注意pdftotext 對(duì) CJK中日韓字符的排版處理存在一些固有的問題遇到豎排文字、復(fù)雜注音符號(hào)時(shí)提取結(jié)果會(huì)出問題。3.2 以場(chǎng)景分類掃描件、原生 PDF 與復(fù)雜版式選用什么工具取決于你手上的 PDF 是哪種類型。原生 PDF文本型這類 PDF 自帶文本層文字可以直接提取。用 pdfplumber 或 PDFMiner 都可以獲得較好的結(jié)果速度也快。如果是簡(jiǎn)單版式的學(xué)術(shù)論文PyPDF2 就夠用如果版面較復(fù)雜建議直接上 pdfplumber。掃描型 PDF這類 PDF 本質(zhì)上是一堆圖片套了一個(gè) PDF 外殼。任何純文本提取工具對(duì)它都無效必須走 OCR 流程。處理鏈?zhǔn)窍扔霉ぞ邔?dǎo)出頁面圖片pdfimages 或者 PyMuPDF 的 get_pixmap再對(duì)圖片做 OCR。如果頁數(shù)不多也可以直接用具備 OCR 能力的文檔解析 API 一次性處理。復(fù)雜版式 PDF表格、多欄、頁眉頁腳這類是 RAG 項(xiàng)目里最容易出問題的。用 pdfplumber 提取表格時(shí)可以用 extract_table 方法但前提是表格必須有明確的線框。沒有線的表格識(shí)別率會(huì)大幅下降。這里有個(gè)小實(shí)戰(zhàn)建議拿到 PDF 后先用工具建議 pdfplumber把第一頁的文字坐標(biāo) dump 出來觀察文本流的順序和坐標(biāo)分布判斷這個(gè) PDF 是不是“正常排版”再做后續(xù)處理決策。3.3 九種工具速查對(duì)比我按“適用場(chǎng)景 優(yōu)缺點(diǎn) 建議指數(shù)”整理了一張速查表可以直接抄作業(yè)。工具類型適用場(chǎng)景核心優(yōu)勢(shì)主要缺點(diǎn)建議指數(shù)PyPDF2Python庫輕量文本提取、PDF合并拆分簡(jiǎn)單易用、社區(qū)活躍復(fù)雜排版提取亂序中等pdfplumberPython庫精確文本提取、表格提取支持字符坐標(biāo)、表格識(shí)別較好速度慢、內(nèi)存占用高高PDFMiner.sixPython庫自定義解析邏輯底層控制力強(qiáng)、穩(wěn)定性好上手門檻高中等PyMuPDFPython庫高性能文本圖片導(dǎo)出速度快、支持格式多API偶爾變動(dòng)高CamelotPython庫線框表格提取表格提取精度高對(duì)無框表格無效中等poppler-utils系統(tǒng)工具Linux批處理、圖片導(dǎo)出輕量快速、無需編碼中文復(fù)雜排版處理一般中等Apache TikaJava/Python多格式統(tǒng)一解析格式覆蓋廣、自動(dòng)探測(cè)解析質(zhì)量不如專用工具較低Adobe Acrobat商業(yè)軟件人工處理復(fù)雜文檔還原度極高貴、無法自動(dòng)化視場(chǎng)景商用文檔解析API云服務(wù)全類型文檔一體化解析開箱即用、報(bào)表分析強(qiáng)成本高、有數(shù)據(jù)隱私考慮高選型的核心邏輯就一條先看 PDF 類型再選工具。掃描件直接免談純文本提取工具原生 PDF 優(yōu)先開源 Python 庫復(fù)雜場(chǎng)景優(yōu)先商用 API 或自建多模態(tài)方案。4. 實(shí)操一條可落地的“PDF 解析流水線”4.1 解析流程設(shè)計(jì)我在幾個(gè)項(xiàng)目里沉淀了一套解析流水線整體思路是判斷類型 → 分類處理 → 版面還原 → 輸出標(biāo)準(zhǔn)格式。這套流程在長(zhǎng)文檔幾十頁到幾百頁和短文檔幾頁合同上都驗(yàn)證過。第一步用 PyMuPDF 檢查 PDF 是否包含文本層。這個(gè)判斷很簡(jiǎn)單提取每頁的文本長(zhǎng)度如果接近 0說明該頁是掃描頁。第二步按頁面類型分流。文本頁走 pdfplumber 提取文本和表格掃描頁走 PaddleOCR 做文字識(shí)別同時(shí)對(duì)有表格的區(qū)域調(diào)用多模態(tài)模型做結(jié)構(gòu)還原。第三步版面合并。將 OCR 結(jié)果、文本提取結(jié)果、表格還原結(jié)果按頁面順序拼接成結(jié)構(gòu)化 Markdown并保留坐標(biāo)信息用于后續(xù)的可視化調(diào)試。第四步清洗與歸一化。統(tǒng)一處理中文標(biāo)點(diǎn)、編碼問題、多余空行和亂碼字符。實(shí)際項(xiàng)目中環(huán)節(jié)順序可以這樣安排。4.2 硬核示例從 PDF 到干凈文本的完整實(shí)現(xiàn)下面這套代碼是我在實(shí)際項(xiàng)目里用的邏輯可以直接復(fù)現(xiàn)。核心思路是針對(duì)混合型 PDF既有文本頁又有掃描頁做分流處理。import fitz # PyMuPDF import pdfplumber from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) def is_scanned_page(page) - bool: 判斷當(dāng)前頁面是否為純掃描頁 text page.get_text().strip() return len(text) 10 # 文本量極低判定為掃描頁 def extract_text_page(pdf_path, page_num): 文本頁提取優(yōu)先用 pdfplumber 保留版面順序 text_result [] with pdfplumber.open(pdf_path) as pdf: page pdf.pages[page_num] # 提取表格 tables page.extract_tables() for table in tables: if table: text_result.append([TABLE_START]) for row in table: text_result.append( | .join([str(c).replace(\n, ) if c else for c in row])) text_result.append([TABLE_END]) # 提取文本 text_result.append(page.extract_text()) return \n.join(text_result) def extract_scan_page(pdf_path, page_num, zoom2.0): 掃描頁提取渲染高清圖走 OCR doc fitz.open(pdf_path) page doc[page_num] mat fitz.Matrix(zoom, zoom) pix page.get_pixmap(matrixmat) img_path f/tmp/page_{page_num}.png pix.save(img_path) result ocr.ocr(img_path, clsTrue) lines [] if result and result[0]: for item in result[0]: box, (text, confidence) item, None # 實(shí)際 PaddleOCR 返回結(jié)構(gòu)為 [box, (text, score)] text item[1][0] lines.append(text) return \n.join(lines) def parse_mixed_pdf(pdf_path): 主流程逐頁判斷類型分流處理 doc fitz.open(pdf_path) full_text [] for page_num in range(len(doc)): page doc[page_num] if is_scanned_page(page): page_text extract_scan_page(pdf_path, page_num) else: page_text extract_text_page(pdf_path, page_num) full_text.append(f!-- PAGE {page_num 1} --\n{page_text}) return \n\n.join(full_text) if __name__ __main__: output parse_mixed_pdf(your_document.pdf) with open(output.md, w, encodingutf-8) as f: f.write(output)這段代碼里幾個(gè)細(xì)節(jié)值得說清楚zoom2.0用于控制渲染清晰度。對(duì)中文 OCR至少 200 DPI 起步不然小字號(hào)文字識(shí)別率下降非常明顯。測(cè)試下來 2.0 倍縮放是一個(gè)性價(jià)比合理的閾值。pdfplumber.extract_tables()輸出的是嵌套列表結(jié)構(gòu)需要自己拼接。拼接時(shí)用豎線分隔這樣轉(zhuǎn)成 Markdown 后格式依然保留。OCR 結(jié)果中每個(gè)元素的結(jié)構(gòu)是[box_coordinates, (text, confidence)]提取文本時(shí)注意別把坐標(biāo)當(dāng)成文字輸出了。這套流水線的處理耗時(shí)大約在每秒 2~3 頁取決于頁面復(fù)雜度對(duì)于大部分企業(yè)級(jí)知識(shí)庫的文檔量級(jí)來說完全夠用。如果吞吐要求高可以用多進(jìn)程按頁并行處理再用頁碼號(hào)合并結(jié)果。4.3 多模態(tài)模型接入的接口設(shè)計(jì)只靠 PaddleOCR 做圖文解析遇到復(fù)雜版面會(huì)不夠用。我建議在流水線上預(yù)留一個(gè)“多模態(tài)模型補(bǔ)充解析”的接口。設(shè)計(jì)上不需要所有頁面都走多模態(tài)還是那句話傳統(tǒng) OCR 先兜底多模態(tài)負(fù)責(zé)補(bǔ)漏。import base64 import requests def ocr_with_visual_model(image_path, prompt請(qǐng)?zhí)崛D中的全部文字信息保留原文結(jié)構(gòu)輸出為Markdown格式。): with open(image_path, rb) as f: img_base64 base64.b64encode(f.read()).decode() payload { model: your-visual-model, messages: [{ role: user, content: [ {type: text, text: prompt}, {type: image_url, image_url: {url: fdata:image/png;base64,{img_base64}}} ] }} ] resp requests.post(http://your-endpoint/v1/chat/completions, jsonpayload) return resp.json()[choices][0][message][content]Prompt 的設(shè)計(jì)是這類場(chǎng)景的關(guān)鍵。我個(gè)人在大量測(cè)試后推薦用下面的 Prompt 模板要求模型“原樣輸出”“不要總結(jié)”“保留表格結(jié)構(gòu)”“區(qū)分標(biāo)題正文”。一個(gè)常見的問題是模型會(huì)自動(dòng)做摘要式回答把原文改寫一遍這對(duì) RAG 是致命的。在 Prompt 里顯式聲明“逐字輸出原文”能有效減少這種情況。5. 實(shí)操中的五個(gè)高頻坑5.1 掃描件文字 OCR 識(shí)別不出韓文、日文等多語言熱詞里有個(gè)問題很有代表性“以下 OCR 代碼識(shí)別不了韓文”。PaddleOCR 默認(rèn)的langch是只識(shí)別中英文的。要識(shí)別韓文必須顯式指定langkorean。同理日文用langjapan。ocr PaddleOCR(use_angle_clsTrue, langkorean, show_logFalse)這里要額外提醒混合語言的文檔如中英夾雜的合同建議使用langch它內(nèi)部覆蓋中英文但如果同一文檔里既有中文又有韓文PaddleOCR 目前沒法在單次調(diào)用中同時(shí)處理。折中方案是分區(qū)域裁剪后分別識(shí)別或者直接換多模態(tài)模型做識(shí)別。5.2 PDF 里的文字是“加密子集”或自定義編碼有些 PDF 生成工具尤其是部分國(guó)產(chǎn)辦公軟件會(huì)對(duì)內(nèi)嵌字體做子集化處理導(dǎo)致文本層的 Unicode 映射異常。表現(xiàn)是用 PDF 閱讀器打開顯示正常用代碼提取卻是亂碼或空白。這種只能靠 OCR 兜底。排查方法很簡(jiǎn)單先用 pdfplumber 提取前幾頁文本看是否亂碼。如果亂碼放棄文本層方案直接把頁面渲染成圖片走 OCR。這種情況下渲染清晰度非常關(guān)鍵建議zoom參數(shù)設(shè)為 3.0 以上。5.3 表格提取結(jié)果串行錯(cuò)位很多 RAG 項(xiàng)目導(dǎo)入 PDF 后表格內(nèi)容的召回效果特別差。原因基本都在解析環(huán)節(jié)表格被拍平成了無序文本序列。Camelot 對(duì)有線框的表格提取效果極佳但它依賴 Ghostscript安裝時(shí)容易出錯(cuò)。如果不想引入這個(gè)依賴pdfplumber 的 extract_table 是更輕量的替代。實(shí)測(cè)經(jīng)驗(yàn)表格提取后先用 Markdown 表格格式輸出再讓語言模型把 Markdown 表格轉(zhuǎn)成 JSON 或帶 schema 的結(jié)構(gòu)化數(shù)據(jù)召回時(shí)按字段查詢的效果遠(yuǎn)好于純文本檢索。這條經(jīng)驗(yàn)我驗(yàn)證過多次值得收藏。5.4 PDF 頁面方向顛倒導(dǎo)致識(shí)別率暴跌掃描儀出來的 PDF 經(jīng)常會(huì)有個(gè)別頁面方向不對(duì)。PaddleOCR 自帶use_angle_clsTrue的方向分類器能在一定程度上糾正但對(duì)旋轉(zhuǎn) 180°的頁面效果有限。最穩(wěn)的方法是預(yù)處理階段用圖像方向檢測(cè)模型統(tǒng)一校正。另外手機(jī)拍攝的文檔照片常常帶著 EXIF 旋轉(zhuǎn)信息。直接把這個(gè)圖喂給 OCR 接口部分 API 會(huì)報(bào)格式錯(cuò)誤或者識(shí)別出完全無意義的內(nèi)容。處理方式是用PIL.ImageOps.exif_transpose()先消除 EXIF 旋轉(zhuǎn)再保存。5.5 處理流程的性能太低PDF 解析在很多知識(shí)庫項(xiàng)目里是離線批處理任務(wù)但也有實(shí)時(shí)解析的需求。優(yōu)化思路有三條多用進(jìn)程池并行處理頁面避免 GIL 限制。用 PyMuPDF 替代 pdfplumber 做純文本提取速度能快一個(gè)數(shù)量級(jí)只有需要坐標(biāo)時(shí)才用 pdfplumber。對(duì)大量重復(fù)模板的文檔如統(tǒng)一格式的報(bào)告跑一次解析后保存解析結(jié)果緩存后續(xù)直接復(fù)用。6. 選型決策樹走到這一步該怎么選拿到的 PDF 類型不同最優(yōu)的工具鏈組合也不同。給你一個(gè)可以直接對(duì)照的決策路徑純文本型 PDF不追求表格結(jié)構(gòu)PyMuPDF 提取全文 → 按段落做切片。最優(yōu)先速度快、質(zhì)量穩(wěn)。文本型 PDF含表格、多欄排版pdfplumber 提取文本表格 → 表格轉(zhuǎn) Markdown → 與正文合并后切片。純掃描型 PDFPaddleOCR中文或 Tesseract多語言→ 版面合并 → 結(jié)構(gòu)化輸出?;旌闲?PDF先用 PyMuPDF 逐頁判斷類型 → 按頁面類型分流處理。這一步已經(jīng)在上面的代碼示例里實(shí)現(xiàn)了。含大量復(fù)雜表格、票據(jù)、蓋章合同的 PDF直接上商用文檔解析 API或多模態(tài)大模型 OCR 雙通道處理不要再浪費(fèi)精力調(diào)開源方案。7. 資料引用與后續(xù)內(nèi)容預(yù)告關(guān)于 PDF 格式本身的技術(shù)細(xì)節(jié)推薦查閱 PDF 規(guī)范文檔ISO 32000以及各類開源解析器的源碼實(shí)現(xiàn)。在實(shí)際項(xiàng)目中pdfplumber 的源碼是了解 PDF 結(jié)構(gòu)的最佳教材之一。補(bǔ)充兩個(gè)實(shí)用資料PaddleOCR 的官方文檔里包含完整的模型列表和參數(shù)說明建議優(yōu)先看 PP-OCRv4 的模型對(duì)比商用解析服務(wù)的技術(shù)文檔里提到版面分析能力對(duì)接前建議先做小規(guī)模效果驗(yàn)證。這篇講完了圖文與 PDF 解析下一篇適合接著討論“解析后的文本如何做清洗與切片”因?yàn)榻馕鲚敵龅慕Y(jié)果直接決定了后續(xù)向量化的質(zhì)量。比如清洗時(shí)要不要統(tǒng)一全角半角、切片時(shí)按固定長(zhǎng)度還是按語義邊界、標(biāo)題和正文如何組合成上下文塊——這些問題都是從解析到召回的必經(jīng)之路。準(zhǔn)備接上一篇的目錄索引保持系列內(nèi)容連貫方便讀者按順序查閱。