创见博客
LangChain 常用 Loader 实战:Text、CSV、JSON、PDF 与 Markdown
七崽爱吃小饼干2026/10/18阅读 0

上一篇(demo-20)搭建了完整的 RAG 流程,其中第一步是「加载文档」。真实项目里的资料不会只有 Markdown,还会有纯文本、CSV、JSON、PDF 等格式。

这一篇专门讲 Loader:它们如何把不同格式统一成 Document,以及 TextLoader、CSVLoader、JSONLoader、PDFLoader 和 Markdown 文档的常见用法。

代码基于 JavaScript 版 langchain v1。TextLoader / JSONLoader 来自 @langchain/classic,CSVLoader / PDFLoader 来自 @langchain/community。

一、Loader 在 RAG 中的位置

RAG 的完整链路是:

Loader -> Document[] -> TextSplitter -> Embedding -> VectorStore -> Retriever -> LLM

Loader 只负责第一步:把文件读成统一的 Document。

js
{
  pageContent: "文档正文……",
  metadata: {
    source: "data/notes.txt",
    line: 1,
    loc: { pageNumber: 1 },
    ...
  }
}

无论原始格式是什么,Loader 的输出都是 Document[]。后续的切分、向量化和检索不需要关心文件原本是 CSV 还是 PDF。

所有 Loader 的调用方式也一致:

js
const documents = await loader.load();

常用 Loader 一览

格式Loader包默认粒度主要 metadata
纯文本TextLoader@langchain/classic一个文件一个 Documentsource
CSVCSVLoader@langchain/community一行一个 Documentsource、line
JSONJSONLoader@langchain/classic一个字符串一个 Documentsource、line
PDFPDFLoader@langchain/community一页一个 Documentsource、pdf、loc.pageNumber
Markdown无独立 Loader—建议 TextLoader + MarkdownTextSplittersource

Markdown 的说明:JavaScript 版 LangChain 没有内置的 MarkdownLoader。Markdown 本质是文本,日常用 TextLoader 载入,再用 MarkdownTextSplitter 按标题和段落切分即可。如果需要解析复杂文档结构,可以使用依赖 Unstructured 服务的 UnstructuredLoader。

二、环境准备

安装 Loader 和相关依赖:

bash
npm install @langchain/classic @langchain/community \
            @langchain/textsplitters pdf-parse d3-dsv

各依赖的作用:

依赖作用
@langchain/classicTextLoader、JSONLoader
@langchain/communityCSVLoader、PDFLoader
@langchain/textsplittersMarkdownTextSplitter
pdf-parsePDFLoader 解析 PDF 的底层依赖
d3-dsvCSVLoader 解析 CSV 的底层依赖

本 demo 使用的示例文件:

文件内容
data/notes.txtLangChain 学习笔记
data/users.csv用户表格
data/faq.json产品 FAQ
data/manual.pdf两页的产品手册
data/handbook.mdMarkdown 使用手册

运行:

bash
node demo-21/index.js

这个 demo 不需要调用大模型,也不消耗 API Token,只演示 Loader 的输出。

三、TextLoader:纯文本

TextLoader 是最简单的 Loader:把整个文件读成一个 Document。

js
import { TextLoader } from "@langchain/classic/document_loaders/fs/text";

const loader = new TextLoader("data/notes.txt");
const docs = await loader.load();

console.log(docs.length); // 1
console.log(docs[0].metadata);
// { source: "data/notes.txt" }
console.log(docs[0].pageContent);

本 demo 的实际输出:

Document 数量:1
[1] source=notes.txt
    LangChain 学习笔记 LangChain 是一个用于构建大模型应用的开源框架。
    它把模型调用、提示词、工具、检索和记忆抽象成可组合的组件。
    RAG 分为离线索引和在线生成两个阶段……

特点:

  • 一个文件 = 一个 Document;
  • 不保留段落、标题等结构;
  • metadata 只有 source;
  • 适合 .txt、.log,也可以直接读 .md 原始文本;
  • 大文件还需要交给 TextSplitter 切分。

