RAG数据处理:PDF/HTML
RAG而言用户输入的数据通常是各种各样文档,本文主要采用langchain实现PDF/HTML文档的处理方法
PDF文档解析
PDF文档很常见格式,但内部结构常常较复杂:
- 复杂的版式布局
- 多样的元素(段落、表格、公式、图片等)
- 文本流无法直接获取
- 特殊元素如页眉页脚、侧边栏
技巧:转md
在实际应用中,我们经常会遇到这样的情况:
- PDF文档中的数学公式在导入知识库过程中变成乱码
- 解析过程极慢,特别是对于包含大量公式的长文档
几乎所有主流大模型都原生支持Markdown格式,它们的输出也多采用Markdown,因此我们可以考虑选择将pdf识别前转为md格式。现在主流方法采用MinerU(star33k)
常规电子版解析
pdfplumber 对中文支持较好,且在表格解析方面表现优秀,但对双拦文本的解析能力较差;pdfminer 和 PyMuPDF 对中文支持良好,但表格解析效果较弱;PyPDF2 对英文支持较好,但中文支持较差;papermage集成了pdfminer 和其他工具,特引适合处理论文场景。开发者可以根据实际业务场景的测试结果选择合适的工具odfplumber 或 pdfminer 都是兰不错的选择。
-
PyMuPDF (fitz):功能强大的PDF解析库,支持文本提取、表格识别和版面分析。
-
LangChain中的解析器:
- PyMuPDFLoader:基于PyMuPDF的封装,可提取文本和图片
-
基于机器视觉的解析工具:
- 深度学习方案:如百度飞桨的PP-Structure、上海AI实验室的MADU
- 商业解决方案:如PDFPlumber、LlamaIndex的LlamaParse
代码示例
# 使用LangChain的PyMuPDFLoader
from langchain.document_loaders import PyMuPDFLoaderloader = PyMuPDFLoader("example.pdf")
documents = loader.load()# 直接使用PyMuPDF进行高级解析
import fitz # PyMuPDF# 打开PDF
doc = fitz.open("example.pdf")# 提取所有文本(按页)
for page_num, page in enumerate(doc):text = page.get_text()print(f"页面 {page_num + 1}:\n{text}\n")# 提取表格
for page_num, page in enumerate(doc):tables = page.find_tables()for i, table in enumerate(tables):# 转换为pandas DataFramedf = table.to_pandas()print(f"页面 {page_num + 1}, 表格 {i + 1}:\n{df}\n")# 提取图片
for page_num, page in enumerate(doc):image_list = page.get_images(full=True)for img_index, img in enumerate(image_list):xref = img[0] # 图片的xref(引用号)image = doc.extract_image(xref)# 可以保存图片或进行进一步处理print(f"页面 {page_num + 1}, 图片 {img_index + 1}: {image['ext']}")
进阶技巧
针对PDF的复杂性,可以采用以下策略:
- 结合OCR处理扫描版PDF
- 使用机器学习模型识别版面布局
- 针对表格内容使用专门的表格解析算法
- 对公式内容使用LaTeX识别工具
含图片电子版解析
基于规则匹配
基于深度学习匹配
比规则匹配有更好的效果
Layout-parser、Pp-StructureV2、PDF-Extract-Kit、pix2text、MinerU、 marker
HTML文档解析
HTML是网页的标准标记语言,包含文本、图片、视频等多种内容,通过不同标签组织。
常用解析工具
-
Beautiful Soup:Python中最常用的HTML解析库,能通过标签和CSS选择器精确提取内容。
-
LangChain中的解析器:
- WebBaseLoader:结合urllib和Beautiful Soup,先下载HTML再解析
- BSHTMLLoader:直接解析本地HTML文件
代码示例
# 使用LangChain的WebBaseLoader解析网页
from langchain.document_loaders import WebBaseLoaderloader = WebBaseLoader("https://example.com")
documents = loader.load()# 使用Beautiful Soup定制解析
from bs4 import BeautifulSoup
import requestsresponse = requests.get("https://example.com")
soup = BeautifulSoup(response.text, "html.parser")# 提取所有代码块
code_blocks = soup.find_all("div", class_="highlight")
for block in code_blocks:print(block.get_text())# 提取所有标题和段落
content = []
for heading in soup.find_all(["h1", "h2", "h3"]):content.append({"type": "heading", "text": heading.get_text()})# 获取标题后的段落for p in heading.find_next_siblings("p"):if p.find_next(["h1", "h2", "h3"]) == p:breakcontent.append({"type": "paragraph", "text": p.get_text()})
进阶技巧
对于复杂的HTML页面,可以考虑以下策略:
- 使用CSS选择器精确定位元素
- 识别并过滤导航栏、广告等无关内容
- 保留文档结构(标题层级关系)
- 特殊处理表格、代码块等结构化内容
基于深度学习的通用文档解析:以DeepDoc为例
传统的解析方法各有局限,近年来基于深度学习的文档解析技术取得了突破性进展。DeepDoc(来自RapidocAI)是一个典型代表,它采用机器视觉方式解析文档。
DeepDoc的工作流程
- 文档转图像:将PDF等文档转换为图像
- OCR文本识别:识别图像中的文本内容
- 布局分析:使用专门模型识别文档布局结构
- 表格识别与解析:使用TSR(Table Structure Recognition)模型解析表格
- 内容整合:将识别的各部分内容整合成结构化数据
代码示例
# 使用DeepDoc进行文档解析
from rapidocr import RapidOCR
from deepdoc import LayoutAnalyzer, TableStructureRecognizer# 初始化模型
ocr = RapidOCR()
layout_analyzer = LayoutAnalyzer()
table_recognizer = TableStructureRecognizer()# 文档OCR
image_path = "document.png" # 可以是PDF转换的图像
ocr_result = ocr.recognize(image_path)
texts, positions = ocr_result# 布局分析
layout_result = layout_analyzer.analyze(image_path)
# 识别出的布局元素:标题、段落、表格、图片等
elements = layout_result["elements"]# 处理识别到的表格
for element in elements:if element["type"] == "table":table_image = element["image"]# 表格结构识别table_result = table_recognizer.recognize(table_image)# 表格数据可转换为CSV或DataFrametable_data = table_result["data"]# 整合所有内容
document_content = []
for element in sorted(elements, key=lambda x: x["position"]):if element["type"] == "title":document_content.append({"type": "title", "text": element["text"]})elif element["type"] == "paragraph":document_content.append({"type": "paragraph", "text": element["text"]})elif element["type"] == "table":document_content.append({"type": "table", "data": element["table_data"]})# 其他类型元素...
DeepDoc的优势
- 多格式支持:可处理PDF、Word、Excel、PPT、HTML等多种格式
- 结构保留:准确识别文档的层次结构和布局
- 表格处理:精确解析复杂表格,包括合并单元格
- 图像处理:可提取和关联文档中的图像内容
- 多语言支持:支持中英文等多种语言的文档解析
通用文档解析:Unstructured库
对于需要处理多种文档格式但又不想为每种格式单独编写解析代码的场景,Unstructured库提供了统一的解决方案。
Unstructured的工作原理
Unstructured可自动识别文件格式,并调用相应的解析器提取内容,支持多种常见文档格式。
代码示例
from unstructured.partition.auto import partition# 自动识别文件格式并解析
elements = partition("document.pdf") # 也可以是docx, pptx, html等# 提取所有文本元素
text_elements = [el for el in elements if hasattr(el, "text")]
for element in text_elements:print(element.text)# 根据元素类型处理
from unstructured.partition.html import partition_html
from unstructured.chunking.title import chunk_by_title# HTML特定解析
html_elements = partition_html("document.html")# 按标题分块
chunks = chunk_by_title(elements)
for chunk in chunks:print(f"标题: {chunk.title}")print(f"内容: {chunk.text}")
构建文档处理管道
在实际的RAG系统中,我们通常需要构建完整的文档处理管道,将解析、清洗、分块等步骤串联起来。
完整处理流程示例
import os
from typing import List, Dict, Any
from langchain.document_loaders import PyMuPDFLoader, WebBaseLoader, UnstructuredExcelLoader
from langchain.text_splitter import RecursiveCharacterTextSplitterdef process_document(file_path: str) -> List[Dict[str, Any]]:"""处理各种格式的文档,返回标准化的文档块"""# 根据文件扩展名选择合适的加载器ext = os.path.splitext(file_path)[1].lower()if ext == ".pdf":loader = PyMuPDFLoader(file_path)elif ext == ".html" or ext == ".htm":# 假设是本地HTML文件with open(file_path, "r", encoding="utf-8") as f:content = f.read()loader = WebBaseLoader(file_path)elif ext in [".xlsx", ".xls"]:loader = UnstructuredExcelLoader(file_path)else:# 对于其他格式,使用Unstructuredfrom langchain.document_loaders import UnstructuredFileLoaderloader = UnstructuredFileLoader(file_path)# 加载文档documents = loader.load()# 文本清洗(去除多余空格、特殊字符等)cleaned_documents = []for doc in documents:text = doc.page_content# 基本清洗text = text.replace("\n\n", " ").replace("\t", " ")text = ' '.join(text.split()) # 规范化空格# 更新文档doc.page_content = textcleaned_documents.append(doc)# 文本分块text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000,chunk_overlap=200,separators=["\n\n", "\n", ". ", " ", ""])chunks = text_splitter.split_documents(cleaned_documents)# 转换为标准格式processed_chunks = []for chunk in chunks:processed_chunks.append({"text": chunk.page_content,"metadata": chunk.metadata,"source": file_path,"chunk_id": f"{os.path.basename(file_path)}_{chunks.index(chunk)}"})return processed_chunks# 使用示例
pdf_chunks = process_document("example.pdf")
html_chunks = process_document("example.html")
excel_chunks = process_document("example.xlsx")# 合并所有文档的处理结果
all_chunks = pdf_chunks + html_chunks + excel_chunks# 现在可以将这些块用于向量化和索引