RAG 检索增强生成实践:文档加载与解析

作者:袖梨 2026-09-13

在 RAG 工程中,文件进入向量化流程之前,必须先被转换为包含正文和元数据的统一 Document 对象。电子 PDF、扫描件以及 Word、Markdown 等格式的底层结构并不相同,使用同一种读取方式往往会丢失内容或结构。接下来将从 Spring AI 的解析组件入手,说明本地抽取、云端 OCR 与自动分流的实现思路。

技术栈:Spring AI 1.1.2 · Spring AI Alibaba 1.1.2 · Spring Boot 3.5.9 · JDK 21+


1. 文档加载与解析概述

1.1 文档解析做什么

文档解析是把各种格式的原始文件(PDF / Word / Markdown / HTML / 图片……)抽取成带文本 + 元数据的统一 Document 对象,作为 RAG 索引的第一步:

原始文件 → 解析 → List<Document>

本文只讲**「解析」这一步**:框架提供了哪些解析组件、怎么调用,以及电子件 / 扫描件分别用什么策略。

1.2 统一数据模型 Document

不管原始文件是 PDF、Word 还是图片,所有解析组件最终都输出同一种对象 —— Document

public class Document {
    private String id;                    // 唯一标识
    private Map<String, Object> metadata; // 来源、文件名、页码、格式等
    private String text;                  // 抽取出的正文文本
    private Media media;                  // 图片/音频等多媒体(可选)
}
Document doc = Document.builder()
        .id(UUID.randomUUID().toString())
        .text("这是一段被抽取出来的正文……")
        .metadata(Map.of(
                "source", "/data/report.pdf",
                "file_name", "report.pdf",
                "page_number", 3,
                "file_type", "application/pdf"))
        .build();

记住一点即可:所有解析组件的入口方法都返回 List<Document>。元数据 metadata 建议在解析时尽量填全(来源、页码、格式等),方便后续追溯。


2. 文档解析组件

2.1 组件总览

Spring AI 及 Spring AI Alibaba 提供了覆盖常见格式的解析组件,用法高度一致——都是 new XxxReader(...).read()(云解析为 parse()),返回 List<Document>

文档类型组件类依赖适用场景
通用多格式TikaDocumentReaderspring-ai-tika-document-reader格式不确定时的首选,自动识别 pdf/word/ppt/html 等
纯文本TextReaderSpring AI 核心内置txt 等纯文本,可指定编码
JSONJsonReaderSpring AI 核心内置按字段 / JSON Pointer 抽取
MarkdownMarkdownDocumentReaderspring-ai-markdown-document-reader按标题/分割线/代码块结构化
PDF(按页)PdfDocumentReaderspring-ai-pdf-document-reader电子件 PDF,按页生成 Document
PDF(按段落)ParagraphPdfDocumentReaderSpring AI Alibaba电子件 PDF,按段落/目录切分,颗粒度更细
WordDocxDocumentReaderSpring AI Alibaba基于 Apache POI 解析 docx
HTMLJsoupDocumentReader / HtmlDocumentReaderspring-ai-jsoup-document-reader / AlibabaCSS 选择器精准抽取正文
云解析 / OCRDashScopeDocumentParserspring-ai-alibaba-starter-document-parser扫描件 OCR + 表格/公式/版面理解

⚠️ Spring AI Alibaba 的 Reader 分散在多个社区模块,具体 artifact 坐标随补丁版本可能调整,以 1.1.2 对应 BOM 为准,必要时用 mvn dependency:tree 核对。

下面精讲三个最有代表性的组件,其余格式用法一致,照总览表按需选用即可。

2.2 通用解析:TikaDocumentReader(首选)

不确定格式时,交给 Apache Tika 自动识别,一个类搞定所有格式

TikaDocumentReader reader = new TikaDocumentReader(new ClassPathResource("slides.pptx"));
List<Document> docs = reader.read();

只做「文本抽取」这一件事,内部流程如下:

  1. 格式探测:读取文件头(magic bytes)自动识别真实类型,pdf/word/ppt/html 等无需手动指定;
  2. 分发解析器:按识别出的类型交给 Apache Tika 内置解析器(PDFBox 解析 pdf、POI 解析 office、Jsoup 解析 html 等);
  3. 抽取文本:提取纯文本内容,附上文件名、格式等元数据,生成 Document

