找回密码
 立即注册
首页 业界区 业界 004:RAG 入门-LangChain读取PDF

004:RAG 入门-LangChain读取PDF

嫁蝇 10 小时前
LangChain 读取 PDF 文档

本文是 refine-rag 系列教程的第四篇,深入讲解如何使用多种方案处理 PDF 文档。
目录


  • 前言
  • 环境准备
  • 方案一:PyPDF(轻量快速)
  • 方案二:PyMuPDF(功能强大)
  • 方案三:Unstructured(智能解析)
  • 方案四:父子文档结构解析
  • 方案对比与选择
  • 常见问题
  • 下一步学习
前言

PDF 是最常见的文档格式,但也是最难处理的格式之一。不同于纯文本,PDF 包含:

  • 复杂的排版布局
  • 多列文本
  • 表格和图片
  • 嵌入字体
  • 扫描件(需要 OCR)
本文将介绍 4 种主流的 PDF 处理方案,从简单到复杂,从快速到智能,可根据自己的实际使用场景,选择合适的方案。
环境准备

1. 安装依赖包
  1. # 方案一:PyPDF(最轻量)
  2. pip install pypdf langchain-community
  3. # 方案二:PyMuPDF(功能强大)
  4. pip install pymupdf
  5. # 方案三:Unstructured(智能解析)
  6. pip install "unstructured[pdf]" langchain-unstructured
  7. # 中文 OCR 支持(可选)
  8. brew install tesseract-lang  # macOS
  9. # 或
  10. sudo apt-get install tesseract-ocr-chi-sim  # Ubuntu
  11. # 一键安装所有依赖
  12. pip install pypdf pymupdf "unstructured[pdf]" langchain-community langchain-unstructured
复制代码
2. 准备测试 PDF

准备一个测试 PDF 文件,可以是:

  • 技术文档
  • 产品手册
  • 学术论文
  • 扫描件
方案一:PyPDF(轻量快速)

最简单的 PDF 处理方案,适合纯文本提取。
文件名: 01-PyPDF读取.py
  1. from langchain_community.document_loaders import PyPDFLoader
  2. # 加载 PDF 文件
  3. loader = PyPDFLoader("../99-doc-data/黑悟空/黑神话悟空.pdf")
  4. data = loader.load()
  5. # 遍历每一页
  6. for i, page in enumerate(data):
  7.     print(f"=== 第 {i+1} 页 ===")
  8.     print(page.page_content)
  9.     print(f"元数据: {page.metadata}")
  10.     print("-" * 50)
复制代码
输出结果:
  1. [
  2.     Document(
  3.         page_content='黑神话:悟空\n\n游戏简介...',
  4.         metadata={'source': '黑神话悟空.pdf', 'page': 0}
  5.     ),
  6.     Document(
  7.         page_content='第一章 黑风山...',
  8.         metadata={'source': '黑神话悟空.pdf', 'page': 1}
  9.     )
  10. ]
复制代码
特点:
✅ 优势:

  • 安装简单,无外部依赖
  • 速度快,内存占用低
  • 按页分割,结构清晰
  • 适合纯文本 PDF
❌ 劣势:

  • 不支持复杂布局
  • 无法识别文档结构(标题、段落)
  • 不支持扫描件 OCR
  • 表格提取效果差
适用场景:

  • 简单的文本 PDF
  • 需要快速提取内容
  • 对文档结构要求不高
方案二:PyMuPDF(功能强大)

