- 文档加载与解析概述
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建议在解析时尽量填全(来源、页码、格式等),方便后续追溯。
- 文档解析组件
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 自动识别,一个类搞定所有格式:
<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();
它只做「文本抽取」这一件事,内部流程如下:
- 格式探测:读取文件头(magic bytes)自动识别真实类型,pdf/word/ppt/html 等无需手动指定;
- 分发解析器:按识别出的类型交给 Apache Tika 内置解析器(PDFBox 解析 pdf、POI 解析 office、Jsoup 解析 html 等);
- 抽取文本:提取纯文本内容,附上文件名、格式等元数据,生成
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();
- 电子件与扫描件的解析策略
解析前先回答一个问题:这份 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 效果更好。
两条注意事项:
- 大 PDF 先分页再解析:不要一次性把几百页图片塞给模型,容易超时、超限。
- 涉密文档严禁走公有云 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.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>;
}
}
- 最佳实践与常见问题
| 场景 | 建议 |
|---|---|
| 规整电子版 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 解析为异步任务,注意超时与幂等处理。
覆盖 Spring AI 及 Alibaba 主流解析组件选型与电子件/扫描件分流策略,含依赖与配置示例,适合搭建 RAG 知识库时快速定位解析方案。