RAG从入门到精通(四)——结构化数据读取与导入

1. JSON Loader

1.1 原始的TextLoader加载

TextLoader 是 LangChain 中最基础的文本加载器,功能是按字符读取文件的原始内容,不解析文件格式(无论文件后缀是 .txt、.json 还是 .md)。对于 JSON 文件,它会直接读取 JSON 的原始字符串(包括 {}、""、逗号等语法符号),不会将其转换为 Python 字典或列表。

python 复制代码
from langchain_community.document_loaders import TextLoader
print("=== TextLoader 加载结果 ===")
text_loader = TextLoader("../../90-文档-Data/灭神纪/人物角色.json",encoding="utf-8")
text_documents = text_loader.load()
print(text_documents)

1.2 JsonLoader

  • 结构化提取:从 JSON 中精准筛选出需要的字段(姓名、背景),排除无关信息(如其他未提及的角色属性)。
  • 格式化文本:将 JSON 字段拼接为自然语言文本(如 "姓名:XXX,背景:XXX"),无需大模型额外解析 JSON 格式,降低理解成本。
  • 标准化输出:转换为 Document 对象后,可直接接入 LangChain 的分块、向量存储、检索等流程,无缝融入 RAG 系统。

从 JSON 文件中精准提取并格式化了所需的角色信息,最终转换为 LangChain 标准的 Document 对象,每个对象包含 page_content(文本内容)和 metadata(元数据)两部分。

python 复制代码
from langchain_community.document_loaders import JSONLoader
print("=== JSONLoader 加载结果 ===")
print("1. 主角信息:")
main_loader = JSONLoader(
    file_path="../../90-文档-Data/灭神纪/人物角色.json",
    jq_schema='.mainCharacter | "姓名:" + .name + ",背景:" + .backstory',
    text_content=True
)
main_char = main_loader.load()
print(main_char)
print("\n2. 支持角色信息:")
support_loader = JSONLoader(
    file_path=r"../../90-文档-Data/灭神纪/人物角色.json",
    jq_schema='.supportCharacters[] | "姓名:" + .name + ",背景:" + .background',
    text_content=True,
)
support_chars = support_loader.load()
print(support_chars)

2. langchain的各种网页加载器

2.1 WebBaseLoader

使用 LangChain 的 WebBaseLoader 加载并解析网页内容,核心功能是从指定网页(维基百科《黑神话:悟空》词条)中提取信息,并通过配置只保留网页的主体内容,过滤掉广告、导航栏等无关元素。

python 复制代码
# 使用WebBaseLoader加载网页
import bs4
from langchain_community.document_loaders import WebBaseLoader
page_url = "https://zh.wikipedia.org/wiki/黑神话:悟空"
# loader = WebBaseLoader(web_paths=[page_url])
# docs = []
# docs = loader.load()
# assert len(docs) == 1
# doc = docs[0]
# print(f"{doc.metadata}\n")
# print(doc.page_content.strip()[:3000])


# 只解析文章的主体部分
loader = WebBaseLoader(
    web_paths=[page_url],
    bs_kwargs={
        "parse_only": bs4.SoupStrainer(id="bodyContent"),
    },
    # bs_get_text_kwargs={"separator": " | ", "strip": True},
)
docs = []
docs = loader.load()
assert len(docs) == 1
doc = docs[0]
print(f"{doc.metadata}\n")
print(doc.page_content)

2.2 UnstructuredLoader

python 复制代码
# 使用UnstructuredLoader加载网页
from langchain_unstructured import UnstructuredLoader
page_url = "https://zh.wikipedia.org/wiki/黑神话:悟空"
loader = UnstructuredLoader(web_url=page_url)
docs = loader.load()
for doc in docs[:5]:
print(f'{doc.metadata["category"]}: {doc.page_content}')

UnstructuredLoader -- 父子元素的链接

python 复制代码
from langchain_unstructured import UnstructuredLoader
from typing import List
from langchain_core.documents import Document
page_url = "https://zh.wikipedia.org/wiki/黑神话:悟空"
def _get_setup_docs_from_url(url: str) -> List[Document]:
    loader = UnstructuredLoader(web_url=url)
    setup_docs = []
    parent_id = None  # 初始化 parent_id
    current_parent = None  # 用于存储当前父元素
    for doc in loader.load():
        # 检查是否是 Title 或 Table
        if doc.metadata["category"] == "Title" or doc.metadata["category"] == "Table":
            parent_id = doc.metadata["element_id"]
            current_parent = doc  # 更新当前父元素
            setup_docs.append(doc)
        elif doc.metadata.get("parent_id") == parent_id:
            setup_docs.append((current_parent, doc))  # 将父元素和子元素一起存储
    return setup_docs       