边界:Tika 只抽取文本层,不做 OCR、不做版面理解、也不保留表格结构。适合「有文字、版式简单」的文档;扫描件、复杂表格、公式仍需走云解析

2.3 电子件 PDF 解析

电子件 PDF 有文本层,本地直接抽取即可,按粒度分两种:

// 按页:每 1 页生成 1 个 Document,带 page_number 元数据
PdfDocumentReader pdfReader = new PdfDocumentReader("classpath:report.pdf",
        PdfDocumentReaderConfig.builder()
                .withPageTopMargin(0)
                .withPageBottomMargin(0)
                .withPagesPerDocument(1)
                .build());

// 按段落:按段落/目录结构切分,颗粒度更细,适合有目录的技术手册
ParagraphPdfDocumentReader paragraphReader = new ParagraphPdfDocumentReader(
        "classpath:spec.pdf",
        PdfDocumentReaderConfig.builder()
                .withReversedParagraphPosition(true)  // 适配不同 PDF 坐标体系
                .withPageTopMargin(0)
                .withPagesPerDocument(13)             // 限制单个 Document 承载页数
                .build());

选择:文档有清晰目录/段落 → ParagraphPdfDocumentReader;普通无结构文档 → PdfDocumentReader

2.4 云解析与 OCR:DashScopeDocumentParser

平台:阿里云百炼 DashScope 的文档智能解析服务,Spring AI Alibaba 原生支持DashScopeDocumentParser 就是它的官方封装。

所谓「云解析」,就是把文档上传到阿里云,由云端的文档智能模型完成解析,而不是在本机用 PDFBox/POI 这类本地库解析:

本地解析(Tika/PDFBox/POI)云解析(DashScope)
解析位置本机 JVM 内阿里云服务端
能力只抽取文本层文本 + OCR + 版面 + 表格/公式
能否处理扫描件不能能(云端 OCR 引擎)
成本 / 延迟免费、毫秒级、可离线付费、较慢(异步)、需联网

核心能力

  • 处理本地库搞不定的文档:扫描件、图片型 PDF、复杂表格、数学公式、多栏排版
  • 输出结构化结果(Markdown / JSON),文字、表格、版面信息都保留;
  • 支持长文档多页解析,一次提交整份文件,云端按页/版面拆解。

处理流程(异步)

提交文档(上传文件或给 URL)
   → 云端解析引擎处理(版面分析 / OCR / 表格结构化)
   → 客户端轮询任务结果
   → 返回结构化数据 → SDK 转成 Document
DashScopeDocumentTransformer transformer = new DashScopeDocumentTransformer(
        DashScopeDocumentTransformerConfig.builder()
                .detailLevel(DashScopeDocumentTransformerConfig.DetailLevel.HIGH) // HIGH 启用 OCR + 版面理解
                .build());

DashScopeDocumentParser parser = new DashScopeDocumentParser(transformer);

// 入口是 parse(),图片 / PDF 都能转成 List<Document>
List<Document> docs = parser.parse(new FileSystemResource("/data/扫描合同.pdf"));

detailLevel 两个档位:LOW 仅抽取文本(电子件够用);HIGH 追加 OCR + 版面理解(扫描件必需)。

适合场景:RAG 知识库、合同、报表、公文扫描件等对解析质量要求高的场景。它可直接替换 RAG 里的 loader,把「图片 / PDF → Document 对象」这一步交给云解析。

接入方式DashScopeDocumentParser 底层直接调用 DashScope 的多模态解析接口——图片直接上传,PDF 分页渲染成图片上传,云端完成 OCR + 版面理解后返回结构化结果,再由 SDK 封装成 Document

2.5 其他格式速览

以下组件用法与上面完全一致,按需选用即可:

// 纯文本
new TextReader("classpath:notes.txt", StandardCharsets.UTF_8).read();

// JSON:抽取指定字段
new JsonReader(new ClassPathResource("data.json"), "title", "content").read();

// Markdown:按标题/分割线结构化
new MarkdownDocumentReader(resource, MarkdownDocumentReaderConfig.builder()
        .withHorizontalRuleCreateDocument(true).build()).read();

// Word
new DocxDocumentReader(new ClassPathResource("合同.docx")).read();