TextLoader 也支持传入 Blob,浏览器或上传场景可以直接用:

js
const loader = new TextLoader(new Blob(["你好,LangChain"]));

四、CSVLoader:表格数据

CSVLoader 把表格的每一行转换成一个 Document。

js
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";

const loader = new CSVLoader("data/users.csv");
const docs = await loader.load();

示例 CSV:

csv
id,name,city,interest
1,小明,杭州,RAG
2,小红,上海,Agent
3,小刚,深圳,长期记忆

默认输出:

Document 数量:4

[1] source=users.csv,line=1
    id: 1
    name: 小明
    city: 杭州
    interest: RAG

[2] source=users.csv,line=2
    id: 2
    name: 小红
    ...

每一列会写成 列名: 值 的形式,metadata.line 记录行号,方便定位。

只取某一列

如果只关心某一列,可以把这一列作为 pageContent:

js
const loader = new CSVLoader("data/users.csv", { column: "name" });
const docs = await loader.load();

console.log(docs.map((doc) => doc.pageContent));
// ["小明", "小红", "小刚", "小美"]

其它选项

js
new CSVLoader("data/users.csv", {
  column: "interest",
  separator: ",",
});
  • column:指定内容列;
  • separator:自定义分隔符,默认逗号。

注意:

  • CSVLoader 假设第一行是表头;
  • 字段里本身包含逗号时,需要标准 CSV 引号转义;
  • 大文件会一次性读入内存,超大 CSV 建议流式处理或先入库;
  • 中文 CSV 注意文件编码,建议统一使用 UTF-8。

五、JSONLoader:结构化 JSON

JSONLoader 默认会递归提取 JSON 中所有字符串,每个字符串变成一个 Document。

js
import { JSONLoader } from "@langchain/classic/document_loaders/fs/json";

const loader = new JSONLoader("data/faq.json");
const docs = await loader.load();

示例 JSON:

json
{
  "product": "星尘笔记",
  "faqs": [
    {
      "question": "如何导出笔记?",
      "answer": "在设置页面选择「导出」,支持 Markdown 和 PDF 两种格式。"
    },
    {
      "question": "是否支持多人协作?",
      "answer": "专业版支持最多 20 人同时编辑,并且保留历史版本。"
    }
  ]
}

不传 pointer 时,输出 8 个 Document,包括产品名、问题、答案和 meta 里的字符串:

[1] 星尘笔记
[2] 如何导出笔记?
[3] 在设置页面选择「导出」,支持 Markdown 和 PDF 两种格式。
...

用 JSON Pointer 选择子树

更常见的做法是只取需要的部分:

js
const loader = new JSONLoader("data/faq.json", "/faqs");
const docs = await loader.load();

输出 6 个 Document,只包含两组的 question 和 answer,不再包含 product 和 meta。

精确到字段

可以传数组,指定多个 JSON Pointer:

js
const loader = new JSONLoader("data/faq.json", [
  "/faqs/0/question",
  "/faqs/1/question",
]);

JSON Pointer 的规则:

  • 用 / 分隔层级;
  • 数组用下标,例如 /faqs/0/answer;
  • 不支持通配符 *,需要逐个字段指定;
  • 如果 JSON 是数组,根路径可以用 /0 表示第一个元素。

JSON Lines

如果文件是 JSON Lines(每行一个 JSON 对象),使用 JSONLinesLoader:

js
import { JSONLinesLoader } from "@langchain/classic/document_loaders/fs/json";

const loader = new JSONLinesLoader("data/events.jsonl", "/text");

注意:

  • 无 pointer 时会提取所有字符串,文档数量可能很多;
  • 结构化字段之间会失去关联,FAQ 的 question 和 answer 会变成两条独立 Document;
  • 如果业务需要保留对象级关系,建议在加载后自己组装,或改用自定义 Loader。