更强大的 PDF 处理库,提供丰富的元数据和控制能力。
文件名: 02-PyMuPDF.py
  1. import pymupdf
  2. # 打开 PDF 文件
  3. doc = pymupdf.open("../../99-doc-data/黑悟空/黑神话悟空.pdf")
  4. # 提取所有页面的文本
  5. text = [page.get_text() for page in doc]
  6. print(text)
  7. # 获取文档元数据
  8. print("=== PyMuPDF 基本信息提取 ===")
  9. print(f"文档页数: {len(doc)}")
  10. print(f"文档标题: {doc.metadata.get('title', 'N/A')}")
  11. print(f"文档作者: {doc.metadata.get('author', 'N/A')}")
  12. print(f"创建时间: {doc.metadata.get('creationDate', 'N/A')}")
  13. print(f"完整元数据: {doc.metadata}")
  14. # 遍历每一页,提取详细信息
  15. for page_num, page in enumerate(doc):
  16.     print(f"\n--- 第 {page_num + 1} 页 ---")
  17.     # 提取文本
  18.     text = page.get_text()
  19.     print(f"文本内容: {text[:200]}...")  # 显示前 200 个字符
  20.     # 提取图片
  21.     images = page.get_images()
  22.     print(f"图片数量: {len(images)}")
  23.     # 获取页面链接
  24.     links = page.get_links()
  25.     print(f"链接数量: {len(links)}")
  26.     # 获取页面尺寸
  27.     width, height = page.rect.width, page.rect.height
  28.     print(f"页面尺寸: {width:.2f} x {height:.2f}")
  29. doc.close()
复制代码
进阶功能:
  1. import pymupdf
  2. doc = pymupdf.open("document.pdf")
  3. page = doc[0]
  4. # 1. 提取图片并保存
  5. for img_index, img in enumerate(page.get_images()):
  6.     xref = img[0]
  7.     base_image = doc.extract_image(xref)
  8.     image_bytes = base_image["image"]
  9.    
  10.     # 保存图片
  11.     with open(f"image_{img_index}.png", "wb") as f:
  12.         f.write(image_bytes)
  13. # 2. 提取表格(需要额外处理)
  14. tables = page.find_tables()
  15. for table in tables:
  16.     df = table.to_pandas()
  17.     print(df)
  18. # 3. 搜索文本
  19. text_instances = page.search_for("关键词")
  20. for inst in text_instances:
  21.     print(f"找到位置: {inst}")
  22. # 4. 提取带格式的文本
  23. blocks = page.get_text("dict")["blocks"]
  24. for block in blocks:
  25.     if block["type"] == 0:  # 文本块
  26.         for line in block["lines"]:
  27.             for span in line["spans"]:
  28.                 print(f"文字: {span['text']}, 字体: {span['font']}, 大小: {span['size']}")
  29. doc.close()
复制代码
特点:
✅ 优势:

  • 速度快,性能优秀
  • 丰富的元数据(作者、创建时间等)
  • 可以提取图片、链接
  • 支持表格识别
  • 可以获取文本格式(字体、大小)
  • 内存占用少
❌ 劣势:

  • 不自动识别文档结构
  • 需要手动处理布局分析
  • 扫描件需要额外 OCR
适用场景:

  • 需要提取图片和元数据
  • 需要精细控制 PDF 处理
  • 性能要求高
  • 需要处理大量 PDF
方案三:Unstructured(智能解析)

最智能的 PDF 处理方案,自动识别文档结构。
文件名: 03-LangChain-Unstrucured-PDF.py
  1. from langchain_unstructured import UnstructuredLoader
  2. # 中文 PDF
  3. loader = UnstructuredLoader(
  4.     file_path="../99-doc-data/山西文旅/云冈石窟-ch.pdf",
  5.     strategy="hi_res",      # 高分辨率策略
  6.     languages=["chi_sim"]   # 简体中文 OCR
  7. )
  8. # 英文 PDF
  9. # loader = UnstructuredLoader(
  10. #     file_path="../99-doc-data/山西文旅/云冈石窟-en.pdf",
  11. #     strategy="hi_res"
  12. # )
  13. docs = []
  14. # lazy_load() 延迟加载,节省内存
  15. for doc in loader.lazy_load():
  16.     docs.append(doc)
  17. # 查看解析结果
  18. for i, doc in enumerate(docs[:5]):  # 只显示前 5 个
  19.     print(f"\n=== 元素 {i+1} ===")
  20.     print(f"类型: {doc.metadata.get('category')}")
  21.     print(f"内容: {doc.page_content[:100]}...")
  22.     print(f"页码: {doc.metadata.get('page_number')}")
  23.     print(f"元素ID: {doc.metadata.get('element_id')}")
  24.     print(f"父元素ID: {doc.metadata.get('parent_id')}")