// HTML:CSS 选择器精准抽取正文
new JsoupDocumentReader("https://example.com/news", JsoupDocumentReaderConfig.builder()
        .withSelector("article.content").build()).read();

3. 电子件与扫描件的解析策略

解析前先回答一个问题:这份 PDF 是电子件还是扫描件? 答案不同,解析路径完全不同。

3.1 两类文档的区别

维度电子件扫描件
本质文本层(文字可选中、复制)每页就是一张图片,文字印在像素里
解析方式本地直接抽文本必须 OCR 识别
成本 / 延迟免费、毫秒级、可离线付费、秒级、需联网
质量文字准确受分辨率、倾斜、污渍影响

3.2 程序怎么判断:文本层探测

核心思路:尝试抽取文本,文本量低于阈值即判定为扫描件

private static final int SCAN_THRESHOLD = 50;   // 每页平均字符数低于该值 → 扫描件

public boolean hasTextLayer(Resource pdf) {
    TikaDocumentReader reader = new TikaDocumentReader(pdf);
    int totalChars = reader.read().stream()
            .mapToInt(d -> d.getText().length())
            .sum();
    int pages = estimatePageCount(pdf);          // 用 PDFBox 读取页数
    return totalChars / Math.max(pages, 1) > SCAN_THRESHOLD;
}

阈值是启发式的:电子件每页通常数百到上千字符;扫描件抽出来的文本几乎为空。生产上可结合「是否含图片型页面」进一步判断。

3.3 电子件:本地抽取

电子件有文本层,本地直接抽取即可(组件用法见 2.3 节):

打开 PDF → 读取文本层 → 按页 / 按段落聚合 → 生成 Document
  • 文档有目录/段落 → ParagraphPdfDocumentReader(按段落,颗粒度更细)
  • 普通无结构 → PdfDocumentReader(按页)

3.4 扫描件:OCR 解析

扫描件没有文本层,必须走 OCR(组件用法见 2.4 节):

图片页 → 版面分析 → OCR 逐块识别 → 表格/公式结构化 → 按阅读顺序拼接 → 生成 Document

路线选择:文档专项 OCR vs 通用 VL 模型

维度文档专项 OCR(推荐)通用 VL 模型批量处理
输出结构Markdown 自带标题层级每页自由文本,无跨页关联
跨页表格自动关联,结构完整上一页下半截与下一页上半截无法关联
阅读顺序保持容易颠倒
开发量parse() 一行搞定分页 + 逐页请求 + prompt + 拼接 + 修复
成本 / 稳定性低 / 稳高 / 差

一句话:文档专项 OCR 是「产品级封装」,通用 VL 只是「看得懂图的原材料」。前者输出自带层级,清洗只做简单去噪、分片可利用标题元数据,RAG 效果更好

两条注意事项

  1. 大 PDF 先分页再解析:不要一次性把几百页图片塞给模型,容易超时、超限。
  2. 涉密文档严禁走公有云 OCR:敏感合同、涉密文档只能私有化部署开源 OCR 模型。

3.5 决策树(一图总结)

flowchart TD
    A[收到 PDF] --> B{文本层探测<br/>有文字吗}
    B -->|有 = 电子件| C[本地解析<br/>PdfDocumentReader / ParagraphPdfDocumentReader]
    B -->|无 = 扫描件| D[云端 OCR<br/>DashScopeDocumentParser HIGH]
    B -->|部分页无/不确定| D
    C --> E[生成 Document]
    D --> E

4. 文档解析工程实现

