RAG 实战教程(三):PDF、Word、Markdown 多文档知识库实战——文档解析、清洗与智能索引

在这里插入图片描述

RAG 系列第三篇。

本篇将在第二篇「Python + Qdrant 搭建第一个 RAG 知识库」的基础上继续升级,实现一个更加接近真实企业场景的知识库系统。

我们将支持:

  • PDF 文档
  • Word 文档
  • Markdown 文档
  • HTML 网页
  • 文档清洗
  • Metadata 管理
  • 增量索引
  • 多格式统一入库

最终实现:

企业文档
   |
   |
   ↓

PDF / Word / Markdown / HTML

   |
   ↓

Document Loader

   |
   ↓

文本清洗

   |
   ↓

结构化 Chunk

   |
   ↓

Embedding

   |
   ↓

Qdrant 向量数据库

   |
   ↓

RAG 问答系统

一、为什么真实 RAG 最难的是数据,而不是模型?

很多人在第一次接触 RAG 时,会认为:

RAG 的核心就是调用大模型。

实际上,真正落地之后你会发现:

模型往往不是最大的问题。

企业真正困难的是:

我的资料在哪里?

如何读取?

如何切分?

如何保持结构?

如何避免垃圾内容进入知识库?

如何知道答案来自哪一页?

例如:

一个企业知识库可能包含:

产品文档

├── 产品介绍.pdf
├── API 接口文档.md
├── 用户手册.docx
├── 售后规则.html
├── 内部培训 PPT
└── 数据库说明文档

这些文件有几个共同问题:


1. 文件格式不同

PDF:

文字
图片
表格
扫描页
页眉页脚

Word:

标题
正文
列表
表格
图片

Markdown:

标题层级
代码块
列表
链接

HTML:

标签
脚本
广告
导航

如果直接丢给 Embedding:

效果通常不会很好。


2. 文档结构容易丢失

例如:

原始 PDF:

第三章 用户退款规则

3.1 普通退款

用户购买后 7 天内可以申请退款。

3.2 特殊情况

超过 7 天需要人工审核。

如果简单提取:

可能变成:

第三章 用户退款规则
3.1 普通退款
用户购买后7天内可以申请退款
3.2特殊情况
超过7天需要人工审核

标题关系丢失。

Embedding 不知道:

普通退款

属于:

用户退款规则

3. 企业需要引用来源

一个真正可用的 RAG:

不是:

答案:

可以退款。

而应该:

答案:

用户购买后 7 天内可以申请退款。

来源:

用户协议.pdf

第 18 页

第三章 用户退款规则

所以我们必须保存:

{
    "content": "...",

    "metadata": {

        "source": "用户协议.pdf",

        "page": 18,

        "title": "退款规则"

    }
}

二、第三篇最终目标

这一篇完成之后,我们的 RAG 架构:

                  数据接入层


        PDF
         |
        Word
         |
     Markdown
         |
        HTML

          ↓


   Document Loader


          ↓


   Text Cleaner


          ↓


   Smart Chunk


          ↓


   Embedding


          ↓


   Qdrant


          ↓



用户问题

          ↓

Query Embedding

          ↓

Vector Search

          ↓

Context

          ↓

LLM

          ↓

答案 + 来源

三、项目结构升级

第二篇项目:

rag-demo/

升级:

rag-enterprise/


├── data/
│
│   ├── pdf/
│   ├── word/
│   ├── markdown/
│   └── html/
│


├── loaders/
│
│   ├── pdf_loader.py
│   ├── word_loader.py
│   ├── markdown_loader.py
│   └── html_loader.py
│


├── processors/

│   ├── cleaner.py
│   └── splitter.py


├── index/

│   ├── embedding.py
│   └── vector_store.py


├── pipeline/

│   └── index_pipeline.py


├── app.py


├── requirements.txt


└── README.md

这就是一个更加接近生产环境的结构。


四、安装依赖

创建:

requirements.txt

内容:

openai

qdrant-client

python-dotenv


pymupdf

python-docx

beautifulsoup4

markdown

安装:

pip install -r requirements.txt

五、统一 Document 数据结构

这是整个系统非常重要的一步。

不要让不同 Loader 返回不同格式。

例如:

PDF 返回:

{
    "text": "...",
    "page": 1
}

Word 返回:

{
    "content": "...",
}

后面一定会混乱。

所以我们定义统一格式:

{
    "content": "",

    "metadata": {

        "source": "",

        "type": "",

        "page": None,

        "title": ""

    }
}

创建:

models.py

代码:

from dataclasses import dataclass
from typing import Optional


@dataclass
class Document:


    content: str


    source: str


    doc_type: str


    page: Optional[int] = None


    title: Optional[str] = None



    def metadata(self):

        return {

            "source":
                self.source,


            "type":
                self.doc_type,


            "page":
                self.page,


            "title":
                self.title

        }

以后所有 Loader:

都返回:

Document

六、PDF 文档解析

PDF 是企业 RAG 中最常见的数据来源。

安装:

pip install pymupdf

PyMuPDF 导入:

import fitz

创建:

loaders/pdf_loader.py

代码:

import fitz

from models import Document



