Jean's Blog

一个专注软件测试开发技术的个人博客

0%

RAG组件--数据导入之pdf解析

用各种工具解析PDF

各种工具的简单比较

文档加载器 说明 Package/API 特点
PyPDF 使用 pypdf 加载和解析 PDF 文件 Package 高效轻量,适合处理简单 PDF 文档
Unstructured 使用 Unstructured 的开源库加载 PDF 文件 Package/API 兼容多种文档格式,支持内容提取和分析
Amazon Textract 使用 AWS API 加载 PDF 文件 API 云服务支持,适合大批量文档的 OCR 处理
MathPix 使用 MathPix 加载和解析 PDF 文件 API 专为数学公式设计,精准解析复杂内容
PDFPlumber 使用 PDFPlumber 加载 PDF 文件 Package 丰富的 PDF 内容控制和处理功能
PyPDFDirectry 加载目录中的 PDF 文件 Package 批量加载,便于处理多个 PDF 文档高效解析,支持 PDF 页面的渲染和转换
PyPDFium2 使用 PyPDFium2 加载 PDF 文件 Package
PyMuPDF 使用 PyMuPDF 加载 PDF 文件 Package 速度优化,支持复杂 PDF 的精细化处理
PDFMiner 使用 PDFMiner 加载 PDF 文件 Package 适合文本抽取,处理 PDF 中的嵌入文字内容
  • 工具多样性:PDF来源多样(Word转换/扫描生成等)导致解析难度大,需根据文档类型选择合适工具
  • PyPDF特点:轻量高效但仅能提取纯文本,无法处理图片/表格等复杂内容
  • Unstructured优势:兼容多格式,支持内容提取与分析,综合能力较强
  • MathPix专长:专为数学公式设计,能精准解析复杂数学内容
  • PDFPlumber功能:提供丰富的PDF内容控制和处理功能
  • 云服务方案:如Amazon Textract适合大批量文档OCR处理

三大类解析方法

  • 基于规则的解析:
    • 适用场景:规范生成的PDF(文本/表格等)
    • 原理:直接解析PDF内部结构,按照格式规范提取内容
    • 优势:处理速度快,内存占用少
  • 基于深度学习的解析:
    • 核心能力:引入视觉模型识别图像,执行OCR检测
    • 处理流程:文本框聚合→图像OCR→文本分类→结构化输出
    • 典型应用:扫描件、复杂版式文档处理
  • 多模态大模型解析:
    • 代表工具:ChatGPT/Qwen VL等视觉模型
    • 局限性:需上传知识库至API,存在数据隐私风险

执行以下操作:

  • 通过启发式方法或机器学习推理将文本框聚合为行、段落或其他结构;
  • 对图像运行OCR,以检测其中的文本;
  • 将文本分类为段落、列表、表格或其他结构;
  • 将文本组织成表格的行和列,或以键值对的形式呈现。

用PyPDFLoader等工具进行简单文本提取

  • 基础功能:
    • 快速加载PDF文档
    • 准确提取纯文本内容(包括复杂排版文字)
  • 适用场景:
    • 处理结构良好的PPT转PDF文档
    • 需要快速获取基础文本内容时
  • 局限性:
    • 无法提取图片/表格等非文本元素
    • 不提供文档元数据信息

示例代码如下:

1
2
3
4
5
6
7
8
from langchain_community.document_loaders import PyPDFLoader

file_path = "../data/黑悟空/黑神话悟空.pdf"
loader = PyPDFLoader(file_path)
pages = loader.load()
print(f"加载了 {len(pages)} 页PDF文档")
for page in pages:
print(page.page_content)

执行结果,输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
加载了 5 页PDF文档
08.20
直面天命
序章

黑神话:悟空

是一款基于

西游记

改编的中国神话动作角色扮演游戏,玩家化身
“天命之人”,在险象环生的西游冒险中追寻传说背后的秘密。
改编自公元
1592
年中国神魔小说

西游记



