2. 文档加载器对比

任务目标

本案例将使用同一份PDF文档,分别通过 PyPDFLoader、TextLoader 和 UnstructuredPDFLoader 三种加载器进行解析,对比它们在文本提取质量、元数据保留和特殊字符处理上的差异,最终根据对比结果确定最适合当前场景的加载器。

环境与依赖

pip install langchain langchain-community pypdf unstructured pdf2image

若需使用 UnstructuredPDFLoader 的OCR能力,还需安装 tesseract 系统依赖(本案例不涉及OCR,仅做基础文本提取对比)。

测试文档准备

创建一个包含标题、正文段落、列表和特殊符号(如 ©、™、→)的PDF文件,用于验证各加载器对格式和特殊字符的保留情况。以下Python脚本生成该测试文件:

from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas

c = canvas.Canvas("sample.pdf", pagesize=letter)
c.setFont("Helvetica", 14)
c.drawString(72, 720, "RAG Document Loader Test")
c.setFont("Helvetica", 11)
c.drawString(72, 690, "This is a paragraph with special chars: © 2024, TM symbol, arrow →")
c.drawString(72, 660, "List item 1: Python")
c.drawString(72, 645, "List item 2: LangChain")
c.drawString(72, 630, "List item 3: Vector DB")
c.save()
print("sample.pdf created")

完整实现代码

from langchain_community.document_loaders import PyPDFLoader, TextLoader, UnstructuredPDFLoader
import os

def compare_loaders(pdf_path):
    """对比三种加载器的输出结果"""
    results = {}
    
    # 1. PyPDFLoader
    try:
        loader = PyPDFLoader(pdf_path)
        docs = loader.load()
        results['PyPDFLoader'] = {
            'doc_count': len(docs),
            'content': docs[0].page_content if docs else '',
            'metadata': docs[0].metadata if docs else {}
        }
    except Exception as e:
        results['PyPDFLoader'] = {'error': str(e)}
    
    # 2. TextLoader (直接读取PDF二进制,通常会产生乱码)
    try:
        loader = TextLoader(pdf_path, encoding='utf-8')
        docs = loader.load()
        results['TextLoader'] = {
            'doc_count': len(docs),
            'content': docs[0].page_content[:200] if docs else '',
            'metadata': docs[0].metadata if docs else {}
        }
    except Exception as e:
        results['TextLoader'] = {'error': str(e)}
    
    # 3. UnstructuredPDFLoader
    try:
        loader = UnstructuredPDFLoader(pdf_path, mode="single")
        docs = loader.load()
        results['UnstructuredPDFLoader'] = {
            'doc_count': len(docs),
            'content': docs[0].page_content if docs else '',
            'metadata': docs[0].metadata if docs else {}
        }
    except Exception as e:
        results['UnstructuredPDFLoader'] = {'error': str(e)}
    
    return results

if __name__ == "__main__":
    pdf_file = "sample.pdf"
    if not os.path.exists(pdf_file):
        print("请先运行测试文档生成脚本")
    else:
        output = compare_loaders(pdf_file)
        
        # 打印对比结果
        for loader_name, result in output.items():
            print(f"\n{'='*50}")
            print(f"Loader: {loader_name}")
            if 'error' in result:
                print(f"Error: {result['error']}")
            else:
                print(f"Document count: {result['doc_count']}")
                print(f"Content preview: {result['content'][:150]}...")
                print(f"Metadata keys: {list(result['metadata'].keys())}")

运行与验证

运行步骤:

  1. 先执行测试文档生成脚本,创建 sample.pdf;
  2. 再运行对比脚本,观察三种加载器的输出差异。

预期输出与判定标准:

  • PyPDFLoader:能正确提取文本,保留段落结构,元数据包含 source 和 page 字段;特殊字符(©、→)应正确显示。
  • TextLoader:直接以文本模式读取PDF二进制内容,输出为乱码或不可读字符,元数据仅包含 source 字段。
  • UnstructuredPDFLoader:能提取文本,且对版面结构(如列表)的还原度较高,元数据包含 source 和 filetype 字段。

通过条件:若 PyPDFLoader 和 UnstructuredPDFLoader 均能正确提取可读文本,而 TextLoader 输出乱码,则证明对比实验有效。对于纯文本型PDF,PyPDFLoader 因速度快、依赖少而更适合作为默认选择;若PDF包含复杂表格或多栏布局,则 UnstructuredPDFLoader 更优。

选择建议

根据上述对比结果,在RAG知识库构建中:

  • 若PDF为数字生成的文本型文档(非扫描件),优先选择 PyPDFLoader,因其解析速度快且元数据完整;
  • 若PDF包含扫描图像或复杂排版,选择 UnstructuredPDFLoader 并配合OCR使用;
  • TextLoader 不适用于PDF文件,仅用于纯文本文件(如TXT、Markdown)。