乐于分享
好东西不私藏

Spring AI 文档处理全指南:从 PDF 到 Markdown,一文掌握所有 DocumentReader

Spring AI 文档处理全指南:从 PDF 到 Markdown,一文掌握所有 DocumentReader

上一期我们搭好了向量数据库,但数据还躺在各种文件里。今天我们就来系统学习 Spring AI 的 ETL 管道,彻底搞定文档的“读”与“拆”。

在《Spring AI 向量数据库实战:给大模型装上“语义搜索引擎”》中,我们用 SimpleVectorStore 和 PGVector 完成了数据的存储与检索。但有一个前置步骤被我们刻意跳过了:

数据是怎么从 PDF、Word、HTML、JSON 这些杂七杂八的格式,变成向量库能“消化”的标准 Document 对象的?

答案就是 ETL 管道(Extract, Transform, Load)。今天这篇文章,我们就聚焦 ETL 的第一个环节——DocumentReader,把 Spring AI 支持的所有文档读取器一网打尽。


什么是 ETL 管道?

在 RAG(检索增强生成)应用场景中,数据处理流程遵循经典的 ETL 模型:

阶段
组件
作用
Extract(提取) DocumentReader
从 PDF、Word、JSON 等原始文件中读取文本
Transform(转换) DocumentTransformer
切分长文本、清洗数据、添加元数据
Load(加载) DocumentWriter
调用 Embedding 模型转向量,存入向量库

Spring AI 为这三个环节提供了统一的接口抽象:

  • DocumentReader 实现 Supplier<List<Document>>
  • DocumentTransformer 实现 Function<List<Document>, List<Document>>
  • DocumentWriter 实现 Consumer<List<Document>>

这种函数式设计让你可以像搭积木一样,把 reader → transformer → writer 链式组合起来。

今天我们先吃透 DocumentReader


JSON 文档读取:JsonReader

很多企业的数据是以 JSON 格式存储的,比如产品目录、API 返回结果。JsonReader 能帮你精准提取 JSON 中的关键字段。

1. 示例数据

[  {"id""bike-001","name""山地探险者 X1","brand""拓路者","price"1299.99,"description""一款坚固的全避震山地自行车,专为越野小径和复杂地形下坡而设计。","content""车架:铝合金材质,29英寸轮组。前避震行程140毫米,后避震行程130毫米。配备液压碟刹系统。"  },  {"id""bike-002","name""城市通勤专家版","brand""优行","price"899.50,"description""轻量化混合动力自行车,非常适合日常通勤和城市休闲骑行。","content""车架:铬钼钢材质,700c轮径。禧玛诺21速变速系统,搭配防刺穿轮胎。"  },  {"id""bike-003","name""公路竞速 3000","brand""速行者","price"2450.00,"description""高性能碳纤维公路自行车,适用于竞技比赛和快速团队骑行。","content""车架:碳纤维一体成型。禧玛诺 Ultegra 套件,气动碳纤维轮组,22速变速。"  }]

2. 代码实现

