LangChain 学习笔记 · Day 8 文档加载器(Loader)

掌握 LangChain 文档加载器的使用方法,包括 PDF/TXT/网页加载,理解 Document 与 metadata 概念,并实现最小基于上下文回答的 Demo。
15 分钟阅读
LangChainPythonLoaderPDFTXTWebDocumentRAG

🎯 学习目标

  1. 学会使用 LangChain Loader 读取本地 PDF、TXT 和网页内容
  2. 理解 Documentmetadata 的作用
  3. 使用 TextSplitter 切分文档,做好后续向量化准备
  4. 完成一个最小 Demo:基于上下文回答问题(非完整 RAG)

🔁 核心概念

  • Document LangChain 的统一文档格式:
    Document(page_content: str, metadata: dict)
    
    • page_content: 文本内容
    • metadata: 来源信息(文件路径、页码、URL 等)
  • Loader 把外部数据转成 Document 对象。常见:
    • PyPDFLoader: 读取文本型 PDF
    • TextLoader: 加载 TXT/Markdown 等纯文本
    • WebBaseLoader: 抓取网页正文

⚙️ 环境准备

pip install --upgrade langchain langchain-community langchain-openai pypdf
pip install bs4 html2text   # 可选:网页加载

注意:PyPDFLoader 依赖 pypdf,只能读取文本型 PDF。扫描件需要 OCR(见常见问题)。


🧩 示例代码:day8_load_pdf.py

# file: day8_load_pdf.py
import os
from pathlib import Path
from typing import List

from langchain_community.document_loaders import PyPDFLoader, TextLoader, WebBaseLoader
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 1) 加载 PDF
def load_pdf(path: str) -> List[Document]:
    loader = PyPDFLoader(path)
    return loader.load()

# 2) 加载 TXT
def load_txt(path: str) -> List[Document]:
    return TextLoader(path, encoding="utf-8").load()

# 3) 加载网页
def load_web(url: str) -> List[Document]:
    return WebBaseLoader(url).load()

# 4) 切分
def split_docs(docs: List[Document], chunk_size=1200, chunk_overlap=200) -> List[Document]:
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        add_start_index=True,
    )
    return splitter.split_documents(docs)

# 5) 最小“基于上下文回答”
def answer_with_context(chunks: List[Document], question: str, model="kimi-k2-0711-preview") -> str:
    context = "\n\n".join(d.page_content for d in chunks[:3])
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是严谨的中文阅读助手。仅基于上下文回答,如果上下文没有答案,请说“根据提供的上下文无法确定”。"),
        ("user", "问题:{q}\n\n上下文:\n{ctx}")
    ])
    llm = ChatOpenAI(model=model, temperature=0.2)
    chain = prompt | llm | StrOutputParser()
    return chain.invoke({"q": question, "ctx": context})

if __name__ == "__main__":
    pdf_path = "docs/sample.pdf"
    assert Path(pdf_path).exists(), f"找不到文件:{pdf_path}"
    docs = load_pdf(pdf_path)
    print(f"✅ 读取完成:{len(docs)} 页。示例元信息:", docs[0].metadata)

    chunks = split_docs(docs)
    print(f"✅ 切分完成:{len(chunks)} 个文本块")

    question = "这份PDF的主题是什么?"
    answer = answer_with_context(chunks, question)
    print("\nQ:", question)
    print("A:", answer)

📌 常见问题

  1. PDF 是扫描件/图片?
    • PyPDFLoader 无法识别,需要 OCR:
      • pytesseract + pdf2image
      • unstructured 或其他云 OCR 服务
  2. 切分参数如何选?
    • chunk_size=800~1500chunk_overlap=100~200
    • 需在检索实验中微调
  3. 为什么 metadata 重要?
    • 用于答案引用出处(页码/标题/URL)
    • 后续检索链返回时可标注来源

✅ 今日任务

  • 使用 PyPDFLoader 读取一份 PDF,输出 metadata
  • 调整切分参数 (1200,200)(800,120),比较差异
  • answer_with_context() 提 3 个问题,验证“仅基于上下文回答”效果
  • 额外:加载一个 TXT 与一个网页,打印 metadata

🔮 预告 Day 9~14

  • Day 9:清洗与切分策略(标题分段、层级结构)
  • Day 10:Embedding + 向量库(Chroma)检索
  • Day 11:难 PDF(表格、图片 OCR)
  • Day 12:RAG 检索链 + 引用标注
  • Day 13:多文档融合与重排序
  • Day 14:评测与优化(准确性、幻觉率、延迟、成本)