B L A C K M Y T H



...
启程
无惧
历练
逢缘
……

用PyMuPDF等工具进行文本内容增强提取

  • 增强功能:
    • 获取文档元数据(作者/标题等)
    • 统计每页图片数量(示例检测到15张图片)
    • 识别页面尺寸(968.8x540.0)
  • 技术原理:
    • 旧称fitz,基于PDF内部规范解析
    • 支持精细化元素定位
  • 典型应用:
    • 需要分析文档结构的场景
    • 获取图片/链接等非文本元素信息

安装包:

1
pip install -qU PyMuPDF

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pymupdf

# 打开PDF文件
doc = pymupdf.open("../data/黑悟空/黑神话悟空.pdf")
text = [page.get_text() for page in doc]
print(text)

# 示例: 使用PyMuPDF的基础功能
print("=== PyMuPDF 基本信息提取 ===")
print(f"文档页数: {len(doc)}")
print(f"文档标题: {doc.metadata['title']}")
print(f"文档作者: {doc.metadata['author']}")
print(f"文档元数据: {doc.metadata}") # 比Unstructured提供更多元数据

# 遍历每一页
for page_num, page in enumerate(doc):
# 提取文本
text = page.get_text()
print(f"\n--- 第{page_num + 1}页 ---")
print("文本内容:", text[:200]) # 显示前200个字符

# 提取图片
images = page.get_images()
print(f"图片数量: {len(images)}")

# 获取页面链接
links = page.get_links()
print(f"链接数量: {len(links)}")

# 获取页面大小
width, height = page.rect.width, page.rect.height
print(f"页面尺寸: {width} x {height}")

doc.close()

执行结果,内容输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
['08.20\n直面天命\n', '序章\n《黑神话:悟空》是一款基于《西游记》改编的中国神话动作角色扮演游戏,玩家化身\n“天命之人”,在险象环生的西游冒险中追寻传说背后的秘密。\n', '改编自公元1592年中国神魔小说《西游记》\n', '启程\nB\nL\nA\nC\nK\nM\nY\nT\nH\n第\n壹\n章\n关卡多端\n雄奇壮丽,光怪陆离\n重走西游故地,再写神话结局\nW\nU\nK\nO\nN\nG\n启程\n无惧\n历练\n逢缘\n', '天命\n你,也想当神仙?\n天命人\nT\nI\nA\nN\nM\nI\nN\nG\n世\n人\n都\n晓\n神\n仙\n好\n唯\n有\n功\n名\n忘\n不\n了\n古\n今\n将\n相\n在\n何\n方\n荒\n冢\n一\n堆\n草\n末\n了\n启程\n无惧\n历练\n逢缘\n']
=== PyMuPDF 基本信息提取 ===
文档页数: 5
文档标题:
文档作者: 风 听
文档元数据: {'format': 'PDF 1.7', 'title': '', 'author': '风 听', 'subject': '', 'keywords': '', 'creator': 'Microsoft® PowerPoint® for Microsoft 365', 'producer': 'Microsoft® PowerPoint® for Microsoft 365', 'creationDate': "D:20241107154241+08'00'", 'modDate': "D:20241107154241+08'00'", 'trapped': '', 'encryption': None}

--- 第1页 ---
文本内容: 08.20
直面天命

图片数量: 10
链接数量: 0
页面尺寸: 960.0 x 540.0

--- 第2页 ---
文本内容: 序章
《黑神话:悟空》是一款基于《西游记》改编的中国神话动作角色扮演游戏,玩家化身
“天命之人”,在险象环生的西游冒险中追寻传说背后的秘密。

图片数量: 15
链接数量: 0
页面尺寸: 960.0 x 540.0

--- 第3页 ---
...

图片数量: 19
链接数量: 0
页面尺寸: 960.0 x 540.0
……

PyMuPDF (fitz) 与 Unstructured 对比:

优势:

  1. 更快的处理速度
  2. 更细粒度的PDF控制能力
  3. 可以获取更多元数据和文档结构信息
  4. 内存占用更少
  5. 不依赖外部工具

劣势:

  1. 文本提取的智能化程度较低
  2. 没有自动的文档结构理解
  3. 需要手动处理布局分析

用pytesseract+pdf2image 等工具进行文本内容提取

  • 配置要求:
    • 需安装tesseract-ocr及中文语言包
    • 确保输出目录存在(避免FileNotFoundError)
  • 工作原理:
    • 先将PDF转为图像
    • 再通过OCR识别图像文字
  • 适用局限:
    • 对扫描件效果较好
    • 处理原生PDF时可能出现乱码(如示例中中文识别问题)

系统需要安装相关包:

1
2
3
4
5
6
7
# Mac
brew install tesseract-ocr
brew install tesseract-lang

# Ubuntu
sudo apt-get install tesseract-ocr
sudo apt-get install tesseract-ocr-chi-sim

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pdf2image
import pytesseract
import os

# 创建 output 目录
output_dir = 'output'
os.makedirs(output_dir, exist_ok=True)

# 将 PDF 转换为图片并保存
images = pdf2image.convert_from_path('../data/黑悟空/黑神话悟空.pdf')
for i, image in enumerate(images):
image.save(f'{output_dir}/page_{i+1}.png')

# 使用 pytesseract 提取文本
for i, image in enumerate(images):
text = pytesseract.image_to_string(image, lang='chi_sim')
print(f"第 {i+1} 页文本:")
print(text)
print("\n")

执行结果,内容输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
第 1 页文本:


,申 国-歼 事

GAME SCIENCE

游戏科学

08. 20

直面天





第 2 页文本:
游戏科学
GAME SCIENCE

世界虽医, 中国故事

-DC
...

……

内容总结

  • 工具选型原则:
    • 规范PDF优先选择PyPDF/PyMuPDF
    • 扫描件选择pytesseract+pdf2image
    • 复杂文档考虑Unstructured等综合方案
  • 性能对比:
    • 规则解析工具:处理速度快(PyPDF仅需数秒)
    • OCR方案:处理耗时长(需图像转换+文字识别)
  • 实践建议:
    • 测试不同工具的实际效果
    • 根据文档特点组合使用多种工具

PDF转Markdown

用Marker工具把PDF转Markdown

Marker工具介绍

GitHub地址:https://github.com/datalab-to/marker

  • 功能特点:
    • 支持将PDF、图像、PPTX、DOCX等多种格式转换为Markdown、JSON和HTML
    • 能保留表格、公式、内联数学、链接、代码块等复杂结构
    • 可提取并保存图片,去除页眉页脚等冗余内容
    • 支持通过LLM提升转换准确率
  • 性能优势:
    • 在GitHub上获得27k星标,社区活跃度高
    • 相比同类工具(Llamaparse、Mathpix)具有更快的处理速度
    • 批量模式下H100显卡可达122页/秒的处理速度
  • 安装方式:
    • 基础安装:pip install marker-pdf
    • 完整功能:pip install marker-pdf[full]

实操转换文档

  • 转换流程:
    • 首次运行需下载布局识别、文本识别等深度学习模型
    • 使用命令:marker_single /path/to/file.pdf
    • 可指定输出格式:-output_format [markdown|json|html]
  • 技术原理:
    • 基于多个小型深度学习模型协同工作
    • 包含文字识别、图像识别、布局识别等模块
    • 非首次运行时速度更快(约8秒处理一个复杂PDF)
  • 商用注意事项:
    • 研究和个人使用免费
    • 商用需遵守CC-BY-NC-SA 4.0许可
    • 年收入<$500万且融资<$500万的企业可豁免限制

用其他PDF转Markdown工具

介绍PDFOCR工具

