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

文章来源声明: 原文作者:学者猫头鹰; 来源站点:掘金; 原文链接:https://juejin.cn/post/7684478794612686848; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

覆盖 Spring AI 及 Alibaba 主流解析组件选型与电子件/扫描件分流策略,含依赖与配置示例,适合搭建 RAG 知识库时快速定位解析方案。

> 技术栈:**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 索引的第一步:

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

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

1.2 统一数据模型 Document

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

<span>public</span> <span>class</span> <span>Document</span> {
    <span>private</span> String id;                    <span>// 唯一标识</span>
    <span>private</span> Map<String, Object> metadata; <span>// 来源、文件名、页码、格式等</span>
    <span>private</span> String text;                  <span>// 抽取出的正文文本</span>
    <span>private</span> Media media;                  <span>// 图片/音频等多媒体(可选)</span>
}

<span>Document</span> <span>doc</span> <span>=</span> Document.builder()
        .id(UUID.randomUUID().toString())
        .text(<span>"这是一段被抽取出来的正文……"</span>)
        .metadata(Map.of(
                <span>"source"</span>, <span>"/data/report.pdf"</span>,
                <span>"file_name"</span>, <span>"report.pdf"</span>,
                <span>"page_number"</span>, <span>3</span>,
                <span>"file_type"</span>, <span>"application/pdf"</span>))
        .build();

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


  1. 文档解析组件

2.1 组件总览

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

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

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

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

2.2 通用解析:TikaDocumentReader(首选)

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

<span>TikaDocumentReader</span> <span>reader</span> <span>=</span> <span>new</span> <span>TikaDocumentReader</span>(<span>new</span> <span>ClassPathResource</span>(<span>"slides.pptx"</span>));
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 有文本层,本地直接抽取即可,按粒度分两种:

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

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

<span>DashScopeDocumentTransformer</span> <span>transformer</span> <span>=</span> <span>new</span> <span>DashScopeDocumentTransformer</span>(
        DashScopeDocumentTransformerConfig.builder()
                .detailLevel(DashScopeDocumentTransformerConfig.DetailLevel.HIGH) <span>// HIGH 启用 OCR + 版面理解</span>
                .build());

<span>DashScopeDocumentParser</span> <span>parser</span> <span>=</span> <span>new</span> <span>DashScopeDocumentParser</span>(transformer);

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

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

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

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

2.5 其他格式速览

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

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

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

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

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

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


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

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

3.1 两类文档的区别

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

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

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

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

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


  1. 文档解析工程实现

4.1 工程依赖(pom.xml)

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

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

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

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

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

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

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

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

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

4.2 应用配置(application.yml)

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

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

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

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

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

<span>@Configuration</span>
<span>public</span> <span>class</span> <span>ParseConfig</span> {

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

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

<span>@Service</span>
<span>@RequiredArgsConstructor</span>
<span>public</span> <span>class</span> <span>DocumentParseService</span> {

    <span>private</span> <span>final</span> ParagraphPdfDocumentReader paragraphPdfReader;
    <span>private</span> <span>final</span> DashScopeDocumentParser dashScopeParser;

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

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

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

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


  1. 最佳实践与常见问题

场景建议
规整电子版 PDF`ParagraphPdfDocumentReader` / `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 解析为异步任务,注意超时与幂等处理。