当前位置: 首页 > news >正文

从 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.jsonmiddle.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 client=MinerU("your-api-token")result=client.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()甚至不需要 Token:

client=MinerU()# 不传 token,进入 flash-only moderesult=client.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:level=txt.get("text_level",0)prefix=" "*level+f"[H{level}]"iflevelelse" [body]"print(f"{prefix}{txt['text'][:100]}")

三种核心类型的字段差异

字段tableequationtext
type"table""equation""text"
textLaTeX 公式字符串纯文本/标题
text_level0=正文, 1=一级标题, 2=二级标题…
table_bodyHTML<table>字符串
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 表格字符串,可以直接渲染或存入数据库。equationtext字段包含 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.json

frommineruimportMinerUimportzipfileimportjson client=MinerU("your-api-token")result=client.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:layout=json.load(f)# 检查后端类型print(f"Backend:{layout['_backend']}")# "pipeline" 或 "vlm"# 遍历每页forpageinlayout["pdf_info"]:page_idx=page["page_idx"]page_size=page["page_size"]# [width, height]print(f"\n--- Page{page_idx}({page_size[0]}x{page_size[1]}) ---")# para_blocks 包含主要内容块forblockinpage["para_blocks"]:btype=block["type"]bbox=block["bbox"]# [x0, y0, x1, y1]angle=block.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"pipeline""vlm"
para_blocks块类型text,title,table,image,interline_equation同上,额外支持code,list,algorithm
discarded_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 中读取 images

withzipfile.ZipFile(zip_path,"r")aszf:# 列出所有图片文件img_files=[fforfinzf.namelist()iff.startswith("images/")]forimg_nameinimg_files:zf.extract(img_name,"./extracted_images/")

layout.jsonpara_blocksimage类型的块会包含直接引用,而在 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_ok=True)client=MinerU(token)result=client.extract(pdf_url,model="vlm")tables,equations,text_blocks=[],[],[]foriteminresult.content_list:t=item["type"]page=item["page_idx"]ift=="table":html=item.get("table_body","")caption="".join(item.get("table_caption",[]))path=os.path.join(output_dir,f"table_p{page}_{len(tables)}.html")withopen(path,"w",encoding="utf-8")asf:f.write(f"<!--{caption}-->\n{html}")tables.append({"page":page,"html_path":path,"caption":caption})elift=="equation":latex=item.get("text","")path=os.path.join(output_dir,f"eq_p{page}_{len(equations)}.tex")withopen(path,"w",encoding="utf-8")asf:f.write(latex)equations.append({"page":page,"latex_path":path})elift=="text"anditem.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,indent=2,ensure_ascii=False)print(f"提取完成:{len(tables)}个表格,{len(equations)}个公式,{len(text_blocks)}个标题")returnreport report=extract_elements(pdf_url="https://cdn-mineru.openxlab.org.cn/demo/example.pdf",output_dir="./paper_extract",token="your-api-token")

这段代码覆盖了典型的数据工程场景:输入一篇学术论文 PDF,输出保存为结构化文件。content_listpage_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: "text"+text_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_listsave_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 的结构化数据可用于下游流水线和自动化处理。

http://www.jsqmd.com/news/775053/

相关文章:

  • 2026年4月技术好的美缝源头厂家推荐,地砖美缝/全屋美缝/美缝/瓷砖美缝/美缝施工,美缝品牌推荐 - 品牌推荐师
  • 北京AI研究院:机器人实现视频动作学习完成复杂任务能力提升
  • Pod 状态 CrashLoopBackOff 报错怎么查看具体日志原因
  • 浏览器扩展开发实战:构建个人知识管理工具NativeMindExtension
  • Windows下内核文件隐藏技术
  • 将Taotoken集成到自动化工作流中实现智能内容批量处理
  • 基于Laravel与私有AI的Noton文档平台:自托管部署与实战指南
  • AISMM模型成熟度评估全解析(附2024最新打分细则与组织自测速查表)
  • qt:QList和ExtraSelection
  • Armv9-A架构Cortex-A720核心寄存器解析与应用
  • Automation1Studio 界面七 Transformation(坐标变换)​ 设置界面
  • YOLO11涨点优化:损失函数优化 | 引入EIoU与Focal Loss结合,同时解决包围框宽高比例与正负样本不平衡问题
  • 低空经济新蓝海:一网统管平台如何支持“低空+城市治理“?
  • 软件测试生产验证缺陷常见流程
  • 2026AI大模型API代理站亲测:五大平台硬核数据横评,为开发者提供权威选型指南
  • 3分钟掌握iOS位置模拟神器:iFakeLocation跨平台实战指南
  • 麻省理工新工具:虚拟小提琴提前试音效,助力制琴师设计
  • 找不到童年游戏?小霸王游戏机珍藏84合1免费下载一键搞定
  • 静态路由拓展配置实验
  • 量子深度学习硬件平台与软件工具解析
  • ChanlunX:通达信缠论分析插件的技术实现与应用实践
  • BuildingAI 上部署自定义工作流智能体:5 个实用技巧
  • 【独家首发】奇点大会闭门报告实录:AISMM在金融/医疗/制造三大场景的ROI测算模型(含2027Q1商用倒计时)
  • SGR Agent Core:基于Schema-Guided Reasoning的深度研究智能体框架解析
  • TimeoutError: [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。
  • 量子计算四支柱协议:评估量子优势的系统方法论
  • OpenClaw安全审计工具:本地优先、低依赖的自动化配置与密钥泄露检测
  • 3大技术突破:Whisky如何让Windows程序在macOS上原生运行
  • 融资“狂飙”:月之暗面半年吸金超39亿美元,DeepSeek估值450亿“等梁文锋点头”
  • 前端开发选 VSCode 还是 Sublime Text 配置更灵活?