GitHub地址:https://github.com/datalab-to/surya

  • 开发背景:由Vik Paruchuri开发,是多语言PDF OCR工具
  • 项目规模:获得17k+星标,支持90+种语言的识别
  • 核心功能:
    • 表格识别(检测行列结构)
    • LaTeX OCR(公式识别转换)
    • 文档布局分析和阅读顺序识别
  • 技术特点:被marker工具用作底层OCR引擎
  • 性能优势:
    • 价格仅为主流云服务的1/4
    • 99.99%高可用性
    • 250页PDF转换约15秒
  • 安装要求:需要Python 3.10+和PyTorch环境

介绍Mineru工具

GitHub地址:https://github.com/opendatalab/MinerU

  • 项目背景:国内厂商开发,获得29.6k星标
  • 语言支持:提供中英文双语文档说明
  • 功能特点:
    • 一站式PDF转Markdown/JSON工具
    • 基于书生·浦语大模型开发
    • 支持84种语言OCR
  • 技术亮点:
    • 自动删除页眉页脚等干扰元素
    • 保留文档原始结构(标题/段落/列表)
    • 支持表格转HTML、公式转LaTeX
  • 使用方式:
    • 提供桌面端图形界面
    • 支持华为昇腾NPU加速(端到端加速300%)
    • 自动语言识别功能

介绍LLamaParse工具

  • 核心差异:
    • 非开源工具,采用API调用模式
    • 需要申请API key(免费额度有限)
  • 输出质量:
    • 将复杂表格转换为规范的Markdown格式
    • 保留原始文档的层级结构
    • 适合LLM应用的优化格式
  • 性能对比:
    • 处理速度慢于本地工具(需云端排队)
    • marker作者声称其性能优于LlamaParse
  • 使用限制:
    • 文档需上传至LlamaCloud处理
    • 存在隐私和数据安全考量
    • 不适合处理敏感文档

示例演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 需要LLAMA_CLOUD_API_KEY
from dotenv import load_dotenv
load_dotenv()

# LlamaParse PDF reader for PDF Parsing
from llama_parse import LlamaParse
documents = LlamaParse(result_type="markdown").load_data(
"90-文档-Data/黑悟空/黑神话悟空.pdf"
)
print(documents)

from llama_index.core.node_parser import MarkdownElementNodeParser
node_parser = MarkdownElementNodeParser()
nodes = node_parser.get_nodes_from_documents(documents)

print(nodes)
  • 实现步骤:
    • 安装llama-parse包
    • 配置环境变量存储API key
    • 指定输出格式为markdown
    • 调用load_data()方法处理PDF
  • 技术要点:
    • 使用MarkdownElementNodeParser解析节点
    • 处理结果包含文档结构和内容元素
  • 注意事项:
    • 需耐心等待API返回结果(比本地工具慢)
    • 免费额度有限,适合小规模试用

LLamaParse工具解析PDF详解:

  • 工具特点:LlamaParser相比传统OCR工具(如pytesseract+pdf2image)能力更强,但并非完全可靠,例如在解析诗歌格式时可能尝试转换为表格型markdown格式。
  • 适用场景:更适合解析包含表格结构的数据(如GDP统计、富豪排行榜等),对于纯文本内容直接使用文字格式读取更准确。
  • 技术实现:需要LLAMA_CLOUD_API_KEY,通过LlamaParse(result_type=”markdown”).load_data()加载数据,支持MarkdownElementNodeParser进行节点解析。
  • PyPDFLoader特点:
    • 基础PDF加载器
    • 仅提供基本文本提取功能
    • 处理速度较快
  • UnstructuredPDFLoader特点:
    • 支持非结构化文档解析
    • 能保留更多原始格式信息
    • 适合复杂文档处理

普通PDF解析器与LlamaParser对比

image-20250807091707037

  • 格式保留能力:LlamaParser能保持原有表格结构和markdown格式,而普通解析器仅能提取纯文本。
  • 财务数据示例:成功解析了CAP与财务绩效的关联表格,包括Fiscal 2021-2024年的CEO薪酬、NED平均CAP等数据。
  • 标记语言支持:自动将表格转换为markdown格式,保留$符号等财务数据特殊格式。

