文档导入 · 工程实战

从 PDF / Word 到知识库:文档导入完整流程

这是《知识库建设完全指南》的配套实操篇,专门讲清楚:一份 PDF 或 Word 文档,从"双击打开"到"能在问答里被召回",中间到底经历了什么。聚焦导入阶段——接入、解析、清洗、结构化、切分、元数据,再衔接向量化入库。配完整 Python 代码与流程图。

01为什么要把"文档导入"单独拎出来讲

知识库 8 成以上的质量问题,源头都在"解析"这一步。PDF / Word 是知识库最典型、也最棘手的数据源:它们不是纯文本,而是带版式、可能带图片/表格/扫描页的二进制容器。解析不准,后面切得再好、模型再强也白搭。

📕PDF:版式优先

本质是"打印稿",保的是样子不是结构。文本可能在任意坐标,还有扫描页、加密、表格跨页。

📘Word:结构优先

.docx 是 zip + XML,标题/段落/表格有标记;但旧 .doc 是二进制,图片内嵌,样式乱。

🧨坑都在细节

扫描件没 OCR、表格丢了结构、页眉页脚混进正文、加密文档打不开——任一处都会污染知识。

一句话定位:文档导入 = 把"人能看懂的版式文件"转成"机器能检索的干净文本块 + 元数据"。它是知识库质量的天花板,也是本篇主角。

02导入完整流程:从文件到可被召回

下面 7 步是"一份文档进知识库"的标准动作。前 6 步属于离线导入侧,最后一步衔接《知识库总览》里的在线检索与生成。

① 接入上传/同步 ② 识别格式/类型 ③ 解析抽文本/版式 ④ 清洗去噪/结构化 ⑤ 切分Chunk ⑥ 元数据来源/页码 ⑦ 向量化入库Embed+存储 导入侧(离线) 衔接在线检索/生成
与总览篇的关系:本篇负责"把文档变成干净的 Chunk + 元数据"(①②③④⑤⑥),第 ⑦ 步起就是《知识库建设完全指南》里的"向量化 → 入库 → 检索 → 重排 → 生成"。两篇拼起来才是完整闭环。

03第一步:接入与格式识别

在解析之前,先搞清楚"这是什么文件、能不能读、怎么读"。这一步决定后面走哪条解析路径。

📥接入方式

手动上传、对象存储(S3/OSS)监听、网盘/CRM Webhook、定时爬虫、数据库导出。企业里常见"共享文件夹 → 监听变更自动入库"。

🔍格式识别

按扩展名 + MIME + 魔数(magic bytes)双重确认。PDF 要看是否加密、是否有可读文本层;Word 要区分 .docx(OOXML)与老 .doc(二进制)。

关键判断:PDF 是"文本型"还是"扫描型"?

这是 PDF 解析的分水岭。文本型 PDF 能直接提取文字;扫描型(本质是图片)必须先 OCR。判断方法:

  • 尝试抽取文本:用解析库抽一页,如果有效文字极少(如 < 20 字符),大概率整本是扫描件。
  • 检查字体资源:PDF 里没有 /Font 对象,或页面只有一张大图,基本是扫描件。
  • 用户标注:上传时让用户勾选"是否为扫描件",可省一次探测。
拿到一个 PDF 是否加密? 加密 → 需密码 authenticate() 抽一页文本探测 有效文字量? ✅ 文本型 PyMuPDF / pdfplumber 🖼️ 扫描型 渲染页面 → OCR → 走第 4 节两条路径

04第二步:PDF 解析 —— 两条路线

路线 A · 文本型 PDF:直接抽文字 + 版式

文本型 PDF 已经内嵌文字,关键是在"抽干净"的同时保留结构信息(标题层级、段落、表格位置、阅读顺序)。

PyMuPDF (fitz)

速度快、能拿文本块坐标和阅读顺序,适合大批量。缺点:表格需配合其它库。

📊pdfplumber

基于 pdfminer,表格抽取(线条/单元格)能力强,适合报表、合同。

🧠版面模型