def load_pdf(path):


    pdf = fitz.open(path)


    documents = []


    for page_number, page in enumerate(
        pdf,
        start=1
    ):


        text = page.get_text()



        if not text.strip():

            continue



        documents.append(

            Document(

                content=text,


                source=path,


                doc_type="pdf",


                page=page_number

            )

        )


    return documents

测试:

from loaders.pdf_loader import load_pdf


docs = load_pdf(
    "data/pdf/example.pdf"
)


for doc in docs:


    print(doc.page)

    print(
        doc.content[:200]
    )

输出:

1

RAG 是一种检索增强生成技术...

2

Embedding 可以把文本转换成向量...

七、PDF 最大的问题:扫描件

现实中很多 PDF:

不是文字 PDF。

例如:

扫描合同.pdf
扫描发票.pdf
扫描说明书.pdf

打开:

你能看到文字。

但是程序:

page.get_text()

返回:

原因:

里面其实是图片。

需要:

OCR

流程:

PDF

 ↓

图片

 ↓

OCR

 ↓

文字

 ↓

Chunk

常见方案:

PaddleOCR

Tesseract

Azure OCR

Google Vision

后续企业版 RAG 会专门讲 OCR。


八、Word 文档解析

Word 使用:

python-docx

安装:

pip install python-docx

创建:

loaders/word_loader.py

代码:

from docx import Document as DocxDocument


from models import Document



def load_word(path):


    doc = DocxDocument(path)


    texts = []



    for paragraph in doc.paragraphs:


        text = paragraph.text.strip()


        if text:

            texts.append(text)



    content = "\n".join(
        texts
    )



    return [

        Document(

            content=content,


            source=path,


            doc_type="word"

        )

    ]

测试:

docs = load_word(
    "data/word/manual.docx"
)


print(
    docs[0].content
)

九、Markdown 文档解析

Markdown 在技术知识库里非常常见。

例如:

GitHub Wiki

技术博客

API 文档

项目 README

创建:

loaders/markdown_loader.py

代码:

from models import Document



def load_markdown(path):


    with open(
        path,

        "r",

        encoding="utf-8"

    ) as f:


        content = f.read()



    return [

        Document(

            content=content,


            source=path,


            doc_type="markdown"

        )

    ]

但是 Markdown 有一个优势:

它有结构。

例如:

# RAG

## 什么是 RAG

内容...


## 什么是 Embedding

内容...

我们后面可以根据:

#
##
###

进行智能切分。


十、HTML 网页解析

很多企业知识来自:

官网

帮助中心

API 文档

博客

HTML 需要清理:

删除:

script

style

导航

广告

安装:

pip install beautifulsoup4

创建:

loaders/html_loader.py

代码:

from bs4 import BeautifulSoup


from models import Document



def load_html(path):


    with open(

        path,

        "r",

        encoding="utf-8"

    ) as f:


        html = f.read()



    soup = BeautifulSoup(

        html,

        "html.parser"

    )



    for tag in soup(

        [
            "script",

            "style"

        ]

    ):

        tag.decompose()



    text = soup.get_text(

        "\n"

    )



    return [

        Document(

            content=text,


            source=path,


            doc_type="html"

        )

    ]

十一、统一 Loader 接口

现在:

PDF:

load_pdf()

Word:

load_word()

Markdown:

load_markdown()

HTML:

load_html()

虽然可以工作。

但是项目越来越大后:

调用会很乱。

所以统一:

创建:

loaders/__init__.py

代码:

from loaders.pdf_loader import load_pdf

from loaders.word_loader import load_word

from loaders.markdown_loader import load_markdown

from loaders.html_loader import load_html



def load_document(path):


    if path.endswith(
        ".pdf"
    ):

        return load_pdf(path)



    if path.endswith(
        ".docx"
    ):

        return load_word(path)



    if path.endswith(
        ".md"
    ):

        return load_markdown(path)



    if path.endswith(
        ".html"
    ):

        return load_html(path)



    raise Exception(

        f"不支持文件类型:{path}"

    )

以后新增格式:

只需要增加:

if suffix:
    return loader()

即可。


十二、文档清洗

原始文本通常很脏。

例如:

PDF:

第 1 页

公司名称

正文内容


第 2 页

公司名称

正文内容

页眉重复。

需要清洗。


创建:

processors/cleaner.py

代码:

import re



def clean_text(text):


    # 删除多余空白

    text = re.sub(

        r"\s+",

        " ",

        text

    )



    # 删除特殊字符

    text = re.sub(

        r"[^\w\s\u4e00-\u9fa5,。!?:;,.!?]",

        "",

        text

    )



    return text.strip()

测试:

text = """

RAG


是一种技术。


"""

print(
    clean_text(text)
)

输出:

RAG 是一种技术。

十三、为什么清洗非常重要?

因为 Embedding 不是理解人类格式。

它看到:

RAG

RAG

RAG


123456


公司名称

这些垃圾内容。

会影响:

向量质量

最终导致:

搜索不准

所以:

好的 RAG,首先需要好的数据。


(未完,下一部分继续写:智能 Chunk、Metadata 索引、多文档入库、增量更新、完整企业级 Pipeline。)

posted @ 2026-08-24 09:58  JavaPub  阅读(3)  评论(0)    收藏  举报