问题与目标
知识库接收的不只是 TXT。Markdown 有标题层级,JSON 有字段结构,HTML 有导航与正文,PDF 还可能包含分页、表格或扫描图片。如果加载阶段只得到一长串文本,后面很难引用来源、增量更新或执行权限过滤。
本篇把自建的 TXT、Markdown、JSON、HTML 和 PDF 样例统一为 LangChain Document。目标是每个对象都有非空 page_content,并保留来源、类型、版本、页码和访问范围。OCR、复杂表格还原和多模态解析不在本篇展开。
核心概念
Document 只有两个核心部分:
page_content:后续切分和检索使用的正文;metadata:来源、标题、页码、版本、权限等不可丢失的事实。
加载器负责“读出来”,不是“读正确”。必须检查乱码、空页、页眉页脚、重复段落、字段缺失和解析器异常。source 应使用稳定的业务标识或受控相对路径,不能把服务器绝对路径直接暴露给最终用户。
可运行实现
python -m pip install "langchain-core>=1,<2" beautifulsoup4 pypdf
下面的主例子用标准库和两个解析依赖统一五类文件。PDF 按页生成 Document,其他格式按文件生成。
import json
from pathlib import Path
from bs4 import BeautifulSoup
from langchain_core.documents import Document
from pypdf import PdfReader
def base_meta(path: Path) -> dict:
return {
"source": path.name,
"file_type": path.suffix.lower(),
"version": "2026-08-27",
"access_scope": "training",
}
def load_one(path: Path) -> list[Document]:
meta = base_meta(path)
suffix = path.suffix.lower()
if suffix in {".txt", ".md"}:
text = path.read_text(encoding="utf-8")
return [Document(page_content=text, metadata=meta)]
if suffix == ".json":
data = json.loads(path.read_text(encoding="utf-8"))
text = "\n".join(f"{key}: {value}" for key, value in data.items())
return [Document(page_content=text, metadata=meta)]
if suffix == ".html":
soup = BeautifulSoup(path.read_text(encoding="utf-8"), "html.parser")
for tag in soup(["script", "style", "nav"]):
tag.decompose()
title = soup.title.get_text(strip=True) if soup.title else path.stem
return [Document(
page_content=soup.get_text("\n", strip=True),
metadata={**meta, "title": title},
)]
if suffix == ".pdf":
pages = []
for index, page in enumerate(PdfReader(path).pages, start=1):
pages.append(Document(
page_content=page.extract_text() or "",
metadata={**meta, "page": index},
))
return pages
raise ValueError(f"unsupported type: {suffix}")
def load_directory(root: Path) -> tuple[list[Document], list[dict]]:
documents, errors = [], []
for path in sorted(root.iterdir()):
if not path.is_file():
continue
try:
loaded = load_one(path)
documents.extend(doc for doc in loaded if doc.page_content.strip())
except Exception as error:
errors.append({"source": path.name, "error": type(error).__name__})
return documents, errors
docs, errors = load_directory(Path("sample_docs"))
print([(d.metadata, len(d.page_content)) for d in docs])
print("errors:", errors)
输入目录由自己创建,避免把课程课件或公司文档直接放入公开仓库。输出先看数量、字符数和元数据,再抽查正文,而不是加载成功就立即入库。
常见问题与排查
PDF 页数正常但正文为空
页面可能是扫描图片。检查是否有文本层,再决定接入 OCR;不要把空字符串静默入库。OCR 结果还要抽查错字、阅读顺序和表格结构。
中文出现乱码
确认文件真实编码,而不是反复尝试 errors="ignore"。忽略错误会悄悄丢字符,后续检索问题更难定位。
HTML 把菜单和脚本当正文
先定位主内容区域并移除 nav/script/style,抽查标题与段落顺序。通用正文提取器也需要针对站点回归。
元数据到切分后丢失
切分器应复制来源、页码、版本和权限,并新增 Chunk ID。权限元数据缺失的片段不能默认公开。
小结
文档加载的产出不是“若干字符串”,而是一组正文可读、来源可追、版本与权限明确的 Document。先建立解析质量检查和失败清单,后面的切分、索引、引用与删除才有可靠依据。
License: CC BY-NC 4.0
Updated 2 hours ago
Was this article helpful? Give it a like.
0 comments


