技术栈: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 索引的第一步:
swift
原始文件 → 解析 → List<Document>
本文只讲**「解析」这一步**:框架提供了哪些解析组件、怎么调用,以及电子件 / 扫描件分别用什么策略。
1.2 统一数据模型 Document
不管原始文件是 PDF、Word 还是图片,所有解析组件最终都输出同一种对象 ------ Document:
java
public class Document {
private String id; // 唯一标识
private Map<String, Object> metadata; // 来源、文件名、页码、格式等
private String text; // 抽取出的正文文本
private Media media; // 图片/音频等多媒体(可选)
}
java
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>:
| 文档类型 | 组件类 | 依赖 | 适用场景 |
|---|---|---|---|
| 通用多格式 | 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 / Alibaba |
CSS 选择器精准抽取正文 |
| 云解析 / 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 自动识别,一个类搞定所有格式:
java
TikaDocumentReader reader = new TikaDocumentReader(new ClassPathResource("slides.pptx"));
List<Document> docs = reader.read();
它只做「文本抽取」这一件事,内部流程如下:
- 格式探测:读取文件头(magic bytes)自动识别真实类型,pdf/word/ppt/html 等无需手动指定;
- 分发解析器:按识别出的类型交给 Apache Tika 内置解析器(PDFBox 解析 pdf、POI 解析 office、Jsoup 解析 html 等);
- 抽取文本 :提取纯文本内容,附上文件名、格式等元数据,生成
Document。
边界:Tika 只抽取文本层 ,不做 OCR、不做版面理解、也不保留表格结构。适合「有文字、版式简单」的文档;扫描件、复杂表格、公式仍需走云解析。
2.3 电子件 PDF 解析
电子件 PDF 有文本层,本地直接抽取即可,按粒度分两种:
java
// 按页:每 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
java
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 其他格式速览
以下组件用法与上面完全一致,按需选用即可:
java
// 纯文本
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 程序怎么判断:文本层探测
核心思路:尝试抽取文本,文本量低于阈值即判定为扫描件。
java
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 效果更好。
两条注意事项:
- 大 PDF 先分页再解析:不要一次性把几百页图片塞给模型,容易超时、超限。
- 涉密文档严禁走公有云 OCR:敏感合同、涉密文档只能私有化部署开源 OCR 模型。
3.5 决策树(一图总结)
4. 文档解析工程实现
4.1 工程依赖(pom.xml)
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)
yaml
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 解析组件配置与自动分流
java
@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);
}
}
java
@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. 最佳实践与常见问题
| 场景 | 建议 |
|---|---|
| 规整电子版 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 解析为异步任务,注意超时与幂等处理。