LayoutLMv3 / Detectron2 / 云服务(Azure、Textract):把"标题/段落/表格/图片"当目标检测,结构还原最好。

路线 B · 扫描型 PDF:渲染 + OCR

扫描件本质是图片。先按页渲染成高清图(300 DPI 起步),再走 OCR 把像素变文字。中文首选 PaddleOCR(识别准、自带版面),英文/通用可上 Tesseract 或云 OCR。

扫描件链路 render(page, dpi=300) → image → OCR(image) → text + 坐标框 → 结构化
文本型 PDF 解析库抽取文字 + 坐标 + 表格 结构化文本段落/标题/表 (Markdown) 扫描型 PDF 渲染成图 OCR 识别 结构化文本
坑:很多"看起来是文本"的 PDF,其实是"文字被转成矢量曲线 / 图片",抽出来是空白。遇到抽不出字,立刻回退到 OCR 路线,别硬刚。

05第三步:Word(.docx) 解析

.docx 比 PDF 友好得多——它是一个 ZIP 包,里面是一堆 XML。文字、标题样式、表格、批注都有标记,结构天然清楚。

report.docx = ZIP 容器 解压得到 XML word/document.xml word/styles.xml word/media/ (图片) python-docx 读取 段落 / 标题 / 表格 / 图 结构化文本 保留层级

用 python-docx 能拿到什么

  • 段落(Paragraph):含文字、样式名("Heading 1/2/3" 可直接当层级)。
  • 表格(Table):按行列遍历单元格,转成 Markdown 表格保留结构。
  • 图片(InlineShape):可提取另存;若要进知识库,建议 OCR 或让多模态模型生成描述。
  • 样式 / 大纲级别:用来还原章节树,是 Word 切分的最大优势。
老 .doc 怎么办:.doc(Word 97–2003)是二进制,python-docx 读不了。两种办法:① 让用户在 Word 里"另存为 .docx";② 服务端用 LibreOffice 无头模式批量转 docx(soffice --headless --convert-to docx),再走正常流程。

06第四步:清洗与结构化

解析出来的是"原始文本",还夹着大量噪声。清洗决定知识纯度。这一步把"能读"变成"好检索"。

🧹去噪

删页眉页脚、页码、水印、目录里的"......3"、重复的版权声明、乱码与多余空行。可用正则 + 版式位置规则。

📐表格结构化

把表格转成 Markdown / HTML,保留行列关系,别拍平成一坨文字,否则"某行某列"的信息全丢。

🖼️图片处理

图里的关键信息(流程图、截图文字)要么 OCR 提取,要么用多模态模型生成文字描述,否则这块知识"失明"。

🔤统一归一

统一编码(UTF-8)、全半角、空白符;合并被连字符断开的单词;规范标点。

结构化输出建议:清洗后尽量输出带标记的中间格式(如 Markdown),保留 # 标题| 表格 |![](图)。后面的切分能直接"按标题层级"切,比纯文本聪明得多。

一句话对比:PDF 清洗 vs Word 清洗

维度PDFWord
结构来源靠解析/版面模型推断XML 原生标记,最准
表格需 pdfplumber / 版面模型python-docx 直接遍历
标题层级靠字号/位置猜样式名直接给
典型噪声页眉页脚、扫描乱码样式混乱、隐藏文字

07第五步:智能切分 Chunking

清洗后的长文本要切成适合检索的"块(Chunk)"。切错了,检索必漏——这是导入阶段第二个关键决策点。

核心参数与重叠

每个 Chunk 大小(常见 300–800 token)和 overlap 重叠(10–20%)是两个旋钮。重叠让"被切断的语义"在相邻块里都能完整出现。

长文档 [ 一段很长的连续文本 ……………………………………………………………… ] 切分 + overlap Chunk 1 Chunk 2 (重叠) Chunk 3 (重叠) 重叠区让语义不被切断

四种切分策略对比