复制代码
strategy 参数说明:
策略说明速度准确度适用场景fast快速模式⚡⚡⚡⭐⭐纯文本 PDFhi_res高分辨率⚡⭐⭐⭐⭐复杂布局、扫描件ocr_only仅 OCR⚡⚡⭐⭐⭐纯图片 PDF使用 partition 函数(更底层):
文件名: 04-Unstrctured-使用partition函数解析PDF-v1.py
  1. from unstructured.partition.auto import partition
  2. filename = "../99-doc-data/黑悟空/黑神话悟空.pdf"
  3. # 使用 partition 函数解析 PDF
  4. elements = partition(
  5.     filename=filename,
  6.     content_type="application/pdf"
  7. )
  8. # 展示解析出的元素类型和内容
  9. print("PDF 解析后的 Elements 类型:")
  10. for i, element in enumerate(elements[:5]):
  11.     print(f"\nElement {i+1}:")
  12.     print(f"类型: {type(element).__name__}")
  13.     print(f"内容: {str(element)[:100]}...")
  14.     print("-" * 50)
  15. # 统计不同类型元素的数量
  16. element_types = {}
  17. for element in elements:
  18.     element_type = type(element).__name__
  19.     element_types[element_type] = element_types.get(element_type, 0) + 1
  20. print("\nElements 类型统计:")
  21. for element_type, count in element_types.items():
  22.     print(f"{element_type}: {count} 个")
复制代码
输出示例:
  1. Elements 类型统计:
  2. Title: 12 个
  3. NarrativeText: 45 个
  4. ListItem: 8 个
  5. Table: 3 个
  6. Image: 2 个
复制代码
特点:
✅ 优势:

  • 自动识别文档结构(标题、段落、列表)
  • 支持扫描件 OCR
  • 可以识别表格
  • 保留父子关系
  • 适合复杂 PDF
❌ 劣势:

  • 速度较慢
  • 依赖外部工具(Tesseract)
  • 内存占用较大
适用场景:

  • 复杂布局的 PDF
  • 需要保留文档结构
  • 扫描件处理
  • 构建知识图谱
方案四:父子文档结构解析

保留文档的层级关系,实现更精准的检索。
4.1 使用 LangChain 封装

文件名: 05-父子文档解析-Unstructured-LangChain.py
  1. from langchain_unstructured import UnstructuredLoader
  2. file_path = '../99-doc-data/山西文旅/云冈石窟-en.pdf'
  3. # 加载 PDF
  4. loader = UnstructuredLoader(
  5.     file_path=file_path,
  6.     strategy="hi_res"
  7. )
  8. docs = []
  9. for doc in loader.lazy_load():
  10.     docs.append(doc)
  11. # 仅筛选第一页的文档
  12. page_number = 1
  13. page_docs = [doc for doc in docs if doc.metadata.get("page_number") == page_number]
  14. # 打印每个元素的详细信息
  15. for i, doc in enumerate(page_docs, 1):
  16.     print(f"Doc {i}:")
  17.     print(f"  内容: {doc.page_content}")
  18.     print(f"  分类: {doc.metadata.get('category')}")
  19.     print(f"  ID: {doc.metadata.get('element_id')}")
  20.     print(f"  Parent ID: {doc.metadata.get('parent_id')}")
  21.     print("=" * 50)
  22. # 构建父子关系
  23. title_dict = {}
  24. # 收集 Title,建立 parent_id -> Title 的映射
  25. for doc in docs:
  26.     if (doc.metadata.get("category") == "Title" and
  27.         doc.metadata.get("page_number") == page_number):
  28.         title_id = doc.metadata.get("element_id")
  29.         title_text = doc.page_content.strip()
  30.         if title_text not in [data["title"] for data in title_dict.values()]:
  31.             title_dict[title_id] = {"title": title_text, "content": []}
  32. # 关联 Title 和其对应的 Text
  33. for doc in docs:
  34.     if (doc.metadata.get("category") in ["NarrativeText", "Text"] and
  35.         doc.metadata.get("page_number") == page_number):
  36.         parent_id = doc.metadata.get("parent_id")
  37.         if parent_id in title_dict:
  38.             content = doc.page_content.strip()
  39.             if content:
  40.                 title_dict[parent_id]["content"].append(content)
  41. # 输出结构化结果
  42. for title_data in title_dict.values():
  43.     if title_data["content"]:
  44.         print("\n=== " + title_data["title"] + " ===")
  45.         for content in title_data["content"]:
  46.             print(content)
  47.         print()