import org.springframework.ai.document.Document;import org.springframework.ai.reader.JsonReader;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.Resource;import org.springframework.stereotype.Component;import java.util.List;@ComponentpublicclassMyJsonReader{// 注意此处resource是org.springframework.core.io包下的Resource类型,privatefinal Resource resource;    MyJsonReader(@Value("classpath:bikes.json") Resource resource) { // 3. 构造函数注入资源this.resource = resource;    }/**     * 加载 JSON 并转换为文档     * @return     */public List<Document> loadJsonAsDocuments(){// JsonReader 是 Spring AI 提供的工具类//    - 第一个参数:要读取的资源//    - 后续参数:指定 JSON 对象中哪些字段拼接为文档的“内容”(content)        JsonReader jsonReader = new JsonReader(this.resource, "description""content");// 返回 Document 列表        System.out.println(jsonReader.get("/0"));return jsonReader.get();    }}

3. 进阶技巧

  • JSON Pointer 定位:如果 JSON 层级很深,可以用 get(String pointer) 只读取特定部分。

    jsonReader.get("/0")
  • 自定义元数据生成器:通过 JsonMetadataGenerator 将 JSON 中的其他字段(如 pricebrand)存入 Document 的元数据,方便后续过滤。


纯文本读取:TextReader

最简单也最常用。适合处理 .txt.log 等纯文本文件。

import org.springframework.ai.document.Document;import org.springframework.ai.reader.TextReader;import org.springframework.ai.transformer.splitter.TokenTextSplitter;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.Resource;import org.springframework.stereotype.Component;import java.util.List;@ComponentpublicclassMyTextReader{privatefinal Resource resource;    MyTextReader(@Value("classpath:text-source.txt") Resource resource) {this.resource = resource;    }public List<Document> loadText(){// 创建文本读取器        TextReader textReader = new TextReader(this.resource);// 向文档的自定义元数据中添加一个键值对        textReader.getCustomMetadata().put("filename""text.txt");// 执行读取,返回 Document 列表(默认会将整个文件内容作为一个 Document)        List<Document> documents = textReader.read();// 对 Document 列表进行分词        List<Document> splitDocuments = new TokenTextSplitter().apply(documents);return splitDocuments;    }}

注意TextReader 会把整个文件读成一个 Document。如果文件很大,需要配合 TokenTextSplitter 进行切分(我们下一篇文章会详细讲)。


HTML 文档读取:JsoupDocumentReader

网页抓取是 RAG 的重要数据来源。Spring AI 基于 Jsoup 提供了强大的 HTML 解析能力。

引入依赖

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-jsoup-document-reader</artifactId></dependency>

示例 HTML

<article><h2>什么是 Spring AI?</h2><p>Spring AI 是一个用于简化 AI 应用开发的框架...</p><p>了解更多信息,请访问 <ahref="https://spring.io">Spring AI 官网</a></p></article>

代码实现

import org.springframework.ai.document.Document;import org.springframework.ai.reader.jsoup.JsoupDocumentReader;import org.springframework.ai.reader.jsoup.config.JsoupDocumentReaderConfig;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.Resource;import org.springframework.stereotype.Component;import java.util.List;@ComponentpublicclassMyHtmlReader{privatefinal Resource resource;    MyHtmlReader(@Value("classpath:/test.html") Resource resource) {this.resource = resource;    }public List<Document> loadHtml(){// 构建 HTML 解析配置        JsoupDocumentReaderConfig config = JsoupDocumentReaderConfig.builder()// 只提取 <article> 标签内的 <p> 段落            .selector("article p")// 指定文件编码(这里使用 UTF-8)            .charset("UTF-8")// 在元数据中包含页面内的所有链接 URL            .includeLinkUrls(true)// 提取 HTML <meta> 中的 author 和 date            .metadataTags(List.of("author""date"))// 添加自定义元数据            .additionalMetadata("source""my-page.html")            .build();// 创建读取器并读取        JsoupDocumentReader reader = new JsoupDocumentReader(this.resource, config);return reader.get();    }}

关键配置项解读

配置项
作用
selector
CSS 选择器,精确控制提取哪些元素
groupByElement
为每个匹配元素创建一个独立的 Document
allElements
忽略选择器,直接提取整个 <body> 文本
includeLinkUrls
将页面中的绝对链接 URL 存入元数据

Markdown 文档读取:MarkdownDocumentReader

技术文档、README 通常都是 Markdown 格式。Spring AI 能智能识别标题、代码块、引用块,并可按需拆分。

引入依赖

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-markdown-document-reader</artifactId></dependency>

代码实现

import org.springframework.ai.document.Document;import org.springframework.ai.reader.markdown.MarkdownDocumentReader;import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.Resource;import org.springframework.stereotype.Component;import java.util.List;@ComponentpublicclassMyMarkdownReader{privatefinal Resource resource;// 构造函数注入,加载类路径下的 code.md 文件    MyMarkdownReader(@Value("classpath:test.md") Resource resource) {this.resource = resource;    }public List<Document> loadMarkdown(){// 构建 Markdown 解析配置        MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()// 遇到水平分割线(---)时,分割为新文档            .withHorizontalRuleCreateDocument(true)// 不将代码块内容纳入文档内容            .withIncludeCodeBlock(false)// 不将引用块内容纳入文档内容            .withIncludeBlockquote(false)// 为每个文档添加自定义元数据            .withAdditionalMetadata("filename""code.md")            .build();// 创建读取器并读取        MarkdownDocumentReader reader = new MarkdownDocumentReader(this.resource, config);return reader.get();    }}

独特能力

  • Front Matter 自动转为元数据:Markdown 头部的 YAML 信息(如 titledate)会自动存入 Document 的元数据。
  • 按标题层级拆分:未来版本可能支持按 ### 标题切分。

PDF 文档读取:两种模式任选

PDF 是企业文档的“重灾区”,Spring AI 提供了两种精细度的读取器。

1. 按页读取:PagePdfDocumentReader

每页生成一个 Document,适合页数不多、每页内容独立的场景。

引入依赖

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pdf-document-reader</artifactId></dependency>

代码实现

import org.springframework.ai.document.Document;import org.springframework.ai.reader.ExtractedTextFormatter;import org.springframework.ai.reader.pdf.PagePdfDocumentReader;import org.springframework.ai.reader.pdf.ParagraphPdfDocumentReader;import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;import org.springframework.stereotype.Component;import java.util.List;@ComponentpublicclassMyPagePdfDocumentReader{public List<Document> getDocsFromPdf(){//创建 PDF 读取器  PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(// 指定类路径下的 PDF 文件"classpath:/智力题.pdf",// 构建配置对象    PdfDocumentReaderConfig.builder()// 设置页面顶部边距裁剪      .withPageTopMargin(0)      .withPageExtractedTextFormatter(        ExtractedTextFormatter.builder()// 删除提取文本的前 N 行          .withNumberOfTopTextLinesToDelete(0)          .build()      )// 每个 Document 包含的 PDF 页数      .withPagesPerDocument(1)      .build()  );// 执行读取,返回 Document 列表return pdfReader.read(); }}

2. 按段落读取:ParagraphPdfDocumentReader

利用 PDF 内嵌的目录(Catalog)信息,按章节/段落切分。注意:并非所有 PDF 都包含目录结构

import org.springframework.ai.document.Document;import org.springframework.ai.reader.ExtractedTextFormatter;import org.springframework.ai.reader.pdf.PagePdfDocumentReader;import org.springframework.ai.reader.pdf.ParagraphPdfDocumentReader;import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;import org.springframework.stereotype.Component;import java.util.List;@ComponentpublicclassMyPagePdfDocumentReader{public List<Document> getDocsFromPdfWithCatalog(){// 创建段落级 PDF 读取器  ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader(// 类路径下的 PDF 文件"classpath:/智力题.pdf",    PdfDocumentReaderConfig.builder()// 不裁剪页面顶部边距      .withPageTopMargin(0)      .withPageExtractedTextFormatter(        ExtractedTextFormatter.builder()// 不删除提取文本的前 N 行          .withNumberOfTopTextLinesToDelete(0)          .build()      )// 每个逻辑单位处理 1 页      .withPagesPerDocument(1)      .build()  );return pdfReader.read(); }}

万能读取器:TikaDocumentReader

如果你不想为每种格式单独引入依赖,或者需要处理 Word (.docx)、PPT (.pptx)、Excel (.xlsx) 等办公文档,TikaDocumentReader 是你的最佳选择。它基于 Apache Tika,支持超过 1000 种文件格式

引入依赖

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-tika-document-reader</artifactId></dependency>

代码实现

@ComponentpublicclassMyTikaDocumentReader{privatefinal Resource resource;    MyTikaDocumentReader(@Value("classpath:/孩子为什么越来越难教.docx") Resource resource) {this.resource = resource;    }public List<Document> loadText(){        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(this.resource);// 一行代码搞定 Word、PDF、PPT、HTML...return tikaDocumentReader.read();    }}

格式支持一览

文件类型
是否支持
PDF
Word (.doc, .docx)
Excel (.xls, .xlsx)
PowerPoint (.ppt, .pptx)
HTML, XML
电子书 (.epub)
压缩包 (.zip, .tar)
✅ 可提取内部文件

总结与最佳实践

今天我们系统学习了 Spring AI 提供的 7 种 DocumentReader,帮你打通了从原始文件到标准 Document 对象的“最后一公里”。

快速选型指南

你的文件类型
推荐 Reader
备注
JSON 数据
JsonReader
可指定字段拼接
纯文本
TextReader
最简单
网页 HTML
JsoupDocumentReader
支持 CSS 选择器
技术文档 .md
MarkdownDocumentReader
识别 Front Matter
PDF(页数少)
PagePdfDocumentReader
按页拆分
PDF(有目录)
ParagraphPdfDocumentReader
按章节拆分
混合/未知格式 TikaDocumentReader 万能,推荐首选

最佳实践建议

  1. **优先使用 TikaDocumentReader**:除非有特殊需求(如需要精细控制 HTML 选择器),否则一个 Tika 全搞定。
  2. Reader 只负责“读”,不负责“切”:读完的 Document 可能很大,下一步必须接 TokenTextSplitter 进行切分(这也是我们下一篇文章的重点)。
  3. 善用元数据:读取时尽量把文件名、页码、章节标题等信息存入元数据,后续检索时可以按这些条件过滤。

如果觉得文章对你有帮助,欢迎 点赞、在看、转发 三连支持!我们下期见。