策略做法优点缺点
固定长度按 token/字符硬切简单、均匀割裂语义
按标题层级按 Markdown/样式章节切保结构、上下文完整(Word 最佳)块大小不均
语义切分按句向量聚类找断点语义连贯最优计算成本高
父子分块小块检索 + 大块喂模型召回准、上下文足存储翻倍
父子分块(强烈推荐生产用):小块(如 200 token)做向量检索,命中后用它所属的大块(如整节 2000 token)喂给 LLM。既保证"召回精准",又保证"给模型的上下文完整",代价是多存一份映射。

08第六步:元数据抽取

每个 Chunk 不能只存文字,还要带元数据——它是后面"过滤、溯源、权限"的基础设施。这一步常被忽略,却是生产级知识库的命脉。

📎来源类

文件名、路径、文档类型(PDF/Word)、原始 URL、上传者。

📍位置类

页码、章节标题、在文档内的偏移,方便"答案回到原处"。

🔐权限类

部门 / 角色标签、密级,用于检索时的元数据过滤隔离。

🕒时间类

文档创建/更新时间、入库时间,支撑"新鲜度"与失效回收。

🏷️语义类

自动打的标签 / 摘要(可选 LLM 生成),加速粗筛。

🆔关系类

父块 ID(父子分块用)、文档 ID,便于回溯整篇。

# 一个 Chunk 入库前的标准结构 { "chunk_id": "doc_123#p4#seg2", "text": "退款规则:订单完成后 7 天内可申请...", "metadata": { "source": "退款政策.docx", "doc_type": "word", "page": 4, "section": "第三章 售后", "dept": "客服部", "updated_at": "2026-08-20", "parent_id": "doc_123#sec3" } }
为什么要这么细:用户问"退款要几天",检索命中后可直接回链到"退款政策.docx 第 4 页 第三章",实现可溯源;权限过滤时一句 dept = 当前用户部门 就能防泄露。

09第七步:向量化与入库(衔接总览篇)

到这里,文档已变成"干净的 Chunk + 元数据"。最后这步把它变成"可被语义检索"的形态,正式进入《知识库建设完全指南》的检索侧。

入库 vector = Embedding(text) → vector_store.upsert(id, vector, payload={text, metadata})

🔢Embedding 选型

中文优先 bge-large-zhBGE-M3;海外/合规宽松可用 text-embedding-3。入库与查询必须同一模型

🗄️向量库选型

原型 Chroma;单体 pgvector;规模大 Qdrant / Milvus。必须支持元数据过滤(权限/时间/来源)。

导入侧最终交付物:一张"向量 + 原文 + 元数据"的表。后续问答时,用户问题同样向量化,在向量库里按相似度 + 元数据过滤召回 Chunk,再交给 LLM 生成并引用——完整闭环。

10实战代码:PDF + Word 统一导入

下面是一段可运行骨架,演示如何把 PDF 和 Word 走通"解析 → 清洗 → 切分 → 向量化 → 入库"全过程(用 PyMuPDF、python-docx、LangChain 切分、本地 bge 向量、Qdrant 存储)。

import fitz, docx, re from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter from sentence_transformers import SentenceTransformer from qdrant_client import QdrantClient embed = SentenceTransformer("BAAI/bge-large-zh") qdrant = QdrantClient("localhost", port=6333) # ---------- 1) PDF 解析(文本型) ---------- def parse_pdf(path): doc = fitz.open(path) pages = [] for i, page in enumerate(doc): txt = page.get_text("text") # 文字 + 自然阅读顺序 txt = re.sub(r"\n{3,}", "\n", txt) # 简单去噪 pages.append({"text": txt, "page": i + 1}) return pages # ---------- 2) Word 解析 ---------- def parse_docx(path): d = docx.Document(path) blocks, cur = [], "" for p in d.paragraphs: style = p.style.name or "" if style.startswith("Heading"): # 用样式还原标题层级 blocks.append(cur); cur = f"# {p.text}\n" else: cur += p.text + "\n" blocks.append(cur) return [{"text": b, "page": 1} for b in blocks if b.strip()] # ---------- 3) 切分(按标题 + 长度) ---------- splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=80) # ---------- 4) 统一导入 ---------- def ingest(path, doc_type): pages = parse_pdf(path) if doc_type == "pdf" else parse_docx(path) cid = 0 for pg in pages: for chunk in splitter.split_text(pg["text"]): vec = embed.encode(chunk).tolist() qdrant.upsert("kb", points=[{ "id": hash(f"{path}#{cid}"), "vector": vec, "payload": {"text": chunk, "source": path, "doc_type": doc_type, "page": pg["page"]} }]) cid += 1 # 用法 ingest("退款政策.docx", "word") ingest("产品手册.pdf", "pdf")
说明:这是教学骨架,生产要把"解析异常处理、扫描件 OCR 分支、表格转 Markdown、父子分块、元数据补全、批量并发、增量同步"都补上。完整工程链路见本篇各节与《知识库建设完全指南》。