4.1 工程依赖(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.9</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>rag-document-parse</artifactId>
    <version>1.0.0</version>
    <name>rag-document-parse</name>
    <description>基于 Spring AI 的文档解析示例工程</description>

    <properties>
        <java.version>21</java.version>
        <spring-ai.version>1.1.2</spring-ai.version>
        <spring-ai-alibaba.version>1.1.2</spring-ai-alibaba.version>
    </properties>

    <dependencies>
        <!-- 文档云解析(含 OCR)与 Tika 多格式兜底 -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-document-parser</artifactId>
        </dependency>

        <!-- PDF 电子件本地解析(基于 PDFBox,按页抽取文本层) -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pdf-document-reader</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- Spring AI 依赖版本统一管理 -->
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Spring AI Alibaba 依赖版本统一管理 -->
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-bom</artifactId>
                <version>${spring-ai-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <!-- 打包为可执行 Jar -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

说明:spring-ai-alibaba-starter-document-parser 已包含云解析(OCR)与 Tika 兜底;如需其它格式(Word/Markdown/HTML),再按 2.1 总览表补充对应 reader 依赖。

4.2 应用配置(application.yml)

spring:
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}   # 云解析 OCR 用

云解析真正「必须指定」的只有两项,其余都有默认值:

配置是否必须说明
开通百炼「文档解析」服务必须阿里云控制台开通,否则调用报「未开通」
spring.ai.dashscope.api-key必须认证凭证,本地解析不需要
detailLevel(代码里)可选LOW 抽文本 / HIGH 加 OCR+版面,默认 LOW
endpoint / baseUrl一般不用SDK 内置官方地址,仅私有化/专有云/自定义网关时才改

对比:本地解析(Tika/PDFBox)零配置就能跑;云解析至少要「开通服务 + API Key」两项。

4.3 解析组件配置与自动分流

@Configuration
public class ParseConfig {

    /** 电子件:段落级解析 */
    @Bean
    public ParagraphPdfDocumentReader paragraphPdfReader() {
        return new ParagraphPdfDocumentReader("classpath:docs/*.pdf",
                PdfDocumentReaderConfig.builder()
                        .withPagesPerDocument(13)
                        .build());
    }

    /** 扫描件 / 复杂版面:云端 OCR */
    @Bean
    public DashScopeDocumentParser dashScopeParser() {
        DashScopeDocumentTransformer transformer = new DashScopeDocumentTransformer(
                DashScopeDocumentTransformerConfig.builder()
                        .detailLevel(DashScopeDocumentTransformerConfig.DetailLevel.HIGH)
                        .build());
        return new DashScopeDocumentParser(transformer);
    }
}
@Service
@RequiredArgsConstructor
public class DocumentParseService {

    private final ParagraphPdfDocumentReader paragraphPdfReader;
    private final DashScopeDocumentParser dashScopeParser;

    private static final int SCAN_THRESHOLD = 50; // 每页平均字符数阈值

    /** 按「电子件 / 扫描件」自动选择解析策略 */
    public List<Document> parsePdf(Resource pdf) {
        if (hasTextLayer(pdf)) {
            // 电子件:本地快速解析
            return paragraphPdfReader.read();
        }
        // 扫描件:云端 OCR + 版面理解
        return dashScopeParser.parse(pdf);
    }

    private boolean hasTextLayer(Resource pdf) {
        TikaDocumentReader reader = new TikaDocumentReader(pdf);
        int totalChars = reader.read().stream()
                .mapToInt(d -> d.getText().length())
                .sum();
        int pages = estimatePageCount(pdf);
        return totalChars / Math.max(pages, 1) > SCAN_THRESHOLD;
    }

    private int estimatePageCount(Resource pdf) {
        // 用 PDFBox 读取页数(示例略,或从元数据取)
        return 1;
    }
}

5. 最佳实践与常见问题

场景建议
规整电子版 PDFParagraphPdfDocumentReader / PdfDocumentReader,离线免费
扫描件 / 拍照件必须走 DashScopeDocumentParser(HIGH),本地无法 OCR
复杂表格/公式/双栏云解析(HIGH)版面理解最强
混合多格式目录TikaDocumentReader 兜底自动识别
成本敏感本地 Reader 处理电子件,仅扫描件/关键件走云

常见问题:

  • 把扫描件当电子件:PDFBox/Tika 对扫描件抽出的文本几乎为空,解析出的 Document 全是空文本。务必先做文本层探测
  • 编码问题TextReader 默认 UTF-8,中文 GBK 文件需显式指定 Charset
  • PDF 坐标体系不一致:不同 PDF 生成器坐标系可能颠倒,用 withReversedParagraphPosition(true) 修正。
  • 依赖版本漂移:Spring AI / Alibaba 迭代快,Reader 的 artifact 坐标偶有拆分合并,升级前先核对 BOM。
  • 云解析失败重试:DashScope 解析为异步任务,注意超时与幂等处理。

相关文章

精彩推荐