docs = _get_setup_docs_from_url(page_url)
for item in docs:
    if isinstance(item, tuple):
        parent, child = item
        print(f'父元素 - {parent.metadata["category"]}: {parent.page_content}')
        print(f'子元素 - {child.metadata["category"]}: {child.page_content}')
    else:
        print(f'{item.metadata["category"]}: {item.page_content}')
    print("-" * 80)

3.Markdown文档的解析

3.1 UnstructuredMarkdownLoader

UnstructuredMarkdownLoader 是 LangChain 中专门用于加载和解析 Markdown(.md)文件的加载器,底层基于 unstructured 库实现,核心优势是能理解 Markdown 的结构化语法 ,避免将 Markdown 当作纯文本处理,让解析结果更贴合原始文档的逻辑结构。结合你提供的代码(两种模式:默认模式 vs mode="elements"),其优势可拆解为以下 4 点,每点都对应实际使用场景:

  1. 原生支持 Markdown 语法解析,保留结构而非纯文本

    普通加载器(如 TextLoader)会把 Markdown 文件当作纯文本,直接读取所有字符(包括 #、*、| 等语法符号),导致解析结果杂乱无章;而 UnstructuredMarkdownLoader 能识别 Markdown 的原生语法,自动提取结构化信息:

    • 识别标题层级(# 一级标题、## 二级标题);
    • 识别列表(有序列表 1.、无序列表 -/*);
    • 识别表格(| 表头 | 内容 |);
    • 识别代码块(python ... )、引用(>)等。
  2. mode="elements" 模式拆分独立元素,实现精细化处理

    这是最核心的优势!通过 mode="elements" 可将 Markdown 按语法拆分为多个独立的 Document 对象(每个对象对应一个"元素"),每个元素都带类型标记,方便后续精准处理:

    • 元素类型包括:Title(标题)、List(列表)、Table(表格)、CodeBlock(代码块)、NarrativeText(普通段落)等;
    • 每个 Document 的 metadata["category"] 会标注元素类型,page_content 是元素的具体内容。
  3. 自动处理编码和格式兼容,容错性强

    • 编码兼容 :默认支持 UTF-8 编码(中文无乱码),无需手动指定 encoding 参数(对比 TextLoader 常需显式设置 encoding="utf-8");
    • 格式容错:即使 Markdown 语法不规范(比如表格缺少分隔线、标题层级混乱),也能正常解析,不会因语法错误导致加载失败;
    • 跨平台兼容 :无需担心 Windows/macOS/Linux 下的换行符(\n/\r\n)差异,自动适配处理。
  4. 无缝集成 LangChain 生态,支持进阶流程

    解析后直接返回 LangChain 标准的 Document 对象,可无缝对接后续所有 LangChain 流程,无需额外格式转换:

    • 分块:结合 RecursiveCharacterTextSplitter 时,因元素已拆分,分块能更贴合逻辑(比如按标题-段落分组分块);
    • 向量存储:每个元素的 metadata(如 category="Table"、source="文件路径")可作为检索时的筛选条件(比如只检索表格类型的文档);
    • 元数据保留:自动保留文件路径(source)、元素类型(category)等元数据,便于追溯信息来源。

对比普通加载器(TextLoader)的核心差异

特性 UnstructuredMarkdownLoader TextLoader(纯文本加载)
Markdown 语法识别 支持(识别标题、列表、表格等) 不支持(当作纯文本,保留所有语法符号)
解析结果结构 结构化(元素拆分/保留逻辑) 非结构化(纯文本字符串)
元数据丰富度 高(含元素类型、文件路径等) 低(仅含文件路径)
后续处理灵活性 高(可按元素类型筛选、精准分块) 低(需手动清理语法、拆分结构)
  1. 若只需快速获取 Markdown 的"可读文本"(用于直接作为上下文):用默认模式,自动去除语法符号,得到干净的文本;
  2. 若需精细化处理(比如提取所有表格、按标题分组存储):用 mode="elements" 模式,拆分元素后针对性处理;
  3. 若后续要构建 RAG 系统:其结构化解析和元数据保留能大幅提升检索精度,避免纯文本加载的冗余信息干扰。

简单说,UnstructuredMarkdownLoader 是"懂 Markdown 语法"的加载器,让 Markdown 文件的解析从"纯文本读取"升级为"结构化提取",是处理 Markdown 文档的最优选择之一。

python 复制代码
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from io import StringIO

class EncodedUnstructuredMarkdownLoader(UnstructuredMarkdownLoader):
    """支持编码的 UnstructuredMarkdownLoader"""
    def __init__(self, file_path: str, encoding: str = "utf-8", **kwargs):
        super().__init__(file_path=file_path, **kwargs)
        self.encoding = encoding

    def _get_elements(self):
        # 手动按指定编码读取文件
        with open(self.file_path, "r", encoding=self.encoding) as f:
            content = f.read()
        # 转为文件对象传给父类解析
        self.file_obj = StringIO(content)
        return super()._get_elements()

# -------------------------- 使用自定义加载器 --------------------------
markdown_path = "../../90-文档-Data/黑悟空/黑悟空版本介绍.md"

# 默认模式
loader = EncodedUnstructuredMarkdownLoader(markdown_path, encoding="utf-8")
data = loader.load()
print("=== 默认模式(前250字符)===")
print(data[0].page_content[:250])

# mode="elements" 模式
loader_elements = EncodedUnstructuredMarkdownLoader(markdown_path, mode="elements", encoding="utf-8")
data_elements = loader_elements.load()
print(f"\nNumber of documents: {len(data_elements)}")

4. 解析图文数据

4.1 读取图片

python 复制代码
from langchain_community.document_loaders import UnstructuredImageLoader
image_path = "../../90-文档-Data/黑悟空/黑悟空英文.jpg"
loader = UnstructuredImageLoader(image_path)

data = loader.load()
print(data)

4.2 读取PPT

python 复制代码
"""
Q: 在使用 unstructured 解析 PPT 等 Office 文件时,如果出现 FileNotFoundError: soffice command was not found 错误怎么办?
A: 这是因为系统中缺少 libreoffice。unstructured 需要调用 libreoffice 的 soffice 命令来处理 Office 文档。
解决方案是在系统中安装 libreoffice。
在 Debian/Ubuntu 系统中,可以使用以下命令安装:
sudo apt-get update && sudo apt-get install -y libreoffice

- Install instructions: https://www.libreoffice.org/get-help/install-howto/
- Mac: https://formulae.brew.sh/cask/libreoffice
- Debian: https://wiki.debian.org/LibreOffice
"""
from unstructured.partition.ppt import partition_ppt
# 解析 PPT 文件
ppt_elements = partition_ppt(filename="90-文档-Data/黑悟空/黑神话悟空.pptx")
print("PPT 内容:")
# for element in ppt_elements:
#     print(element.text)
    
from langchain_core.documents import Document
# 转换为 Documents 数据结构
documents = [
Document(page_content=element.text, 
  	     metadata={"source": "data/黑神话悟空PPT.pptx"})
    for element in ppt_elements
]

# 输出转换后的 Documents
print(documents[0:3])

4.3 基于大模型读取图文

python 复制代码
from pdf2image import convert_from_path
import base64
import os
from openai import OpenAI

# 初始化 OpenAI 客户端
client = OpenAI()
output_dir = "temp_images"

# 1. PDF 转图片
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

images = convert_from_path("90-文档-Data/黑悟空/黑神话悟空.pdf")
image_paths = []
for i, image in enumerate(images):
    image_path = os.path.join(output_dir, f'page_{i+1}.jpg')
    image.save(image_path, 'JPEG')
    image_paths.append(image_path)
print(f"成功转换 {len(image_paths)} 页")


# 2. GPT-4o 分析图片
print("\n开始分析图片...")
results = []
for image_path in image_paths:
    with open(image_path, "rb") as image_file:
        base64_image = base64.b64encode(image_file.read()).decode('utf-8')
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": "请详细描述这张PPT幻灯片的内容,包括标题、正文和图片内容。"},
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": f"data:image/jpeg;base64,{base64_image}"
                        }
                    }
                ]
            }
        ],
        max_tokens=300
    )
results.append(response.choices[0].message.content)


# 3. 转换为 LangChain 的 Document 数据结构
from langchain_core.documents import Document

documents = [
    Document(
        page_content=result,
        metadata={"source": "data/黑悟空/黑神话悟空.pdf", "page_number": i + 1}
    )
    for i, result in enumerate(results)
]

# 输出所有生成的 Document 对象
print("\n分析结果:")
for doc in documents:
    print(f"内容: {doc.page_content}\n元数据: {doc.metadata}\n")
    print("-" * 80)

# 清理临时文件
for image_path in image_paths:
    os.remove(image_path)
os.rmdir(output_dir)

5. PDF文件解析

除此之外还有:

  • LlamaParse:深度 PDF 结构解析工具,针对法律合同、学术论文等场景,解析精度高,但属于商业 API。
  • Mineru:多模态集成解析工具,结合 LayoutLMv3 + YOLOv8 技术,适合学术文献、财务报表等多元素融合的 PDF 处理。
  • FireCrawlLoader:专注网页内容抓取,可将在线 PDF 文档实时转换为可处理的结构化数据。
  • Google Cloud Vision API:谷歌云提供的 OCR 服务,支持 PDF 扫描件的高精度文本识别,适合企业级大规模文档处理。
  • Azure AI Document Intelligence:微软云的文档智能服务,能解析 PDF 中的文本、表格、表单等复杂结构,还支持自定义模型训练。

三大类解析方法:

• 基于规则的解析

• 基于深度学习的解析

• 基于多模态大模型的解析

执行以下操作:

• 通过启发式方法或机器学习推理将文本框聚合为行、段落或其他结构;

• 对图像运行OCR,以检测其中的文本;

• 将文本分类为段落、列表、表格或其他结构;

• 将文本组织成表格的行和列,或以键值对的形式呈现

5.1 基于PyPDFLoader读取PDF

python 复制代码
from langchain_community.document_loaders import PyPDFLoader
file_path = "90-文档-Data/黑悟空/黑神话悟空.pdf"
loader = PyPDFLoader(file_path)
pages = loader.load()
print(f"加载了 {len(pages)} 页PDF文档")
for page in pages:
    print(page.page_content)

5.2 基于Unstructured 提取文档结构

python 复制代码
file_path = ("90-文档-Data/山西文旅/云冈石窟-en.pdf")
from langchain_unstructured import UnstructuredLoader
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("未找到任何表格和标题")

5.3 扫描类PDF

python 复制代码
# 扫描图片型 PDF,建议用 pytesseract + pdf2image  
# sudo apt-get install tesseract-ocr
# sudo apt-get install tesseract-ocr-chi-sim

import pdf2image
import pytesseract
import os

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

# 将 PDF 转换为图片并保存
images = pdf2image.convert_from_path('90-文档-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") 

5.4 LlamaParser

python 复制代码
# 需要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)

6. 导入CSV表格数据

6.1 DirectoryLoader加载表格数据

python 复制代码
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.document_loaders import CSVLoader

loader = DirectoryLoader(
    path="data/黑神话",  # Specify the directory containing your CSV files
    glob="**/*.csv",                # Use a glob pattern to match CSV files
    loader_cls=CSVLoader            # Specify CSVLoader as the loader class
)

docs = loader.load()
print(f"文档数:{len(docs)}")  # 输出文档总数
print(docs[0])

6.2 camelot提取表格数据

camelot 是 PDF 表格提取领域的「专业工具」,「规则驱动 + 传统计算机视觉」的典型代表,相比 pypdf、pdfplumber 等通用 PDF 工具,它对表格的识别逻辑更精细:

  • 支持 带边框 / 无边框表格:即使表格没有明确边框(仅靠对齐分隔),也能通过文本位置、间距识别列边界;
  • 处理 合并单元格、跨列 / 跨行表格:能正确解析 PDF 中常见的合并单元格(如表头合并),不会拆分或错乱数据;
  • 识别 多列嵌套表格:复杂 PDF 中 "表格内套表格" 的场景(如财务报表、统计报告),camelot 能精准拆分层级;
  • 保留 表格结构完整性:提取后的数据行列对应关系与 PDF 完全一致,不会出现 "列错位""行遗漏"(这是通用工具最容易出问题的点)。
  • 对复杂表格的提取精度极高(尤其是结构化表格、多页表格、带边框 / 合并单元格的表格),且能直接转为 pandas.DataFrame 进行数据分析 / 保存。以下是其核心优势、适用场景及对比其他工具的亮点:
python 复制代码
import camelot
import pandas as pd
# from ctypes.util import find_library
# find_library("gs")

pdf_path = "90-文档-Data/复杂PDF/billionaires_page-1-5.pdf"
import time

start_time = time.time()
tables = camelot.read_pdf(pdf_path, pages="all")
end_time = time.time()
print(f"PDF表格解析耗时: {end_time - start_time:.2f}秒")

# 转换所有表格为 DataFrame
if tables:
    # 遍历所有表格
    for i, table in enumerate(tables, 1):
        # 将表格转换为 DataFrame
        df = table.df
        
        # 打印当前表格数据
        print(f"\n表格 {i} 数据:")
        print(df)
        
        # 显示基本信息
        print(f"\n表格 {i} 基本信息:")
        print(df.info())
        
        # 保存到CSV文件
        csv_filename = f"billionaires_table_{i}.csv"
        df.to_csv(csv_filename, index=False)
        print(f"\n表格 {i} 数据已保存到 {csv_filename}")

6.3 解析表格数据

基于 unstructured表格提取+上下文 实现表格提取

python 复制代码
from unstructured.partition.pdf import partition_pdf
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding

# 全局设置
Settings.llm = OpenAI(model="gpt-3.5-turbo")
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

# 解析 PDF 结构,提取文本和表格
file_path = "90-文档-Data/复杂PDF/billionaires_page-1-5.pdf"  # 修改为你的文件路径

elements = partition_pdf(
    file_path,
    # strategy="hi_res",  # 使用高精度策略
)  # 解析PDF文档

# 创建一个元素ID到元素的映射
element_map = {element.id: element for element in elements if hasattr(element, 'id')}

# 创建一个元素索引到元素的映射
element_index_map = {i: element for i, element in enumerate(elements)}

for i, element in enumerate(elements):
    if element.category == "Table":
        print("\n表格数据:")
        print("表格元数据:", vars(element.metadata))  # 使用vars()显示所有元数据属性
        print("表格内容:")
        print(element.text)  # 打印表格文本内容
        
        # 获取并打印父节点信息
        parent_id = getattr(element.metadata, 'parent_id', None)
        if parent_id and parent_id in element_map:
            parent_element = element_map[parent_id]
            print("\n父节点信息:")
            print(f"类型: {parent_element.category}")
            print(f"内容: {parent_element.text}")
            if hasattr(parent_element, 'metadata'):
                print(f"父节点元数据: {vars(parent_element.metadata)}")
        else:
            print(f"未找到父节点 (ID: {parent_id})")
            
        # 打印表格前3个节点的内容
        print("\n表格前3个节点内容:")
        for j in range(max(0, i-3), i):
            prev_element = element_index_map.get(j)
            if prev_element:
                print(f"节点 {j} ({prev_element.category}):")
                print(prev_element.text)
                
        print("-" * 50)

text_elements = [el for el in elements if el.category == "Text"]
table_elements = [el for el in elements if el.category == "Table"]

基于 pdfplumber提取PDF表格并问答

python 复制代码
import pdfplumber
import pandas as pd
from llama_index.core import VectorStoreIndex
from llama_index.core import Document
from typing import List

pdf_path = "90-文档-Data/复杂PDF/billionaires_page-1-5.pdf"

# 打开 PDF 并解析表格
with pdfplumber.open(pdf_path) as pdf:
    tables = []
    for page in pdf.pages:
        table = page.extract_table()
        if table:
            tables.append(table)

# 转换所有表格为 DataFrame 并构建文档
documents: List[Document] = []
if tables:
    # 遍历所有表格
    for i, table in enumerate(tables, 1):
        # 将表格转换为 DataFrame
        df = pd.DataFrame(table)
        
        # 保存到CSV文件
        # csv_filename = f"billionaires_table_{i}.csv"
        # df.to_csv(csv_filename, index=False)
        # print(f"\n表格 {i} 数据已保存到 {csv_filename}")
        
        # 将DataFrame转换为文本
        text = df.to_string()
        
        # 创建Document对象
        doc = Document(text=text, metadata={"source": f"表格{i}"})
        documents.append(doc)

# 构建索引
index = VectorStoreIndex.from_documents(documents)

# 创建查询引擎
query_engine = index.as_query_engine()

# 示例问答
questions = [
    "2023年谁是最富有的人?",
    "最年轻的富豪是谁?"
]

print("\n===== 问答演示 =====")
for question in questions:
    response = query_engine.query(question)
    print(f"\n问题: {question}")
    print(f"回答: {response}")
工具 核心定位 提取精度(原生文本+带边框) 提取精度(原生文本+无边框) 提取精度(扫描件/扭曲/模糊) 复杂表格支持(合并/嵌套) 结构化输出 部署依赖 生态适配(LangChain/LlamaIndex) 适用文件类型 核心优势 核心局限
pypdf 基础文本提取(表格粗提) 低 极低 无 无 纯文本(需手动拆分行列) 无任何依赖 需手动转 Document 原生文本型 PDF 极致轻量、速度最快、部署零成本 仅支持极简表格,无结构还原能力
pdfplumber 轻量表格提取(平衡之选) 中高 中 无 良好(简单合并可行,复杂嵌套一般) 二维列表(直接转 DataFrame) 无额外依赖 需手动转 Document 原生文本型 PDF 轻量无依赖、容错性强、API 简洁、易上手 复杂嵌套表格精度有限,不支持扫描件
Camelot 专业表格提取(带边框最优) 高(最优) 中(需手动调参) 无 优秀(带边框场景最优,无边框需调参) DataFrame(原生支持) 依赖 Ghostscript 需手动转 Document 原生文本型 PDF 带边框复杂表格还原度第一、批量提取高效 不支持扫描件/扭曲表格,无边框复杂表格需调参
Unstructured 多元素结构化(表格+文本) 中高 中高 中(需启用 OCR) 一般(简单合并可行,复杂嵌套较弱) 表格元素对象(需手动转 DataFrame) 轻量(可选 OCR 依赖) 原生支持(自动转 Document) 原生文本型/扫描件 PDF、图片、Word 一站式多元素处理、支持 OCR、层级关联、生态友好 纯表格精度略逊,复杂嵌套表格处理能力有限
LayoutLM 深度学习(噪声表格克星) 高 高(最优) 高(最优) 优秀(全场景复杂表格适配) 结构化字典(需手动转 DataFrame) 依赖 GPU + OCR 工具 需手动适配 扫描件/扭曲/模糊 PDF、原生文本型 PDF 噪声容忍性强、复杂表格泛化能力强 部署复杂、需 GPU、推理耗时、上手门槛高
Azure AI Document Intelligence 企业级高精度(零代码) 高 高(最优) 高(最优) 优秀(全场景复杂表格适配) DataFrame/JSON(API 输出) 无本地依赖(云端服务) 支持 API 对接 所有类型(原生/扫描件/多语言) 零代码、高精度、大规模处理、多场景适配 收费、需联网、无本地部署
相关推荐
回眸&啤酒鸭4 天前
【回眸】Minicart 电商购物车核心功能落地指南
人工智能
一隅论数智4 天前
给AI一张“业务概念地图“:本体如何从哲学走向企业智能
大数据·人工智能·经验分享·笔记·学习·学习方法·政务
AI的探索之旅4 天前
97 个 OpenCV 实例(三十):双目立体,从标定到点云
人工智能·opencv·计算机视觉
AlbertZein4 天前
Step-5-Preview 上手实测:3D 游戏、金融分析、网页设计一次跑完
人工智能·aigc
LaughingZhu4 天前
Product Hunt 每日热榜 | 2026-09-19
人工智能·深度学习·神经网络·搜索引擎·百度
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
wukangjupingbb4 天前
智能网联汽车安全能力框架
人工智能
龙亘川4 天前
明月照湾区,智启新赛道:从顶流文旅IP盛会看智慧文旅升级路径
人工智能·智慧城市·开源软件·数据可视化
飞猫的边缘AI4 天前
边缘AI应用:家用AI摄像头怎么做数据训练?
人工智能·边缘计算·ai算法·边缘ai