从 PDF 中精准提取表格、图片与公式:MinerU 结构化元素抽取的 3 种方案
为什么 PDF 元素提取比纯文本难PDF 是一种视觉格式不是逻辑格式。PDF 文件的本质是一组绘图指令——把文字放在哪、画多粗的线、用什么字体渲染——而非像 HTML 或 Markdown 那样告诉你这是一个表格标题或这是一个三级公式。当你用传统 PDF 解析工具提取文本时得到的通常是一段按阅读顺序拼接的纯文本表格结构、图片位置、公式内容全部丢失。传统 OCR 的困境就在这里。它能把扫描件里的像素转成字符但它不知道哪些字符属于同一列、哪些被包在表格单元格里、哪个公式的分数线跨越三行。这种结构性损失导致文本提取后你需要人工重排数据、重新关联图注与图片、手动修复公式的 LaTeX 表示。这是目前 PDF解析、表格提取、公式识别、图片提取等任务在实际工程中反复踩坑的根本原因。MinerU 的云端 SDK/API 把这一层逻辑封装进了解析管线。它不再只输出纯文本而是返回一个 JSON 层级结构——告诉你每个元素的类型表格、图片、公式、文本块、坐标bbox、阅读顺序以及结构化正文表格的 HTML、公式的 LaTeX。你可以直接通过result.content_list或下载完整的 zip 包来获取这些结构化数据。本地部署用户对应的输出文件为content_list.json和middle.json云端 SDK 在此基础上封装了更直接的方法调用。下面展开三种从 PDF 中提取结构化元素的方案从零代码感知的content_list到支持像素级定位的layout.json再到一个完整的端到端实战。方案一result.content_list 快速提取零代码感知的结构化数据MinerU SDK 的ExtractResult对象暴露了一个content_list字段——它对应本地部署的content_list.json但已经过 SDK 解析为可直接遍历的 Python 列表。每个元素通过type字段告诉你它是什么table附带完整的 HTML 表格正文equation附带 LaTeX 公式字符串text携带纯文本和层级信息。基础用法SDK 使用流程分为三步初始化客户端、调用解析接口、遍历结果。frommineruimportMinerU clientMinerU(your-api-token)resultclient.extract(https://cdn-mineru.openxlab.org.cn/demo/example.pdf)foriteminresult.content_list:print(f[{item[type]}] page{item[page_idx]})result.content_list是一个 Python 列表每个元素是一个字典。你不需要手动解析 JSON 文件或处理文件路径——SDK 已经将云端返回的结果包解析成了可直接消费的数据结构。对于 Agent 轻量解析场景使用flash_extract()甚至不需要 TokenclientMinerU()# 不传 token进入 flash-only moderesultclient.flash_extract(https://example.com/paper.pdf)# content_list 同样可用但输出仅为 Markdown 级别按类型过滤content_list中每种类型携带不同的字段。以下代码分别提取表格、公式和文本块# 提取所有表格tables[itemforiteminresult.content_listifitem[type]table]fortblintables:print(f Caption:{tbl.get(table_caption,[])[0]})print(f HTML body:{tbl[table_body][:200]}...)print(f Image path:{tbl[img_path]})print(f Bbox:{tbl[bbox]})# 提取所有公式equations[itemforiteminresult.content_listifitem[type]equation]foreqinequations:print(f LaTeX:{eq[text][:150]}...)print(f Format:{eq.get(text_format,unknown)})# 提取所有文本块含标题层级texts[itemforiteminresult.content_listifitem[type]text]fortxtintexts:leveltxt.get(text_level,0)prefix *levelf[H{level}]iflevelelse [body]print(f{prefix}{txt[text][:100]})三种核心类型的字段差异字段tableequationtexttypetableequationtexttext—LaTeX 公式字符串纯文本/标题text_level——0正文, 1一级标题, 2二级标题…table_bodyHTMLtable字符串——table_caption字符串数组可能为空——table_footnote字符串数组可能为空——img_path图片文件名图片文件名—text_format—latex—bbox[x0, y0, x1, y1][x0, y0, x1, y1][x0, y0, x1, y1]page_idx页码页码页码实际返回中table_body是一个 HTML 表格字符串可以直接渲染或存入数据库。equation的text字段包含 LaTeX 表达式如$$\\frac{d}{dx}\\int_{a}^{x} f(t) dt f(x)$$配合text_format: latex标识。text中的text_level字段让你区分正文和标题层级——这在构建文档树时很有用。有个需要注意的细节SDK 的content_list中没有独立的image类型本地部署的content_list.json则包含image类型及image_caption字段SDK 层做了语义归并。视觉元素图、表、公式要么以table/equation类型出现并附带img_path要么在layout.json层才暴露为独立的图片块。换句话说SDK 层做了语义归并——纯装饰性图片或无法归类的插图会被过滤或合并到相邻文本块中。实用场景举例构建表格数据集过滤type table将table_body的 HTML 解析为结构化行/列数据用于训练表格理解模型。公式检索系统提取type equation的text字段LaTeX结合page_idx建立公式-文档索引。Markdown 文档还原将text块按text_level组织目录树配合bbox排序还原出保留层级和阅读顺序的文档。方案一适用于不需要像素级坐标的场景。如果你的流水线只需要这个 PDF 里有哪些公式、它们的 LaTeX 是什么或者第 3 页的表格 HTML 是什么content_list是最直接的入口没有之一。方案二save_all() 获取完整 zip读取 layout.json 做精准定位SDK 直接暴露的content_list做了大量简化——它按阅读顺序平铺了可读内容但丢弃了不少底层信息页面尺寸、旋转角度、被丢弃的页眉页脚、图表的细粒度子块body/caption/footnote 分离。这些信息在content_list层不可见。MinerU SDK 不直接暴露middle.json层级的数据。你需要通过result.save_all(dir)下载完整的解析结果 zip 包再从 zip 中读取layout.json——它对应本地部署的middle.json。API 返回结果中的full_zip_url字段提供了 zip 包的远程地址save_all()内部基于此 URL 下载。获取并读取 layout.jsonfrommineruimportMinerUimportzipfileimportjson clientMinerU(your-api-token)resultclient.extract(https://cdn-mineru.openxlab.org.cn/demo/example.pdf)# 下载完整 zip 包到本地目录result.save_all(./output_zip/)# 从 zip 中读取 layout.jsonzip_path./output_zip/result.zip# save_all 实际生成的文件名withzipfile.ZipFile(zip_path,r)aszf:withzf.open(layout.json)asf:layoutjson.load(f)# 检查后端类型print(fBackend:{layout[_backend]})# pipeline 或 vlm# 遍历每页forpageinlayout[pdf_info]:page_idxpage[page_idx]page_sizepage[page_size]# [width, height]print(f\n--- Page{page_idx}({page_size[0]}x{page_size[1]}) ---)# para_blocks 包含主要内容块forblockinpage[para_blocks]:btypeblock[type]bboxblock[bbox]# [x0, y0, x1, y1]angleblock.get(angle,0)print(f [{btype}] bbox{bbox}, angle{angle}°)# discarded_blocks 包含页眉/页脚/页码等fordblockinpage.get(discarded_blocks,[]):print(f [discarded:{dblock[type]}]{dblock[bbox]})bbox 坐标系统content_list即 SDK 的result.content_list的bbox坐标采用0-1000 归一化映射[x0, y0, x1, y1]四个整数均在 0 到 1000 之间分别对应页面左上角到右下角的相对位置。无论原始 PDF 页面是 A4 还是 A3坐标都统一映射到这个范围方便不同页面尺寸之间的坐标比较和渲染。layout.json即本地部署的middle.json的坐标系统因后端而异。在pipeline 后端下bbox使用原始像素值需要配合page_size字段换算比例在VLM 后端下layout.json仍为 0-1000 归一化而同一后端的model.json切换为 0-1 浮点数百分比格式。pipeline 与 VLM 后端的字段差异layout.json的顶层包含_backend字段标识解析模式维度pipeline 后端VLM 后端_backend值pipelinevlmpara_blocks块类型text,title,table,image,interline_equation同上额外支持code,list,algorithmdiscarded_blocks有限类型完整输出header,footer,page_number,aside_text,page_footnote旋转角度无angle字段每个 block 有angle字段 (0/90/180/270)bbox 坐标content_list0-1000 归一化layout.json使用原始像素值layout.json0-1000 归一化model.json0-1 百分比如果你需要处理有旋转内容的页面如扫描件中倾斜的表格VLM 后端的angle字段提供了必要的校正信息。如果你只需要标准阅读顺序的结构化数据pipeline 后端的输出更简洁。从 zip 中读取 imageswithzipfile.ZipFile(zip_path,r)aszf:# 列出所有图片文件img_files[fforfinzf.namelist()iff.startswith(images/)]forimg_nameinimg_files:zf.extract(img_name,./extracted_images/)layout.json的para_blocks中image类型的块会包含直接引用而在 SDKcontent_list层这些图片可能被合并到相邻表格或公式中。方案二适用于需要对图片、表格、公式做像素级精确对应的场景——比如将表格 HTML 渲染后与原 PDF 截图做视觉对比或者在自定义 UI 中按原始位置覆盖渲染提取出的元素。实战从一篇学术论文中批量提取表格 公式 图片以下代码展示了一个完整的端到端流程输入一篇学术论文 PDF通过 MinerU SDK 解析后从content_list中遍历所有元素将表格保存为独立 HTML 文件公式保存为 LaTeX 文件图片保存为本地文件。frommineruimportMinerUimportjsonimportosdefextract_elements(pdf_url:str,output_dir:str,token:str):os.makedirs(output_dir,exist_okTrue)clientMinerU(token)resultclient.extract(pdf_url,modelvlm)tables,equations,text_blocks[],[],[]foriteminresult.content_list:titem[type]pageitem[page_idx]ifttable:htmlitem.get(table_body,)caption.join(item.get(table_caption,[]))pathos.path.join(output_dir,ftable_p{page}_{len(tables)}.html)withopen(path,w,encodingutf-8)asf:f.write(f!--{caption}--\n{html})tables.append({page:page,html_path:path,caption:caption})eliftequation:latexitem.get(text,)pathos.path.join(output_dir,feq_p{page}_{len(equations)}.tex)withopen(path,w,encodingutf-8)asf:f.write(latex)equations.append({page:page,latex_path:path})elifttextanditem.get(text_level,0)0:text_blocks.append({page:page,level:item[text_level],text:item[text]})# 保存图片从 zip 中提取result.save_all(output_dir)# 输出汇总report{total_tables:len(tables),total_equations:len(equations),total_headings:len(text_blocks),tables:tables,equations:equations,headings:text_blocks}withopen(os.path.join(output_dir,extract_report.json),w)asf:json.dump(report,f,indent2,ensure_asciiFalse)print(f提取完成{len(tables)}个表格{len(equations)}个公式{len(text_blocks)}个标题)returnreport reportextract_elements(pdf_urlhttps://cdn-mineru.openxlab.org.cn/demo/example.pdf,output_dir./paper_extract,tokenyour-api-token)这段代码覆盖了典型的数据工程场景输入一篇学术论文 PDF输出保存为结构化文件。content_list的page_idx字段使你可以按页码组织提取结果text_level让标题树的重建变得可直接用。处理结果的文件结构大致如下paper_extract/ ├── extract_report.json# 提取结果索引├── table_p0_0.html# 第 0 页第一个表格├── table_p3_1.html# 第 3 页第二个表格├── eq_p1_0.tex# 第 1 页第一个公式├── eq_p2_0.tex# 第 2 页第二个公式├── result.zip# save_all() 下载的完整包├── images/# 从 zip 中解压的图片│ ├── a8ecda1c69b27e4f.jpg │ └── 181ea56ef185060d.jpg └── full.md# Markdown 全文输出提取出的表格 HTML 可以直接在浏览器中渲染查看公式 LaTeX 可以用 MathJax 或 LaTeX 编译器编译图片则保存在images/目录下。extract_report.json提供了完整的索引方便下游流水线按需加载。需要注意的是content_list中的img_path指向的是 zip 包images/目录内的文件名而非完整 URL 或绝对路径。如果你需要通过save_all()以外的途径独立获取图片资源可以从 zip 包的images/路径直接读取。输入输出对应关系原始 PDF 内容content_list类型输出文件第 2 页的统计表格type: table→table_body(HTML)table_p2_0.html第 5 页的多行公式type: equation→text(LaTeX)eq_p5_0.tex第 1 页的示意图type无独立 image → 通过save_all()的images/目录获取images/*.jpg正文段落type: texttext_level: 0汇总到extract_report.json表格和公式在content_list层就已经是结构化状态无需额外解析。图片则需要通过 zip 包获取——这是content_list层做了语义归并的代价。三种方案选型对比维度方案一content_list方案二save_all()layout.json实战组合方案适用场景快速原型、自动化流水线、仅需结构化数据像素级定位、自定义渲染、需访问 discarded_blocks生产级全量提取是否需要 Token需申请需申请需申请坐标精度0-1000 归一化 bboxbbox page_size angle同方案一代码复杂度低1 行属性访问中下载 zip 解析 JSON中二次开发灵活度中等字段预定义高访问完整 middle 结构高后端差异暴露无_backend字段明示可根据需要切换图片提取方式通过img_path间接获取从 zip 的images/目录提取通过save_all()获取如果你的场景只需要表格 HTML 和公式 LaTeX方案一够用。如果需要像素级坐标或页眉页脚等辅助信息方案二更适合。实战方案面向完整数据管线——从 PDF 到结构化文件系统。选型决策流程结构化数据即可→ 方案一。一行result.content_listbbox用于排序。需要像素级坐标→ 方案二。page_size配合bbox精确还原位置。需要旋转元素或页眉页脚→ 方案二 VLM 后端。生产级全量提取→ 实战方案组合content_list和save_all()。方案一和方案二可以配合使用先用content_list过滤table获取 HTML再通过save_all()下载 zip 读取layout.json获取精确坐标。结尾与关键词收口MinerU 覆盖了从 PDF 元素提取到结构化输出的链路。无论是表格提取、公式提取还是图片提取其云端 SDK/API 都提供了分层的访问接口——从零代码感知的content_list到完整像素级控制的layout.json。根据你的工程需求选择合适的层级可以直接在数据流水线中消费这些结构化输出。在技术实现上MinerU 2.5-Pro 在元素级解析上达到了文本 Edit Distance 0.019、公式 CDM 97.29、表格 TEDS 91.10 的表现——这些数据来自其技术报告的独立评估。如果你需要在自己的项目中进行 PDF 解析、表格识别、公式提取或版面还原可以结合自身对 Token 控制、坐标精度和代码复杂度的需求从上述三种方案中选择最匹配的一种。开源仓库https://github.com/opendatalab/MinerU官方网站https://mineru.net从 PDF 中提取结构化数据不再意味着只能拿到一段破碎的纯文本。通过版面分析还原文档的逻辑结构再逐元素提取表格、公式和图片——这种基于结构化元素抽取的路径使 PDF 的结构化数据可用于下游流水线和自动化处理。