在数字化文档处理场景中,自动提取文档的章节结构是一项核心需求,尤其是面对无书签的PDF、纯文本文档等非结构化数据时,如何精准识别标题层级、构建树形章节结构成为关键。
本文将分享一套基于Java实现的文档章节结构智能提取方案,涵盖大模型辅助识别、文本规则分析、Unstructured JSON解析三种核心场景。
一、整体设计思路
本方案的核心类为DetectTitleChapters,整体提取逻辑遵循"优先大模型识别,兜底规则分析"的策略,同时支持从Unstructured输出的JSON数据中解析章节结构。核心流程如下:
- 大模型优先识别:利用LLM对文本进行目录解析,输出标准化JSON格式的章节结构;
- 规则兜底分析:若大模型识别失败,通过文本规则匹配、层级判定提取标题;
- 层级结构整理:对识别出的标题进行排序,构建父子层级关系;
- Unstructured JSON解析:针对结构化输出的JSON,基于element_id和parent_id构建树形章节。
二、核心功能实现
2.1 依赖与基础配置
核心依赖包括FastJSON2(JSON解析)、Apache Commons Lang3(字符串处理)、Slf4j(日志)等。
2.2 大模型辅助章节提取
通过调用大模型API,传入定制化Prompt,让LLM解析文本并输出标准化的章节JSON结构,适合结构化程度低但文本规范的文档。
java
private List<Chapter> getLLMChapters(String input) {
List<Chapter> allTitles = new ArrayList<>();
if (input.length() > 100000) // 限制文本长度,避免大模型调用超限
return allTitles;
try {
// 获取大模型配置(模型编码、API地址、密钥)
KbLlmModelsCacheService.KbLlmModelDto model = kbLlmModelsCacheService.getChatModel().getModelMapById().get(2L);
// 定制化Prompt,约束输出格式为JSON
String prompt = "角色:你是一名文档解析助手,请对用户的输入文本进行解析,找到目录所在,输出文本的章节目录结构。\n" +
"输出格式:JSON格式:[{\"title\":\"标题\", \"level\":2, \"pageNumber\":7, \"subChapters\":[]}\n" +
"约束条件:输出原文";
JSONObject cfgLLM = new JSONObject();
cfgLLM.put("model", model.getModelCode());
cfgLLM.put("prompt", prompt);
cfgLLM.put("url", model.getApiUrl());
cfgLLM.put("authorization", model.getApiKey());
// 调用大模型API
JSONObject result = httpUtils.chatMessages(input, cfgLLM);
if (result.getBoolean("success")) {
String content = result.getString("content");
JSONArray jsonArray = httpUtils.formatListJSON(content);
allTitles = Chapter.parseChapter(jsonArray); // 解析JSON为Chapter对象
} else
log.info("大模型识别目录失败:" + result.toJSONString());
} catch (Exception e) {
e.printStackTrace();
}
return allTitles;
}
2.3 文本规则分析提取章节
当大模型识别失败时,通过文本规则匹配标题特征,判定层级并构建章节结构,核心分为"标题判定""层级识别""结构整理"三步。
2.3.1 标题判定规则
通过正则匹配、关键词过滤等方式,筛选出符合标题特征的文本行:
java
public static boolean isTitleText(String text) {
// 过滤空文本、制表符、特殊标点
if (text.trim().isEmpty() || text.matches("\t") || text.contains("\t") || text.contains("。") || text.endsWith("、"))
return false;
// 过滤纯数字/特殊符号文本
String cleanText = text.trim();
if (cleanText.matches("^[0-9\\p{Punct}]+$"))
return false;
// 过滤长度异常文本(2-50字符为合理标题长度)
if (text.length() > 50 || text.length() < 2) return false;
// 匹配常见标题格式(第X章、X.1、一、(一)等)
for (Pattern pattern : TITLE_PATTERNS) {
if (pattern.matcher(text).matches()) {
return true;
}
}
// 匹配标题关键词(概况、总则、前言等)
String[] titleKeywords = {"概况", "概述", "前言", "引言", "摘要", "总结", "结论", "总则"};
for (String keyword : titleKeywords) {
if (text.contains(keyword)) {
return true;
}
}
return false;
}
// 常见标题格式正则
private static final List<Pattern> TITLE_PATTERNS = Arrays.asList(
Pattern.compile("^第[零一二三四五六七八九十百千\\d]+章[\\s ]+(.+)$"),
Pattern.compile("^第[零一二三四五六七八九十百千\\d]+节[\\s ]+(.+)$"),
Pattern.compile("^\\d+[.\\.](?!.*%).*[^%]$"),
Pattern.compile("^[一二三四五六七八九十]、[\\s ]*(.+)$"),
Pattern.compile("^[\\((][一二三四五六七八九十][\\))][\\s\\t]*(.+)$"),
Pattern.compile("^\\d+[.\\.]\\d+[.\\.].[^%]$")
);
2.3.2 章节层级识别
根据标题格式判定层级(1级:第X章/一、;2级:(一)/X.1;其他为非标题):
java
public static int detectChapterLevel(String text) {
text = text.trim();
// 1级标题:第X章、中文数字+顿号
if (text.matches("^第[零一二三四五六七八九十百千\\d]+章.*") || text.matches("^[零一二三四五六七八九十百千\\d]+、.*")) {
return 1;
}
// 2级标题:(一)、X.X格式
if (text.matches("^[\\((][一二三四五六七八九十][\\))][\\s\\t]*(.+)$") || text.matches("^\\d+[.\\.]\\d+[.\\.].[^%]$")) {
return 2;
}
return 0; // 非标题
}
2.3.3 层级结构整理
利用栈结构构建父子层级关系,核心逻辑是"弹出栈中层级≥当前层级的节点,将当前节点加入父节点子列表":
java
public List<Chapter> organizeChaptersByLevel(List<Chapter> rawChapters) {
List<Chapter> rootChapters = new ArrayList<>();
Stack<Chapter> levelStack = new Stack<>(); // 栈记录当前层级父节点
for (Chapter chapter : rawChapters) {
int level = chapter.getLevel();
// 找到当前节点的父节点(栈顶层级 < 当前层级)
while (!levelStack.isEmpty() && levelStack.peek().getLevel() >= level) {
levelStack.pop();
}
// 根节点(无父节点)加入根列表,否则加入父节点子列表
if (levelStack.isEmpty()) {
rootChapters.add(chapter);
} else {
levelStack.peek().getSubChapters().add(chapter);
}
// 当前节点入栈,作为后续子节点的父节点
levelStack.push(chapter);
}
return rootChapters;
}
2.4 解析Unstructured JSON章节结构
针对Unstructured工具输出的JSON数据,基于element_id和parent_id构建树形章节:
java
public List<Chapter> extractChaptersFromUnstructured(JSONArray responseJson) {
List<Chapter> chapters = new ArrayList<>();
Map<String, Chapter> chapterMap = new HashMap<>(); // element_id -> Chapter
Map<String, List<Chapter>> parentChildrenMap = new HashMap<>(); // parent_id -> 子章节
// 第一步:提取所有Title类型元素
for (Object parsed : responseJson) {
JSONObject parsedObject = (JSONObject) parsed;
String type = parsedObject.getString("type");
if (StringUtils.isNotEmpty(type) && type.toLowerCase().contains("title")) {
String elementId = parsedObject.getString("element_id");
String text = parsedObject.getString("text");
JSONObject metadata = parsedObject.getJSONObject("metadata");
Integer depth = metadata != null ? metadata.getInteger("category_depth") : null;
String parentId = metadata != null ? metadata.getString("parent_id") : null;
int level = (depth != null ? depth : 0) + 1; // 层级计算(depth从0开始)
Chapter chapter = new Chapter(text, level, 0);
chapterMap.put(elementId, chapter);
// 建立父子映射
if (parentId != null && !parentId.isEmpty()) {
parentChildrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(chapter);
} else {
chapters.add(chapter); // 无父节点为根章节
}
}
}
// 第二步:将子章节挂载到父章节
for (Map.Entry<String, List<Chapter>> entry : parentChildrenMap.entrySet()) {
String parentId = entry.getKey();
List<Chapter> children = entry.getValue();
Chapter parent = chapterMap.get(parentId);
if (parent != null) {
parent.getSubChapters().addAll(children);
}
}
return chapters;
}
三、核心入口方法
对外提供统一的章节提取入口,自动选择提取策略:
java
public List<Chapter> extractChapters(String content) throws IOException {
log.info("开始大模型识别文本标题");
if (content == null || content.isEmpty())
return new ArrayList<>();
List<Chapter> llmChapters = getLLMChapters(content);
if (llmChapters.isEmpty()) {
log.info("未检测到大模型识别的文本标题,开始文本分析识别文本标题");
return getChaptersByAnalysis(content);
} else
return llmChapters;
}
四、方案优势与扩展方向
4.1 优势
- 多策略兜底:大模型+规则双策略,兼顾识别精度和稳定性;
- 通用性强:支持纯文本、Unstructured JSON等多种输入格式;
- 层级精准:通过正则+栈结构,精准构建章节父子关系;
- 可配置化:标题正则、关键词、层级规则均可按需调整。
4.2 扩展方向
- 字体特征融合:若解析PDF时能获取字体大小、加粗等特征,可融入层级判定逻辑,提升精度;
- 自定义规则:支持配置化管理标题正则、关键词,适配不同行业文档(如法律、医疗、技术文档);
- 大模型优化:优化Prompt模板,加入更多格式约束,提升LLM输出稳定性;
- 性能优化:针对超长文本,实现分段处理、并行解析,提升处理效率。
五、总结
本文的文档章节提取方案,结合了大模型的智能解析能力和规则引擎的稳定性,能够高效处理各类非结构化文档的章节提取需求。