3、文档加载器实战:使用LangChain加载PDF、Word、TXT、HTML等不同格式文档

文档加载,说白了就是RAG系统的「入口关」。你想想看,如果连文档都读不进来,后面的检索和生成全是空谈。我在做第一个RAG项目时,就栽在这个环节上——PDF里的表格全乱了,Word文档的样式丢失,HTML里的图片链接全断了……嗯,今天咱们就把这些坑一个个填上。

3.1 为什么需要文档加载器?

LangChain的文档加载器,本质上是一个「格式适配器」。它把不同格式的文件,统一转换成LangChain内部的Document对象。这个对象包含两个核心部分:

  • page_content:文档的文本内容
  • metadata:文档的元数据(来源、页码、创建时间等)

我个人习惯把加载器理解为「翻译官」——它把PDF、Word、TXT、HTML这些「方言」,翻译成RAG系统能听懂的「普通话」。

核心原则: 加载器只负责「读进来」,不负责「理解内容」。清洗、分块、向量化是后面的事。

3.2 PDF加载器实战

PDF是最常见的文档格式,但也是最难处理的。为什么?因为PDF本质上是「打印描述语言」,它记录的是「在哪里画什么」,而不是「文本是什么」。

3.2.1 PyMuPDFLoader(推荐)

我个人最常用的是PyMuPDFLoader。它速度快,对中文支持好,还能保留基本的排版信息。

from langchain_community.document_loaders import PyMuPDFLoader

# 加载PDF文件
loader = PyMuPDFLoader("data/产品手册.pdf")
documents = loader.load()

# 查看加载结果
print(f"加载了 {len(documents)} 页")
print(f"第一页内容(前200字): {documents[0].page_content[:200]}")
print(f"元数据: {documents[0].metadata}")
小技巧: PyMuPDFLoader默认按页分割。如果你需要按章节分割,可以设置mode="page"参数。我在处理技术文档时,经常用这个模式。

3.2.2 PDFPlumberLoader(表格友好)

如果你的PDF里有大量表格,PDFPlumberLoader是更好的选择。我曾经处理过一份财务报表PDF,里面全是表格,PyMuPDFLoader读出来全是乱码,换成PDFPlumberLoader就完美解决了。

from langchain_community.document_loaders import PDFPlumberLoader

loader = PDFPlumberLoader("data/财务报表.pdf")
documents = loader.load()

# PDFPlumberLoader会保留表格结构
print(documents[0].page_content)
注意: PDFPlumberLoader处理扫描件(图片型PDF)时效果很差。如果你遇到扫描件,需要先用OCR工具(如PaddleOCR)转成文本。

3.3 Word文档加载器

Word文档(.docx)的处理相对简单,因为它是结构化的XML格式。LangChain提供了UnstructuredWordDocumentLoader和Docx2txtLoader两种方式。

3.3.1 UnstructuredWordDocumentLoader

这个加载器基于unstructured库,能保留标题、列表、表格等结构信息。

from langchain_community.document_loaders import UnstructuredWordDocumentLoader

loader = UnstructuredWordDocumentLoader("data/项目报告.docx")
documents = loader.load()

print(f"文档长度: {len(documents[0].page_content)} 字符")
print(f"元数据: {documents[0].metadata}")

3.3.2 Docx2txtLoader(轻量级)

如果你只需要纯文本,Docx2txtLoader更轻量,速度也更快。

from langchain_community.document_loaders import Docx2txtLoader

loader = Docx2txtLoader("data/项目报告.docx")
documents = loader.load()

# 纯文本输出,没有格式信息
print(documents[0].page_content[:500])
我的建议: 如果你需要保留标题层级(用于后续的分块策略),用UnstructuredWordDocumentLoader。如果只是提取文本内容,Docx2txtLoader就够了。

3.4 TXT文本加载器

TXT是最简单的格式,但也是最容易出问题的——编码问题。我曾经被一个GBK编码的TXT文件坑了整整一下午。

from langchain_community.document_loaders import TextLoader

# 默认使用UTF-8编码
loader = TextLoader("data/笔记.txt")
documents = loader.load()

# 如果遇到编码问题,指定编码
loader = TextLoader("data/笔记.txt", encoding="gbk")
documents = loader.load()

print(documents[0].page_content[:200])
避坑指南: 我曾经遇到过TXT文件混合了UTF-8和GBK编码的情况。建议先用chardet库检测编码:
import chardet
with open("file.txt", "rb") as f:
result = chardet.detect(f.read())
print(result['encoding'])

3.5 HTML加载器

HTML文档的难点在于「去噪」——你需要提取正文内容,去掉导航栏、广告、页脚等无关信息。

3.5.1 UnstructuredHTMLLoader