image-20250807091739332

  • 普通解析器局限:仅能提取”Three Months Ended March 31,2021 2022”等连续文本,丢失表格结构。
  • LlamaParser优势:正确解析出”Financial and Operational Highlights”表格,包括百分比变化等复杂格式。
  • 应用价值:特别适合处理包含合并单元格、多级表头等复杂结构的财务报表。

Unstructured 工具解析版式和元素

官方网址:https://docs.unstructured.io/open-source/introduction/overview

用Unstructured工具保留版式信息和结构

  • 核心功能:Unstructured工具能够读取PDF并解析文档的版式信息和内部元素结构,是数据导入中的重点工具
  • API调用方式:
    • 通过partition_via_api=True参数可使用API处理文档
    • 优点:使用最新功能,适合处理数百页PDF文档
    • 缺点:需要申请API密钥,有免费额度限制
  • 本地处理方式:
    • 注销API相关参数后在本机运行
    • 优点:数据不离开本地
    • 缺点:速度较慢,依赖本地计算资源

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langchain_unstructured import UnstructuredLoader

file_path = ("../data/山西文旅/云冈石窟-en.pdf")
loader = UnstructuredLoader(
file_path=file_path, # PDF文件路径
strategy="hi_res", # 使用高分辨率策略进行文档处理
# partition_via_api=True, # 通过API进行文档分块,调用接口https://api.unstructuredapp.io相关接口,调用该接口不是完全免费的,可具体查看相关文档
# coordinates=True, # 提取文本坐标信息
)
docs = []

# lazy_load() 是一种延迟加载方法
# 它不会一次性将所有文档加载到内存中,而是在需要时才逐个加载文档
# 这对于处理大型PDF文件时可以节省内存使用
for doc in loader.lazy_load():
docs.append(doc)

print(docs)
  • 基本参数:
    • strategy=”hi_res”:使用高分辨率策略处理文档
    • coordinates=True:提取文本坐标信息
    • lazy_load():延迟加载方法,节省内存
  • 文件路径处理:注意路径格式,如”../data/山西文旅/云冈石窟-en.pdf”

执行完成,输出结果

1
[Document(metadata={'source': '../data/山西文旅/云冈石窟-en.pdf', 'detection_class_prob': 0.8435619473457336, 'coordinates': {'points': ((np.float64(64.78205871582031), np.float64(43.67091751098633)), (np.float64(64.78205871582031), np.float64(67.64956665039062)), (np.float64(236.6887969970703), np.float64(67.64956665039062)), (np.float64(236.6887969970703), np.float64(43.67091751098633))), 'system': 'PixelSpace', 'layout_width': 1700, 'layout_height': 2200}, 'last_modified': '2025-07-16T19:30:42', 'filetype': 'application/pdf', 'languages': ['eng'], 'page_number': 1, 'file_directory': '../data/山西文旅', 'filename': '云冈石窟-en.pdf', 'category': 'Header', 'element_id': '6659876cf076c9a9eb76a28d27ac56f1'}, page_content='9/27/24, 5:16 PM'), Document(metadata={'source': '../data/山西文旅/云冈石窟-en.pdf', 'detection_class_prob': 0.8640056848526001, 'coordinates': {'points': ((np.float64(827.9075317382812), np.float64(42.7579460144043)), (np.float64(827.9075317382812), np.float64(67.62699127197266)), (np.float64(1129.8564453125), np.float64(67.62699127197266)), (np.float64(1129.8564453125), np.float64(42.7579460144043))), 'system': 'PixelSpace', 'layout_width': 1700, 'layout_height': 2200}, 'last_modified': '2025-07-16T19:30:42', 'filetype': 'application/pdf', 'languages': ['eng'], 'page_number': 1, 'file_directory': '../data/山西文旅', 'filename': '云冈石窟-en.pdf', 'category': 'Header', 'element_id': '8bf3693cc743f02cfd5cd5775390f2e2'}, page_content='Yungang Grottoes - Wikipedia'), …… ]