11工具选型对比

PDF 解析工具

工具擅长短板部署
PyMuPDF快、坐标/顺序好表格弱本地 pip
pdfplumber表格抽取强速度一般本地 pip
PaddleOCR中文 OCR 准需 GPU 更佳本地 / 服务
Azure / Textract版面+OCR 一体、省心上云、按页计费云 API

Word 解析 / 转换

工具用途备注
python-docx读 .docx 段落/表格/样式不支持 .doc
LibreOffice 无头批量 .doc → .docx服务器装 soffice
Mammoth.docx → HTML/MarkdownWeb 端友好
选型口诀:文本型 PDF 用 PyMuPDF 起步;要表格上 pdfplumber;扫描件上 PaddleOCR;Word 用 python-docx,老 .doc 先 LibreOffice 转。本地优先,量大或对准确率极度敏感再上云 OCR/版面服务。

12常见坑(导入阶段避坑清单)

坑 1 · 扫描件没 OCR:直接抽文本得到空白,知识库全是空气。先探测文本量,不足就走 OCR。
坑 2 · 表格被拍平:"A 行 B 列 = X" 变成一堆散字,答不出具体值。务必转 Markdown/HTML 保结构。
坑 3 · 页眉页脚污染:每页重复的"公司机密"混进 Chunk,检索噪声大。按位置/正则剔除。
坑 4 · 切太大/太碎:块太大噪声多,太碎丢上下文。用父子分块 + overlap 平衡。
坑 5 · 加密文档打不开:PDF 设了密码,解析直接报错。流程里加 authenticate() 与失败告警。
坑 6 · 图片里的知识失明:流程图/截图上的文字没提取,关键信息丢失。OCR 或多模态描述补上。
坑 7 · 超大文件爆内存:几百页 PDF 一次读入 OOM。按页流式处理,别整本 load。
坑 8 · 忘了元数据:只存文本没存来源/页码,答错无法溯源、无法做权限隔离。

13导入流程检查清单(照着勾)

  1. 格式识别:确认 PDF/Word 类型、是否加密、是否扫描件。
  2. PDF 解析:文本型用 PyMuPDF/pdfplumber;扫描型渲染 + OCR;表格单独处理。
  3. Word 解析:python-docx 提取段落/标题/表格;老 .doc 先 LibreOffice 转 docx。
  4. 清洗:去页眉页脚/水印/页码/乱码,表格转 Markdown,图片 OCR 或描述。
  5. 结构化:输出带标题/表格标记的中间格式(Markdown),保留层级。
  6. 切分:定 chunk_size / overlap;优先按标题层级 + 父子分块。
  7. 元数据:补来源、页码、章节、部门、时间、父块 ID。
  8. 向量化:选中文 Embedding(bge/BGE-M3),入库与查询同模型。
  9. 入库:向量 + 原文 + 元数据写支持过滤的向量库(pgvector/Qdrant/Milvus)。
  10. 增量同步:文档更新触发重解析重索引,旧 Chunk 失效回收。
  11. 抽检验证:随机抽样,确认解析保真、切分合理、可溯源。
总结:文档导入 = 识别准 + 解析真 + 清洗净 + 切分巧 + 元数据全。它决定了知识库的质量天花板——这一关做扎实,后面的检索与生成才有意义。完整在线侧(检索/重排/生成/评估)见《知识库建设完全指南》。