复制代码
4.2 使用原生 Unstructured API

文件名: 06-父子文档-Unstructured-ParitionPDF.py
  1. from unstructured.documents.elements import Title, NarrativeText, Text
  2. from unstructured.partition.pdf import partition_pdf
  3. file_path = '../99-doc-data/山西文旅/云冈石窟-en.pdf'
  4. # 使用 unstructured 直接读取 PDF
  5. elements = partition_pdf(
  6.     filename=file_path,
  7.     strategy="hi_res"
  8. )
  9. # 查看第一个元素的完整信息
  10. if elements:
  11.     first_elem = elements[0]
  12.     print("=== 第一个元素的详细信息 ===")
  13.     print(f"类型: {type(first_elem)}")
  14.     print(f"文本: {first_elem.text}")
  15.     print(f"Metadata: {vars(first_elem.metadata)}")
  16.     print("=" * 50)
  17. # 仅筛选第一页的元素
  18. page_number = 1
  19. page_elements = [
  20.     elem for elem in elements
  21.     if getattr(elem.metadata, "page_number", None) == page_number
  22. ]
  23. # 打印每个元素的详细信息
  24. for i, elem in enumerate(page_elements, 1):
  25.     print(f"\nElement {i}:")
  26.     print(f"  内容: {elem.text}")
  27.     print(f"  分类: {type(elem).__name__}")
  28.     print(f"  ID: {getattr(elem, '_element_id', None)}")
  29.     print("=" * 50)
  30. # 构建父子关系
  31. title_dict = {}
  32. # 收集 Title(使用类型检查)
  33. for elem in elements:
  34.     if (isinstance(elem, Title) and
  35.         getattr(elem.metadata, "page_number", None) == page_number):
  36.         title_id = getattr(elem, '_element_id', None)
  37.         title_text = elem.text.strip()
  38.         if title_text not in [data["title"] for data in title_dict.values()]:
  39.             title_dict[title_id] = {"title": title_text, "content": []}
  40. # 关联 Title 和其对应的 Text
  41. for elem in elements:
  42.     if (isinstance(elem, (NarrativeText, Text)) and
  43.         getattr(elem.metadata, "page_number", None) == page_number):
  44.         parent_id = getattr(elem.metadata, "parent_id", None)
  45.         if parent_id in title_dict:
  46.             content = elem.text.strip()
  47.             if content:
  48.                 title_dict[parent_id]["content"].append(content)
  49. # 输出结构化结果
  50. for title_data in title_dict.values():
  51.     if title_data["content"]:
  52.         print("\n=== " + title_data["title"] + " ===")
  53.         for content in title_data["content"]:
  54.             print(content)
  55.         print()
复制代码
两种方式的区别:
特性LangChain 封装原生 Unstructured返回类型Document 对象Element 对象内容访问doc.page_contentelem.text类型判断doc.metadata["category"] == "Title"isinstance(elem, Title)元数据访问doc.metadata["parent_id"]elem.metadata.parent_id类型检查字符串比较Python 类型检查(更安全)用途LangChain/RAG 集成底层文档处理parent_id 的生成机制:
Unstructured 库在解析 PDF 时会:

  • 分析文档的层级结构
  • 识别标题、段落、列表等元素
  • 根据排版、字体大小、位置推断父子关系
  • 为每个元素生成唯一的 element_id
  • 为子元素设置 parent_id 指向父元素
示例结构:
  1. [
  2.     {
  3.         "element_id": "abc123",
  4.         "category": "Title",
  5.         "content": "云冈石窟简介",
  6.         "parent_id": None  # 顶级标题
  7.     },
  8.     {
  9.         "element_id": "def456",
  10.         "category": "NarrativeText",
  11.         "content": "云冈石窟位于山西省...",
  12.         "parent_id": "abc123"  # 属于上面的标题
  13.     }
  14. ]
复制代码
方案对比与选择

性能对比

[table][tr]方案速度内存准确度结构识别OCR 支持[/tr][tr][td]PyPDF[/td][td]⚡⚡⚡[/td][td]
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册