输出了一堆内容,根本看不清输出了哪些内容,因此需求提取文档结构,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
from langchain_unstructured import UnstructuredLoader

file_path = ("../data/山西文旅/云冈石窟-en.pdf")
loader = UnstructuredLoader(
file_path=file_path,
strategy="hi_res",
# partition_via_api=True,
# coordinates=True,
)
docs = []
for doc in loader.lazy_load():
docs.append(doc)

def extract_basic_structure(docs):
"""基础结构化提取:按文档类型组织内容"""
# 定义类别映射
category_map = {
'Title': 'title',
'NarrativeText': 'text',
'Image': 'image',
'Table': 'table',
'Footer': 'footer',
'Header': 'header'
}

# 初始化结构字典
structure = {cat: [] for cat in category_map.values()}
structure['metadata'] = [] # 额外添加metadata类别

# 遍历文档并分类
for doc in docs:
category = doc.metadata.get('category', 'Unknown')
content = {
'content': doc.page_content,
'page': doc.metadata.get('page_number'),
'coordinates': doc.metadata.get('coordinates')
}

target_category = category_map.get(category)
if target_category:
structure[target_category].append(content)

return structure

# 使用示例
structure = extract_basic_structure(docs)

# 输出第2页的标题
print("第2页标题:")
for title in [t for t in structure['title'] if t['page'] == 2]:
print(f"- {title['content']}")


def analyze_layout(docs):
"""分析文档的版面布局结构"""
layout_analysis = {}

for doc in docs:
page = doc.metadata.get('page_number')
coords = doc.metadata.get('coordinates', {})

# 初始化页面信息
if page not in layout_analysis:
layout_analysis[page] = {
'elements': [],
'dimensions': {
'width': coords.get('layout_width', 0),
'height': coords.get('layout_height', 0)
}
}

# 获取元素位置信息
points = coords.get('points', [])
if points:
# 只需要左上和右下坐标点
(x1, y1), (_, _), (x2, y2), _ = points

# 构建元素信息
element = {
'type': doc.metadata.get('category'),
'content': (doc.page_content[:50] + '...') if len(doc.page_content) > 50 else doc.page_content,
'position': {
'x1': x1, 'y1': y1,
'x2': x2, 'y2': y2,
'width': x2 - x1,
'height': y2 - y1
}
}
layout_analysis[page]['elements'].append(element)

return layout_analysis

# 使用示例
layout = analyze_layout(docs)

# 分析第一页的布局
print("第1页布局分析:")
if 1 in layout:
page = layout[1]
print(f"页面尺寸: {page['dimensions']['width']} x {page['dimensions']['height']}")
print("\n元素分布:")

# 按垂直位置排序显示元素
for elem in sorted(page['elements'], key=lambda x: x['position']['y1']):
print(f"\n类型: {elem['type']}")
print(f"位置: ({elem['position']['x1']:.0f}, {elem['position']['y1']:.0f})")
print(f"尺寸: {elem['position']['width']:.0f} x {elem['position']['height']:.0f}")
print(f"内容: {elem['content']}")

# 寻找父子关系
cave6_docs = []
parent_id = -1
for doc in docs:
if doc.metadata["category"] == "Title" and "Cave 6" in doc.page_content:
parent_id = doc.metadata["element_id"]
if doc.metadata.get("parent_id") == parent_id:
cave6_docs.append(doc)

for doc in cave6_docs:
print(doc.page_content)

external_docs = [] # 创建列表来存储外部链接的子文档
parent_id = -1 # 初始化父ID为-1
for doc in docs:
# 检查文档是否为标题类型且内容包含"External links"
if doc.metadata["category"] == "Title" and "External links" in doc.page_content:
parent_id = doc.metadata["element_id"]
external_docs.append(doc)
# 检查文档的parent_id是否匹配我们找到的标题ID
if doc.metadata.get("parent_id") == parent_id:
external_docs.append(doc) # 将属于这个标题的子文档都添加到结果列表中
for doc in external_docs:
print(doc.page_content)


