基于Java实现文档章节结构智能提取方案

在数字化文档处理场景中,自动提取文档的章节结构是一项核心需求,尤其是面对无书签的PDF、纯文本文档等非结构化数据时,如何精准识别标题层级、构建树形章节结构成为关键。

本文将分享一套基于Java实现的文档章节结构智能提取方案,涵盖大模型辅助识别、文本规则分析、Unstructured JSON解析三种核心场景。

一、整体设计思路

本方案的核心类为DetectTitleChapters,整体提取逻辑遵循"优先大模型识别,兜底规则分析"的策略,同时支持从Unstructured输出的JSON数据中解析章节结构。核心流程如下:

  1. 大模型优先识别:利用LLM对文本进行目录解析,输出标准化JSON格式的章节结构;
  2. 规则兜底分析:若大模型识别失败,通过文本规则匹配、层级判定提取标题;
  3. 层级结构整理:对识别出的标题进行排序,构建父子层级关系;
  4. 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_idparent_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 优势

  1. 多策略兜底:大模型+规则双策略,兼顾识别精度和稳定性;
  2. 通用性强:支持纯文本、Unstructured JSON等多种输入格式;
  3. 层级精准:通过正则+栈结构,精准构建章节父子关系;
  4. 可配置化:标题正则、关键词、层级规则均可按需调整。

4.2 扩展方向

  1. 字体特征融合:若解析PDF时能获取字体大小、加粗等特征,可融入层级判定逻辑,提升精度;
  2. 自定义规则:支持配置化管理标题正则、关键词,适配不同行业文档(如法律、医疗、技术文档);
  3. 大模型优化:优化Prompt模板,加入更多格式约束,提升LLM输出稳定性;
  4. 性能优化:针对超长文本,实现分段处理、并行解析,提升处理效率。

五、总结

本文的文档章节提取方案,结合了大模型的智能解析能力和规则引擎的稳定性,能够高效处理各类非结构化文档的章节提取需求。

相关推荐
YueJoy.AI1 小时前
创业公司如何实现持续增长
人工智能·ai·语言模型
我爱cope3 小时前
【Agent智能体13 | 工具使用-什么是工具?】
人工智能·语言模型·职场和发展
tzc_fly4 小时前
ELF:连续扩散语言模型
人工智能·语言模型·自然语言处理
海兰6 小时前
【文字三国志:第五篇】天命重构,游戏前端UI设计
前端·人工智能·游戏·语言模型
星辰AI8 小时前
AI Agent 记忆系统设计与实现:让 AI 记住一切
人工智能·ai·语言模型
YueJoy.AI8 小时前
AI应用的质量保障:从测试到监控的完整流程
人工智能·ai·语言模型
我爱cope9 小时前
【Agent智能体14 | 工具使用-如何创建工具】
人工智能·语言模型·职场和发展
YueJoy.AI9 小时前
AI应用的性能优化:从分析到优化的完整流程
人工智能·ai·语言模型
星辰AI9 小时前
长期记忆存储:构建持久的 AI 记忆系统
人工智能·ai·语言模型