从 200+ 篇材料科学 PDF 文献中自动抽取结构化信息(材料身份、结构、性能、合成、应用、元数据),跨文献实体融合,并提供 Streamlit Web 可视化界面。
本项目是一个面向材料科学领域的智能文献解析系统,作为竞赛 Demo 实现。系统接收约 210 篇 PDF 格式的材料文献(包括期刊论文和学位论文),通过以下技术栈完成端到端的结构化信息抽取:
- MinerU 精准解析 API(VLM 模型):将 PDF 转为结构化 JSON(含表格、公式、图片、章节层级)
- DeepSeek-V4-Pro(思考模式 + JSON Output):基于大模型做 Schema 级结构化抽取
- Streamlit:提供 Web 可视化查询界面
整个流程分为 PDF 解析 → 语义分块 → Schema 抽取 → 原文溯源校验 → 跨文献聚合 五个阶段,支持断点续跑、并行加速、失败重试。
材料文献/赛题数据/*.pdf (210 篇)
│
▼
┌─────────────────────────────────────────┐
│ pdf_splitter.py │ 长 PDF 本地切片(>180 页,规避 MinerU 200 页限制)
└─────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────┐
│ run_parse.py → mineru_client.py │ 批量提交 MinerU API,多 key 并行,chunk 级状态机
└─────────────┬───────────────────────────┘
▼ data/parsed/{chunk_id}/content_list.json
┌─────────────────────────────────────────┐
│ doc_chunker.py │ MinerU 输出 → Document(章节、摘要、表格、全文)
└─────────────┬───────────────────────────┘
▼
┌─────────────────────────────────────────┐
│ extractor.py │ 两阶段抽取(DeepSeek-V4-Pro)
│ Phase 1 串行: 摘要锚点 + Material + Metadata
│ Phase 2 并行: Structure + Properties + Synthesis + Application + Relationships
│ 后处理: verify_original_texts 原文溯源校验
│ 可选: refine_extraction 迭代精炼
└─────────────┬───────────────────────────┘
▼ data/extracted/{stem}.json
┌─────────────────────────────────────────┐
│ aggregator.py │ Union-Find 三级匹配(精确→别名→模糊 Jaccard)
│ │ + 实体关系知识图谱
└─────────────┬───────────────────────────┘
▼ data/aggregated/entities.json + properties.tsv + relationships.json
┌─────────────────────────────────────────┐
│ app.py (Streamlit) │ 总览 / 单文献 / 实体浏览 / 性能查询 / 流水线
└─────────────────────────────────────────┘
- Python 3.10+(推荐 3.11 或 3.12)
- conda 或 miniconda(用于环境管理)
- 网络连接(需要访问 MinerU API 和 DeepSeek API)
# 创建环境
conda create -n minerU_env python=3.11 -y
conda activate minerU_env
# 安装依赖包
pip install streamlit openai requests pypdf项目中用到的外部 Python 包只有 4 个:
| 包名 | 用途 | 最低版本建议 |
|---|---|---|
streamlit |
Web 可视化界面 | ≥1.30 |
openai |
调用 DeepSeek API(OpenAI SDK 兼容) | ≥1.0 |
requests |
调用 MinerU API | ≥2.28 |
pypdf |
PDF 切片(处理超长文献) | ≥3.0 |
其余均为 Python 标准库(json、re、difflib、concurrent.futures、dataclasses、pathlib、argparse、threading、zipfile 等),无需额外安装。
确保项目目录结构如下:
基于MinerU的材料文献智能解析应用/
├── config.json ← API 密钥配置(需手动填写)
├── 材料文献/
│ └── 赛题数据/ ← 放置 210 篇 PDF(1.pdf ~ 210.pdf)
├── 赛题说明/
│ ├── Schema.json ← 抽取目标 Schema 定义
│ └── *.json ← 2 份赛题标注样本(用作 few-shot)
├── src/ ← 全部源代码
├── data/ ← 运行时自动生成
├── run_streamlit.bat ← Windows 启动脚本
└── run_streamlit.sh ← Linux/Git Bash 启动脚本
在项目根目录创建 config.json,填入你的 API 密钥:
{
"mineru_api_keys": [
"<MinerU JWT Token 1>",
"<MinerU JWT Token 2>"
],
"deepseek_api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deepseek_user_id": "mineru_extract"
}各字段说明:
mineru_api_keys:MinerU 精准解析 API 的 JWT Token 数组。支持多个 key 并行解析,至少填 1 个。可在 MinerU 官网 注册获取。deepseek_api_key:DeepSeek API 密钥。在 DeepSeek 开放平台 获取。deepseek_user_id(可选):用于 DeepSeek 并发隔离的标识符,不同 user_id 各自独立享有 500 并发上限。默认值"mineru_extract"。多批次并行跑时建议为每批设不同的 user_id。
config.py 同时兼容以下旧格式,无需手动迁移:
{
"api_key": "<单个 MinerU Token>",
"deepseek_api_key": "sk-xxx"
}或散列格式:
{
"api_key_1": "<Token 1>",
"api_key_2": "<Token 2>",
"deepseek_api_key": "sk-xxx"
}扫描所有 PDF,统计页数和文件大小,输出到 data/pdf_meta.json。
conda activate minerU_env
python src/utils/survey_pdfs.py这一步会识别出超过 MinerU 200 页限制的长 PDF(如学位论文),为后续切片做准备。
将超过 180 页的 PDF 在本地切分成多个小块(每块 ≤180 页,留 20 页缓冲)。
# 生成切片计划(读取上一步的 pdf_meta.json)
python src/pdf_splitter.py
# 执行并行切片(实际切割 PDF 文件)
python src/utils/split_all_parallel.pypdf_splitter.py 读取 data/pdf_meta.json,规划哪些 PDF 需要切片,输出 data/split_plan.json。split_all_parallel.py 则实际执行切割,将切片 PDF 输出到 data/splits/{stem}/ 目录。短 PDF(≤180 页)不做切片,直接以原文件参与后续流程。
将所有 PDF chunk 提交到 MinerU 精准解析 API,转为结构化 JSON。
# 先跑一小批测试(每个 key 只提交 1 个 batch)
python src/run_parse.py --max-batches-per-key 1
# 确认无误后全量运行
python src/run_parse.py命令行参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--poll-interval |
60 | 轮询间隔(秒) |
--max-batches-per-key |
无限制 | 每个 key 最多提交几个 batch(用于测试) |
--max-per-batch |
50 | 每个 batch 最多几个文件 |
--keys |
all |
使用哪些 key(all 或逗号分隔的索引如 0,1) |
--harvest-only |
false | 仅收割已完成的 batch,不提交新任务 |
特性:
- 多 key 并行:每个 MinerU key 跑独立工作线程
- 断点续跑:状态持久化在
data/parse_state.json,中断后再跑会自动跳过已完成的 chunk - 失败隔离:单个 chunk 失败不影响其他 chunk
失败重试: 如果有解析失败的 chunk,可用 retry_failed.py 自动重试:
# 查看哪些 chunk 失败了(不修改状态)
python src/retry_failed.py --dry-run
# 执行重试(根据错误类型自动选择策略:开启 OCR / 调整切片大小)
python src/retry_failed.py
# 强制某些文献以 OCR 模式重提
python src/retry_failed.py --force-ocr-stems 15,42| 参数 | 默认值 | 说明 |
|---|---|---|
--dry-run |
false | 仅打印,不修改 state |
--max-retries |
3 | 同一 chunk 最大重试次数 |
--force-ocr-stems |
空 | 逗号分隔的 stem 列表,强制以 OCR 模式重提 |
对每个 MinerU 解析完成的文献执行大模型结构化抽取。
# 冒烟测试 10 篇
python src/run_extract.py --limit 10 --user-id smoke_10
# 扩大到 30 篇
python src/run_extract.py --limit 30 --user-id smoke_30
# 全量并行(10 篇同时抽取)
python src/run_extract.py --concurrent 10
# 只抽取指定文献
python src/run_extract.py --only 3,7,15
# 启用迭代精炼(抽完后自查修补 1 轮)
python src/run_extract.py --refine 1 --concurrent 5命令行参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--limit |
全部 | 最多抽取多少篇(冒烟测试用) |
--concurrent |
1 | 文档级并行数(建议 5-15,每篇内部 5 路并行) |
--only |
全部 | 只抽取指定的文献编号(逗号分隔) |
--force |
false | 强制重新抽取已有结果 |
--user-id |
自动 | DeepSeek user_id(不同批次用不同 id 隔离并发) |
--refine |
0 | 迭代精炼轮数(0=不精炼,1=自查修补一轮) |
单篇抽取: 也可以直接调用 extractor.py 抽取单篇(默认抽取第 3 篇):
python src/extractor.py # 抽取 data/parsed/3 → data/extracted/3.json将所有抽取结果做实体融合和关系图谱构建。
python src/aggregator.py输出文件:
data/aggregated/entities.json— 同一材料跨文献合并(含所有性能、合成、应用数据)data/aggregated/properties.tsv— 全部性能数据扁平化表格(带文献溯源)data/aggregated/relationships.json— 材料间关系知识图谱(节点 + 边)
# Windows
run_streamlit.bat
# 或手动启动
streamlit run src/app.py --server.port 8501浏览器打开 http://localhost:8501 即可使用。Web 界面包含 5 个页面:
- 总览 Dashboard — 抽取进度、文献统计、性能类别分布
- 单文献详情 — 查看某篇文献的完整抽取结果(6 个 Schema 块)
- 实体浏览器 — 跨文献融合后的材料实体,含关系图谱可视化;支持重新聚合
- 性能数据查询 — 按材料名、性能类别、关键词搜索所有性能数据
- 抽取流水线 — 上传新 PDF 并在线执行抽取
| 模块 | 说明 |
|---|---|
config.py |
统一配置加载,兼容多种 config.json 格式 |
mineru_client.py |
MinerU 精准解析 API 客户端(上传→轮询→下载解压) |
pdf_splitter.py |
长 PDF 本地切片(pypdf,180 页/块) |
doc_chunker.py |
MinerU content_list.json → Document 对象(章节分块、摘要识别、表格提取) |
deepseek_client.py |
DeepSeek-V4-Pro 客户端(OpenAI SDK 兼容,启用 thinking 模式 + JSON Output + 429 退避) |
extractor.py |
两阶段 Schema 抽取核心(Phase 1 串行锚点 + Phase 2 并行 5 块 + 原文校验 + 迭代精炼) |
schema_validator.py |
抽取结果结构化校验(字段存在性、类型检查) |
few_shot.py |
Few-shot 示例管理(静态标注 + 动态验证池,上下文感知智能选择) |
aggregator.py |
跨文献实体聚合(Union-Find 三级匹配)+ 关系知识图谱 |
| 脚本 | 说明 |
|---|---|
run_parse.py |
批量 MinerU 解析入口(多 key 并行,chunk 级状态机,断点续跑) |
run_extract.py |
批量 Schema 抽取入口(文档级并行,冒烟测试,迭代精炼) |
retry_failed.py |
自动重试 MinerU 解析失败的 chunk(开启 OCR / 调整切片大小) |
app.py |
Streamlit Web 应用 |
| 脚本 | 说明 |
|---|---|
utils/survey_pdfs.py |
统计全部 PDF 页数和大小 → data/pdf_meta.json |
utils/split_all_parallel.py |
并行切片所有超长 PDF → data/split_plan.json |
运行时自动生成的文件全部在 data/ 下:
data/
├── parsed/ MinerU 解析输出(每个 chunk 一个子目录)
│ ├── {chunk_id}/
│ │ ├── *_content_list.json 结构化内容(核心输入)
│ │ ├── full.md 全文 Markdown
│ │ ├── full.html 全文 HTML
│ │ ├── layout.json 版面分析
│ │ └── images/ 提取的图片
│ └── ...
├── splits/ 长 PDF 切片(本地生成的小 PDF)
├── raw_zips/ MinerU 返回的原始 zip 包(备份)
├── extracted/ Schema 抽取结果(每篇一个 JSON)
│ ├── 1.json
│ ├── 2.json
│ └── ...
├── aggregated/ 跨文献聚合结果
│ ├── entities.json 实体融合
│ ├── properties.tsv 性能数据扁平表
│ └── relationships.json 关系知识图谱
├── fewshot_pool/ 动态 few-shot 样本池(人工校验后的高质量结果)
├── logs/ 运行日志
├── parse_state.json MinerU 解析状态机(断点续跑)
├── split_plan.json PDF 切片计划
└── pdf_meta.json PDF 页数/大小统计
系统按赛题定义的 Schema 抽取 6 个顶层块,以下是各块的字段概要(详见 赛题说明/Schema.json):
| Schema 块 | 主要内容 | 关键特征 |
|---|---|---|
| Material | CAS 号、通用名、缩写、化学名、材料类型/子类型 | 聚合主键 |
| Structure | 分子式、SMILES、基本单元、分子量、拓扑结构、结晶度、孔结构等 | 软校验 |
| Properties | 性能类别(11 类)、名称、表征方法、条件、值、单位、原文引用 | 数组,严格原文校验 |
| Synthesis | 合成工艺、原料清单、催化剂、溶剂、参数、设备、后处理 | 参数原文校验 |
| Application | 应用领域、应用描述 | — |
| Metadata | 来源、标题、年份、作者及机构 | — |
Properties 的 11 个性能类别:PHYSICAL、MECHANICAL、THERMAL、ELECTRICAL、MAGNETIC、OPTICAL、CHEMICAL、SURFACE、PROCESS、ENVIRONMENTAL、FUNCTIONAL。
-
摘要锚点引导全文:Phase 1 先从摘要抽出材料名/缩写/性能/工艺关键词作为"锚点",Phase 2 用锚点指导全文各块抽取,确保摘要与正文的一致性。
-
严格原文溯源校验:Properties 和 Synthesis.Parameters 的每条数据必须包含
Original text字段,系统会在全文中做五级匹配验证(精确→归一化→高模糊→低模糊→n-gram),无法溯源的条目会被剔除。Structure 和其他块采用软校验(标注置信度但不丢弃)。 -
两阶段并行抽取:Phase 1 串行抽取 Material + Metadata(作为后续块的上下文锚点),Phase 2 用 ThreadPoolExecutor 5 路并行抽取 Structure / Properties / Synthesis / Application / Relationships。
-
Union-Find 三级实体聚合:精确主键匹配(CAS > Chemical Name > Generic Name)→ 别名交叉引用 → Token Jaccard 模糊匹配(阈值 0.75),解决同一材料在不同文献中命名不一致的问题。
-
动态 Few-shot 示例池:静态层(赛题标注样本)+ 动态层(人工校验过的抽取结果),按 11 个性能类别和材料子类型做智能选择,确保示例覆盖度和相关性。
-
迭代精炼机制:可选的 verify-patch 轮次,第一轮抽完后将结果和原文关键段落发回大模型自查,增量修补缺漏,带去重防止重复条目。
-
实体关系知识图谱:额外抽取 8 种材料间关系(共聚物、共混物、复合材料、改性、变体、前驱体、组分、对比),跨文献聚合后构建节点-边图。
Q: MinerU 解析报错 "number of pages exceeds limit"
确保已执行第二步(PDF 切片)。pdf_splitter.py 会将超过 180 页的 PDF 切成小块。如果仍有问题,可运行 python src/retry_failed.py 自动处理。
Q: DeepSeek API 返回 429 Too Many Requests
系统已内置 429 退避逻辑。如果频繁出现,降低 --concurrent 参数值,或使用 --user-id 为不同批次设置不同标识符以隔离并发额度。
Q: 抽取结果中某些字段为空
正常现象。并非所有文献都包含全部字段(如部分文献无 Synthesis 或 Application 信息)。schema_validator.py 会在日志中标注缺失的必填字段。
Q: 如何重新抽取某篇文献
# 删除旧结果后重抽
del data\extracted\3.json
python src/run_extract.py --only 3
# 或强制重抽
python src/run_extract.py --only 3 --forceQ: 如何将人工校验过的结果加入 few-shot 池 在 Python 中调用:
from few_shot import add_to_pool
add_to_pool("3", extraction_result) # stem="3", result 为校验过的 JSON之后所有抽取都会自动利用这份高质量样本作为 few-shot 示例。
# 0. 环境准备
conda create -n minerU_env python=3.11 -y
conda activate minerU_env
pip install streamlit openai requests pypdf
# 1. 填写 config.json(见第四节)
# 2. PDF 统计 + 切片
python src/utils/survey_pdfs.py
python src/pdf_splitter.py
python src/utils/split_all_parallel.py
# 3. MinerU 批量解析
python src/run_parse.py --max-batches-per-key 1 # 测试
python src/run_parse.py # 全量
# 4. 失败重试(可选)
python src/retry_failed.py --dry-run # 先看看
python src/retry_failed.py # 执行
# 5. Schema 抽取
python src/run_extract.py --limit 10 --user-id smoke # 冒烟
python src/run_extract.py --concurrent 10 # 全量
# 6. 跨文献聚合
python src/aggregator.py
# 7. 启动 Web 界面
streamlit run src/app.py --server.port 8501