# def find_tables_and_titles(docs):
# results = []
# for doc in docs:
# # 检查文档是否为表格类型
# if doc.metadata.get("category") == "Table":
# table = doc
# parent_id = doc.metadata.get("parent_id")
# # 查找表格对应的标题文档(parent_id匹配element_id)
# title = next((doc for doc in docs if doc.metadata.get("element_id") == parent_id), None)
# if title:
# results.append({"table": table.page_content, "title": title.page_content})
# return results

# results = find_tables_and_titles(cave6_docs)
# if results:
# for result in results:
# print("找到的表格和标题:")
# print(f"标题: {result['title']}")
# print(f"表格: {result['table']}")
# else:
# print("未找到任何表格和标题")

执行结果,输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
第2页标题:
- Deterioration and Conservation
第1页布局分析:
页面尺寸: 1700 x 2200

元素分布:

类型: Header
位置: (828, 43)
尺寸: 302 x 25
内容: Yungang Grottoes - Wikipedia

类型: Header
位置: (65, 44)
尺寸: 172 x 24
内容: 9/27/24, 5:16 PM

类型: Image
位置: (106, 103)
尺寸: 426 x 87
内容: “e-) WIKIPEDIA “ «47 The Free Encyclopedia

类型: Title
位置: (104, 197)
...
  • 元素分类:
    • 可识别Title、NarrativeText、Image、Table、Footer、Header等类型
    • 示例:识别出”WIKIPEDIA”为Header,”The Free Encyclopedia”为Title
  • 坐标信息:
    • 获取元素在页面中的精确位置和尺寸
    • 示例:某Header位于(65,44),尺寸172x24
  • 结构化处理:
    • 通过category_map映射不同类型元素
    • 可统计各类型元素数量,如Text:2个,Title:53个,Header:8个

使用partition函数解析PDF

  • 原生调用优势:

    • 绕过LangChain封装,直接使用Unstructured核心功能
    • 可查看更底层的处理细节
  • 基本用法:

    • unstructured文档
      • partition函数支持的文件类型
      • 支持格式:
        • 文档类:PDF、DOCX、PPT、HTML、EPUB等
        • 数据类:CSV、TSV、Excel
        • 图像类:PNG、JPG、TIFF等
        • 纯文本:TXT、Markdown等
      • 自动路由:根据文件类型自动调用对应的partition函数
      • partition_pdf函数
      • 处理策略:
        • auto:自动选择最佳策略(默认)
        • hi_res:高精度模式,使用布局分析模型
        • fast:快速模式,适用于纯文本PDF
        • ocr_only:强制OCR模式,适用于扫描件
      • 多语言支持:通过languages参数指定OCR语言包
    • cleaning清洁功能
      • 功能说明:清理文档中的冗余信息和不必要元素
      • 应用场景:去除页眉页脚、水印等干扰内容
    • staging站台功能
      • 功能说明:预处理文档为标准化格式
      • 典型操作:统一编码、规范化段落格式等
    • chunking分块功能
      • 智能分块:
      • 基于语义单元而非简单文本分割
      • 保持元素完整性,避免拆分语义单元
      • 分块策略:
      • basic:基础分块
      • by_title:按标题分块
    • embedding嵌入功能
      • 功能说明:将解析后的内容转换为向量表示
      • 应用价值:为后续的检索和问答提供基础

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    from unstructured.partition.auto import partition

    filename = "../data/黑悟空/黑神话悟空.pdf"
    elements = partition(filename=filename,
    content_type="application/pdf"
    )
    print("\n\n".join([str(el) for el in elements][:10]))

    执行结果,输出内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    08.20 直面天命

    序章

    《黑神话:悟空》是一款基于《西游记》改编的中国神话动作角色扮演游戏,玩家化身 “天命之人”,在险象环生的西游冒险中追寻传说背后的秘密。

    改编自公元1592年中国神魔小说《西游记》

    启程

    无惧

    历练

    逢缘

    启程B

    L
    ……

    解析内容,代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    from unstructured.partition.auto import partition

    # 设置PDF文件路径
    filename = "../data/黑悟空/黑神话悟空.pdf"

    # 使用partition函数解析PDF文件
    # content_type指定文件类型为PDF
    elements = partition(filename=filename,
    content_type="application/pdf"
    )

    # 展示解析出的elements的类型和内容
    print("PDF解析后的Elements类型:")
    for i, element in enumerate(elements[:5]):
    print(f"\nElement {i+1}:")
    print(f"类型: {type(element).__name__}")
    print(f"内容: {str(element)}")
    print("-" * 50)

    # 统计不同类型elements的数量
    element_types = {}
    for element in elements:
    element_type = type(element).__name__
    element_types[element_type] = element_types.get(element_type, 0) + 1

    print("\nElements类型统计:")
    for element_type, count in element_types.items():
    print(f"{element_type}: {count}个")

    执行结果,输出内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    PDF解析后的Elements类型:

    Element 1:
    类型: Text
    内容: 08.20 直面天命
    --------------------------------------------------

    Element 2:
    类型: Title
    内容: 序章
    --------------------------------------------------

    Element 3:
    类型: Title
    内容: 《黑神话:悟空》是一款基于《西游记》改编的中国神话动作角色扮演游戏,玩家化身 “天命之人”,在险象环生的西游冒险中追寻传说背后的秘密。
    --------------------------------------------------

    Element 4:
    类型: Title
    内容: 改编自公元1592年中国神魔小说《西游记》
    --------------------------------------------------

    Element 5:
    类型: Header
    ...
    Elements类型统计:
    Text: 2个
    Title: 53个
    Header: 8个
    ……