这个加载器基于unstructured库,能自动识别并提取正文内容。

from langchain_community.document_loaders import UnstructuredHTMLLoader

loader = UnstructuredHTMLLoader("data/文章.html")
documents = loader.load()

# 自动去除了HTML标签和无关元素
print(documents[0].page_content[:500])

3.5.2 BSHTMLLoader(自定义提取)

如果你需要更精细的控制,可以用BSHTMLLoader,它基于BeautifulSoup,你可以自定义提取规则。

from langchain_community.document_loaders import BSHTMLLoader

loader = BSHTMLLoader("data/文章.html")
documents = loader.load()

# 可以访问BeautifulSoup对象进行自定义处理
soup = loader.soup
# 提取所有h2标题
h2_tags = soup.find_all('h2')
for h2 in h2_tags:
    print(h2.get_text())
实战经验: 我在爬取网页做RAG时,通常先用BSHTMLLoader加载,然后手动去除<script><style>标签,再提取<article><main>区域的内容。这样准确率最高。

3.6 多格式批量加载器

实际项目中,你往往需要同时处理多种格式。LangChain提供了DirectoryLoader,可以批量加载整个目录下的文件。

from langchain_community.document_loaders import DirectoryLoader
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_community.document_loaders import TextLoader
from langchain_community.document_loaders import UnstructuredWordDocumentLoader

# 批量加载PDF文件
pdf_loader = DirectoryLoader(
    "data/",
    glob="**/*.pdf",
    loader_cls=PyMuPDFLoader
)
pdf_docs = pdf_loader.load()

# 批量加载TXT文件
txt_loader = DirectoryLoader(
    "data/",
    glob="**/*.txt",
    loader_cls=TextLoader,
    loader_kwargs={"encoding": "utf-8"}
)
txt_docs = txt_loader.load()

# 合并所有文档
all_docs = pdf_docs + txt_docs
print(f"总共加载了 {len(all_docs)} 个文档")
最佳实践: 我建议为每种格式单独配置加载器,然后合并结果。这样既能利用各加载器的优势,又能统一管理元数据。

3.7 加载器对比总结

格式 推荐加载器 优点 缺点 适用场景
PDF PyMuPDFLoader 速度快,中文支持好 表格处理一般 通用PDF文档
PDF(表格) PDFPlumberLoader 表格提取准确 扫描件不支持 财务报表、数据报表
Word UnstructuredWordDocumentLoader 保留结构信息 依赖unstructured库 需要保留标题层级
Word(纯文本) Docx2txtLoader 轻量快速 丢失格式信息 只需提取文本
TXT TextLoader 简单直接 需处理编码 纯文本文件
HTML UnstructuredHTMLLoader 自动去噪 可能误删内容 通用网页
HTML(自定义) BSHTMLLoader 灵活可控 需要手动处理 需要精确提取

3.8 实战:构建一个通用文档加载器

最后,我分享一个我在项目中实际使用的通用加载器。它能根据文件扩展名自动选择加载器,并统一处理异常。

import os
from langchain_community.document_loaders import (
    PyMuPDFLoader,
    TextLoader,
    UnstructuredWordDocumentLoader,
    UnstructuredHTMLLoader
)

def load_document(file_path):
    """通用文档加载器"""
    ext = os.path.splitext(file_path)[1].lower()
    
    try:
        if ext == '.pdf':
            loader = PyMuPDFLoader(file_path)
        elif ext == '.docx':
            loader = UnstructuredWordDocumentLoader(file_path)
        elif ext == '.txt':
            # 自动检测编码
            import chardet
            with open(file_path, 'rb') as f:
                encoding = chardet.detect(f.read())['encoding']
            loader = TextLoader(file_path, encoding=encoding)
        elif ext in ['.html', '.htm']:
            loader = UnstructuredHTMLLoader(file_path)
        else:
            raise ValueError(f"不支持的格式: {ext}")
        
        documents = loader.load()
        # 统一添加来源信息
        for doc in documents:
            doc.metadata['source'] = file_path
            doc.metadata['format'] = ext
        
        return documents
    
    except Exception as e:
        print(f"加载失败: {file_path}, 错误: {e}")
        return []

# 使用示例
docs = load_document("data/产品手册.pdf")
if docs:
    print(f"成功加载: {docs[0].metadata['source']}")
    print(f"内容长度: {len(docs[0].page_content)}")
最后提醒: 文档加载只是第一步。加载完成后,记得检查内容质量——我曾经加载了一个PDF,看起来正常,但实际内容全是乱码。建议每次加载后都打印前200字确认一下。

好了,文档加载器就讲到这里。下一章咱们聊聊文档分割——把长文档切成适合检索的小块。嗯,那才是真正考验技术的地方。