Document Loader:LangChain 如何读取 PDF、网页、Word、数据库?
一、Loader 是 RAG 的入口,不是普通文件读取
很多人第一次用 LangChain 做知识库,代码通常是这样的:
loader = PyPDFLoader("产品手册.pdf") |
看起来像两行文件读取。其实不是。
Loader 的真正任务,是把各种乱七八糟的资料,统一变成 LangChain 标准的 Document。PDF、网页、Markdown、CSV、数据库、云盘,格式完全不同。后面的 Splitter、Embedding、VectorStore、Retriever 不想关心这些差异。
所以 LangChain 在这里做了一层抽象:不管你从哪里来,最后都要变成 Document。
二、官方定义:Loader 解决的是“统一入口”问题
LangChain 官方文档把 Document Loaders 定义为一个标准接口:它们负责从 Slack、Notion、Google Drive 等不同来源读取数据,并转换成 LangChain 的 Document 格式。这样后续组件可以用统一方式处理资料。
所有文档加载器都实现 BaseLoader 接口。每个加载器参数可以不同,但共享两个核心 API:
API | 作用 | 适合场景 |
load() | 一次性加载所有 Document | 小文件、调试、Demo |
lazy_load() | 按需产出 Document 生成器 | 大文件、批量任务、生产环境 |
aload() / alazy_load() | 异步加载版本 | 异步服务、爬取、批量并发 |
load_and_split() | 加载后直接切分 | 旧写法,不建议作为生产核心入口 |
一句话:load() 是方便入口,lazy_load() 才是生产思维。 |
三、BaseLoader 到底做了什么?
BaseLoader 的源码并不复杂,但设计很关键。它没有假设你读的是 PDF、网页还是数据库。它只约定一件事:子类最终要产出 Document。
源码逻辑可以压缩成四句话:
• load():直接把 lazy_load() 的结果收集成 list。
• lazy_load():真正的懒加载入口,应该由子类实现。
• aload() / alazy_load():异步包装,适合异步任务。
• load_and_split():先 load,再 split,但源码里已经提醒不要重写,且应逐步淡出核心用法。
用伪源码看,更清楚:
class BaseLoader: |
这里有一个非常重要的工程点:
BaseLoader 鼓励子类实现 lazy_load(),因为大文件、大目录、大网页集合,不能一次性全塞进内存。 |
四、Document 是 Loader 的标准产物
Loader 读完资料以后,不是返回字符串,也不是返回 JSON,而是返回 Document。
Document 的核心字段很少,但都很关键:
字段 | 含义 | 为什么重要 |
page_content | 正文文本 | 后续会被切分、向量化、拼进 Prompt |
metadata | 元数据字典 | source、page、title、权限、业务标签、时间都靠它 |
id | 可选唯一标识 | 方便增量更新、删除、去重、追溯 |
源码里还特意强调:Document 是 retrieval workflow 的对象,不是 chat message。也就是说,Document 是知识库里的资料单元,不是多轮对话里的 HumanMessage、AIMessage。
Document( |
生产环境里,metadata 比你想象中更重要。没有 metadata,RAG 就没法做来源引用;没有 page,用户不知道答案来自哪一页;没有权限字段,私有文档可能被错误召回;没有版本号,增量更新会变成噩梦。
五、Blob 和 Parser,为什么要拆开?
更深一点看源码,LangChain 还有 Blob 和 BaseBlobParser。
Blob 表示原始数据:可能是文件路径,可能是 bytes,可能是内存里的字符串,还可以带 MIME、encoding、metadata。Parser 负责把 Blob 解析成一个或多个 Document。
这个设计背后的思想是:
• 加载和解析分开。拿到文件是一回事,理解 PDF、HTML、DOCX 又是另一回事。
• 同一个 Blob 可以交给不同 Parser。比如 PDF 可以用简单解析器,也可以用带 OCR 的解析器。
• Parser 可以懒解析。大文件不必一次性全部读完。
• 扩展更干净。你要接企业内部文档系统,只需要把原始内容变成 Blob,再写 Parser。
这就是为什么企业级 RAG 不要只写“loader.load()”。你真正要关心的是:原始数据怎么取?解析器怎么选?Document 怎么补元数据?失败怎么重试?
六、常见 Loader 怎么选?
LangChain 的集成生态很大。官方文档中,Document Loader 按来源覆盖网页、PDF、云服务、常见文件类型等类别。LangChain 的整体集成生态也覆盖聊天模型、Embedding、工具、文档加载器、向量库等 1000+ 集成。
选择 Loader 不要只看“能不能读出来”,还要看“读出来的结构好不好”。
资料类型 | 重点问题 | 建议 |
表格、页码、图片、扫描件、双栏排版 | 普通 PDF 可用 PyPDF 类;复杂版式考虑 Docling、Unstructured、OCR | |
网页 | 导航、广告、脚本、动态渲染 | 静态页用 WebBaseLoader;动态页考虑浏览器/爬虫服务 |
Markdown | 标题层级、代码块、列表结构 | 尽量保留 Markdown 结构,方便后续按标题切分 |
目录 | 文件太多、格式混杂、隐藏文件 | 用 glob 精准过滤,用 loader_cls 指定解析器 |
表格 | 字段语义、行级颗粒度 | 把字段名和业务主键放进 metadata |
数据库 | 分页、权限、增量 | 按更新时间/主键批量拉取,不要全表扫 |
七、PDF Loader:最容易被低估的坑
PDF 是 RAG 里最常见,也最容易出问题的资料。
很多 PDF 看起来很规整,但里面可能是文本框、图片、表格、页眉页脚、两栏布局。普通解析器把它转成纯文本时,很容易丢结构。
PDF 解析要重点看四件事:
• 能不能保留页码。页码是溯源的关键。
• 能不能处理表格。表格被打散后,答案会很容易错。
• 能不能处理扫描件。扫描件需要 OCR,不是普通文本提取。
• 能不能处理版式。两栏排版、页眉页脚、脚注都可能污染内容。
PDF Loader 的核心不是“能读出字”,而是“读出的字还能不能回答问题”。 |
八、WebBaseLoader:网页不是正文,网页是噪声集合
网页加载也一样。你想要的是正文,但网页里还有导航栏、推荐文章、广告、版权声明、评论区、脚本内容。
WebBaseLoader 通常会配合 BeautifulSoup 解析 HTML。BeautifulSoup 的定位是从 HTML/XML 中提取数据,并提供遍历、搜索、修改解析树的能力。
真正使用时,不要无脑抓整页。最好指定正文区域。比如只抓文章标题、正文容器、文档主体 class。
loader = WebBaseLoader( |
这里的关键不是代码,而是思路:
• 能过滤就过滤,不要把整页垃圾都送进知识库。
• 动态网页要小心,requests 抓不到 JS 渲染后的内容。
• 批量抓网页要有速率限制、User-Agent、失败重试。
• 网页内容要保留 URL、标题、发布时间、站点来源。
九、DirectoryLoader:它不是解析器,它是调度器
很多人误会 DirectoryLoader,以为它自己会解析所有文件。更准确地说,它负责遍历目录,真正解析通常交给 loader_cls。
也就是说:
DirectoryLoader( |
这段代码的核心是:DirectoryLoader 找文件,TextLoader 读文件。
所以目录加载要重点控制:
• glob:只加载你要的文件,不要把临时文件、日志、备份都吃进去。
• loader_cls:不同文件类型要选不同 Loader。
• silent_errors:生产中不要简单吞错,要记录失败原因。
• use_multithreading:I/O 密集任务可以加速,但错误处理要更严格。
• load_hidden:隐藏文件是否加载,要看业务安全策略。
十、Loader 之后,立刻进入数据治理
企业级项目里,Loader 后面不能直接进 Embedding。中间至少要经过清洗、去重、补元数据、权限校验。
推荐的数据表设计可以简单分成几层:
表/对象 | 作用 | 关键字段 |
raw_document | 保存原始文档记录 | source_url、file_path、hash、status |
parse_task | 记录解析任务 | task_id、status、error_message、retry_count |
document_chunk | 保存切分后的 Chunk | doc_id、chunk_id、content、metadata |
document_embedding | 保存向量索引关联 | chunk_id、vector_id、embedding_model |
source_permission | 权限控制 | source_id、tenant_id、role、user_scope |
这样做的好处是:出了问题可以查。哪份文档解析失败,哪一页表格错了,哪个 Chunk 被召回,哪个用户有权限看,都能追溯。
十一、Loader 最容易翻车的地方
这几个坑,生产系统里非常常见:
• 编码问题:中文、日文、特殊符号乱码,后续向量化也会跟着错。
• PDF 表格丢失:财报、研报、合同里的关键数据经常藏在表格里。
• 网页噪声:导航、广告、推荐位进入知识库,召回结果会变脏。
• 元数据不足:没有 source/page/title,答案无法引用来源。
• 权限缺失:私有资料被公共用户检索到,属于严重安全事故。
• 全量重跑:每次都重新解析所有文档,成本高、速度慢、容易不稳定。
十二、源码级总结:Loader 链路压缩成一条线
真正要记住的不是 API 名字,而是职责边界:
• Loader 负责把外部资料读进来。
• Document 负责统一数据结构。
• metadata 负责来源、权限、追溯。
• Blob/Parser 负责把原始数据和解析逻辑拆开。
• lazy_load 负责生产环境的大文件和流式加载。
十三、总结
LangChain 的 Document Loader 看起来是小组件。
但在 RAG 系统里,它是第一道门。
门口放进来的资料越干净,后面的检索越准。
门口放进来的资料越混乱,后面的模型越容易胡说。
RAG 的质量,不是从 Prompt 开始的,而是从 Loader 开始的。 |
下一章继续讲 Text Splitter:为什么知识库不能整篇文档直接丢给大模型?
内容来源:Document Loader:LangChain 如何读取 PDF、网页、Word、数据库?:功能变化与行业影响解析_热闻岛