渲染PDF页面版式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import fitz  # PyMuPDF库,用于处理PDF文件
import matplotlib.patches as patches # 用于在图像上绘制多边形
import matplotlib.pyplot as plt # Matplotlib库,用于绘图
from PIL import Image # 用于图像处理

def render_pdf_page(file_path, doc_list, page_number):
"""
渲染指定PDF页面并绘制段落分类框。

参数:
- file_path: str,PDF文件路径。
- doc_list: list,包含段落信息的文档列表,每个元素是一个字典,包含段落元数据。
- page_number: int,要渲染的页面号(从1开始计数)。
"""
# 打开PDF文件并加载指定页面
pdf_page = fitz.open(file_path).load_page(page_number - 1)
segments = [doc.metadata for doc in doc_list if doc.metadata.get("page_number") == page_number]

# 将PDF页面渲染为位图图像
pix = pdf_page.get_pixmap()
pil_image = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)

# 创建绘图环境
fig, ax = plt.subplots(figsize=(10, 10))
ax.imshow(pil_image)

# 定义类别颜色映射
category_to_color = {"Title": "orchid", "Image": "forestgreen", "Table": "tomato"}
categories = set()

# 绘制段落标注框
for segment in segments:
points = segment["coordinates"]["points"]
layout_width = segment["coordinates"]["layout_width"]
layout_height = segment["coordinates"]["layout_height"]
scaled_points = [
(x * pix.width / layout_width, y * pix.height / layout_height) for x, y in points
]
box_color = category_to_color.get(segment["category"], "deepskyblue")
categories.add(segment["category"])
rect = patches.Polygon(scaled_points, linewidth=1, edgecolor=box_color, facecolor="none")
ax.add_patch(rect)

# 添加图例
legend_handles = [patches.Patch(color="deepskyblue", label="Text")]
for category, color in category_to_color.items():
if category in categories:
legend_handles.append(patches.Patch(color=color, label=category))
ax.axis("off")
ax.legend(handles=legend_handles, loc="upper right")
plt.tight_layout()