六、PDFLoader:PDF 文档

PDFLoader 使用 pdf-parse 提取文本,默认一页一个 Document。

js
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";

const loader = new PDFLoader("data/manual.pdf");
const docs = await loader.load();

本 demo 的两页 PDF 输出:

Document 数量:2

[1] source=manual.pdf,pdf={...},loc={"pageNumber":1}
    星尘笔记 PDF 手册 第一章 产品简介 星尘笔记是一款面向知识工作者的笔记工具……

[2] source=manual.pdf,pdf={...},loc={"pageNumber":2}
    如果发现内容丢失,可以从历史版本中恢复。第八章 联系与支持……

metadata 里包含:

  • source:文件路径;
  • pdf:PDF 版本、页数等元信息;
  • loc.pageNumber:当前页码,做引用溯源时很有用。

合并为一页

如果不想按页拆分:

js
const loader = new PDFLoader("data/manual.pdf", {
  splitPages: false,
});
const docs = await loader.load();
// Document 数量:1

常用选项:

js
new PDFLoader("data/manual.pdf", {
  splitPages: true,
  parsedItemSeparator: "\n",
});
  • splitPages:是否按页拆分,默认 true;
  • parsedItemSeparator:同一页内文本片段的连接符。

PDF 的常见坑

  1. 扫描件不能直接提取文字:需要 OCR(如 Tesseract、云 OCR);
  2. 复杂排版顺序可能错乱:双栏、表格、页眉页脚会干扰阅读顺序;
  3. 字体编码问题:某些 PDF 提取后可能出现兼容字符或乱码;
  4. 表格结构丢失:PDF 里的表格通常变成普通文本,需要额外解析;
  5. 页数很多时:按页拆分会产生大量小 Document,建议再做合并或切分。

生产环境如果 PDF 质量参差不齐,可以考虑 Unstructured、LlamaParse、云文档解析服务等更专业的方案。

七、Markdown 文档:TextLoader + MarkdownTextSplitter

JavaScript 版 LangChain 没有独立的 MarkdownLoader。推荐做法:

js
import { TextLoader } from "@langchain/classic/document_loaders/fs/text";
import { MarkdownTextSplitter } from "@langchain/textsplitters";

const docs = await new TextLoader("data/handbook.md").load();

const splitter = new MarkdownTextSplitter({
  chunkSize: 120,
  chunkOverlap: 20,
});

const chunks = await splitter.splitDocuments(docs);

载入结果仍然是一个 Document:

Document 数量:1
[1] source=handbook.md
    # 星尘笔记使用手册 星尘笔记是一款面向知识工作者的笔记工具……

按 Markdown 结构切分后:

Document 数量:4
[1] # 星尘笔记使用手册
    星尘笔记是一款面向知识工作者的笔记工具……
[2] ## 快速开始
    1. 注册账号并创建工作区。
    2. 点击「新建笔记」,输入标题和正文。
    ...
[3] ## 数据同步
    云端同步默认开启……
[4] ## 常见问题
    如果同步失败,先检查网络和登录状态……

MarkdownTextSplitter 会优先在标题、列表、段落等边界切分,尽量保持语义完整。metadata.source 会从原 Document 继承。

如果需要更复杂的结构解析

JavaScript 版没有专门的 Markdown Loader,但可以使用通用的 UnstructuredLoader:

js
import { UnstructuredLoader } from "@langchain/community/document_loaders/fs/unstructured";

const loader = new UnstructuredLoader("data/handbook.md", {
  apiUrl: "http://localhost:8000",
});
const docs = await loader.load();

它依赖 Unstructured API 或本地 Unstructured 服务,适合需要解析复杂文档结构的场景。普通 Markdown 项目用 TextLoader + MarkdownTextSplitter 就足够了。

关于 frontmatter

如果 Markdown 顶部有 YAML frontmatter,TextLoader 会把它当作普通文本读入。需要把 frontmatter 变成 metadata 时,可以在加载后自己解析:

js
const [frontmatter, body] = rawText.split(/^---$/m);

或者使用专门的 frontmatter 解析库。Loader 本身不负责这件事。

八、把 Loader 输出统一交给后续流程

不同 Loader 的输出结构完全一致,可以直接汇总:

js
const allDocs = [
  ...textDocs,
  ...csvDocs,
  ...faqDocs,
  ...pdfDocs,
  ...markdownChunks,
];

然后进入同一条流水线:

Loader -> Document[] -> TextSplitter -> Embedding -> VectorStore

本 demo 的汇总结果:

总 Document 数:17
- notes.txt: 1
- users.csv: 4
- faq.json: 6
- manual.pdf: 2
- handbook.md: 4

不同格式的切分建议

格式建议
纯文本按段落或固定长度切分,保留重叠
CSV一行一个 Document,通常不需要再切
JSON用 pointer 选择业务对象,再按字段组装
PDF按页加载,再按段落切分;注意页眉页脚
MarkdownMarkdownTextSplitter 按标题和段落切分

工程实践

  1. 保留 metadata:source、页码、行号、标题路径,方便引用溯源;
  2. 加载后先检查:空文档、乱码、重复内容、页眉页脚;
  3. 不要过早切分:先拿到完整 Document,再按格式选择切分策略;
  4. 批量加载:大量文件可以用 DirectoryLoader,或自己遍历目录;
  5. 增量更新:记录文件哈希或更新时间,只重新索引变化的文件;
  6. 权限过滤:把租户 ID、可见范围写进 metadata,检索时过滤;
  7. 删除同步:源文件删除后,向量库里的对应文档也要删除。

九、Python 对应写法

Python 侧的 Loader 主要集中在 langchain_community.document_loaders:

python
from langchain_community.document_loaders import (
    TextLoader,
    CSVLoader,
    JSONLoader,
    PyPDFLoader,
)

# 纯文本
text_docs = TextLoader("data/notes.txt", encoding="utf-8").load()

# CSV:每行一个 Document
csv_docs = CSVLoader(
    "data/users.csv",
    csv_args={"delimiter": ","},
    encoding="utf-8",
).load()

# JSON:用 jq 语法选择子树
json_docs = JSONLoader(
    "data/faq.json",
    jq_schema=".faqs[]",
    text_content=False,
).load()

# PDF:通常一页一个 Document
pdf_docs = PyPDFLoader("data/manual.pdf").load()

Markdown 同样推荐 TextLoader + MarkdownTextSplitter:

python
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import MarkdownTextSplitter

docs = TextLoader("data/handbook.md", encoding="utf-8").load()
splitter = MarkdownTextSplitter(chunk_size=120, chunk_overlap=20)
chunks = splitter.split_documents(docs)

如果需要结构化的 Markdown 解析,可以用 UnstructuredMarkdownLoader:

python
from langchain_community.document_loaders import UnstructuredMarkdownLoader

docs = UnstructuredMarkdownLoader("data/handbook.md").load()

注意 Python 的 JSONLoader 使用 jq 语法(.faqs[]),JavaScript 版使用 JSON Pointer(/faqs),两者不要混用。

十、小结

  • Loader 的作用:把不同格式统一成 Document { pageContent, metadata }。
  • TextLoader:纯文本,一文件一 Document。
  • CSVLoader:表格,一行一 Document,可用 column 只取某列。
  • JSONLoader:结构化 JSON,用 JSON Pointer 选择子树或字段。
  • PDFLoader:按页提取文本,扫描件需要 OCR,复杂排版要额外处理。
  • Markdown:没有独立 MarkdownLoader,推荐 TextLoader + MarkdownTextSplitter。
  • 统一出口:所有 Loader 都返回 Document[],后续接切分、Embedding、向量库和 RAG。

一句话:Loader 不负责理解文档,它只负责把不同格式变成统一的 Document。

评论
0/100