这是文档结构识别(Document Structure Analysis),判断当前页是否是目录页,需要结合文本特征 + 版面特征 + 统计特征进行综合判断。

整体流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
扫描PDF

PaddleOCR

特征提取

规则引擎(高精度)

目录标题检测
+
目录行正则匹配
+
右侧页码聚类
+
跨页连续性判断

XGBoost(二分类)

低置信度交给LLM

输出目录页范围

正文抽取

其中XGBoost只负责处理“规则难以覆盖的边界情况”,而不是完全替代规则。

目录页的典型特征

典型的中文目录页:

1
2
3
4
5
6
7
8
目录

第一章 总则.......................1
第二章 组织架构...................8
第三章 业务流程..................15
3.1 用户管理......................16
3.2 权限控制......................18
附录A 配置说明....................88

目录页通常具有以下特点:

含有目录标题

1
2
3
4
5
目录
CONTENTS
目 录
Contents
Table of Contents

存在大量“标题 + 页码”模式

1
2
3
第一章 XXXXX .......... 12
2.3 XXXXX ............. 25
附录A XXXXX ........... 88

可以抽象为:

1
2
3
4
^(第[一二三四五六七八九十百]+[章节篇]|[0-9]+(\.[0-9]+)*|附录)
.*?
([·..…]{2,})
\s*\d+$

匹配:

1
2
3
第一章 系统概述........1
2.1 数据处理..........15
附录A................98

点线密集

目录中常见:

1
2
3
...................
…………………
··················

统计点线数量:

1
2
dot_count = text.count(".")
ellipsis_count = text.count("…")

如果:

1
(dot_count + ellipsis_count) / 文本长度 > 0.05

则该页为目录页的概率较高。

页码密度高

正文一页一般只存在一个页码,而目录页:

1
2
3
4
……
第一章……1
第二章……8
第三章……15

可能存在

1
2
20 行
15 个数字

定义:

1
page_number_density = 页码数量 / 行数

判断

1
2
3
正文:<0.1

目录:>0.4

OCR后的判定流程

OCR结果:

1
2
3
4
5
6
[
{"text":"目录"},
{"text":"第一章 总则..........1"},
{"text":"第二章 架构..........8"},
...
]

提取特征:

1
2
3
4
5
6
features = {
"has_title":False,
"toc_line_ratio":0,
"dot_ratio":0,
"page_number_ratio":0
}

目录行识别

定义目录行:

1
2
3
4
5
6
7
8
9
def is_toc_line(text):
patterns = [
r'^第[一二三四五六七八九十百]+[章节篇].*\d+$',
r'^\d+(\.\d+)*.*\d+$',
r'^附录.*\d+$'
]

return any(re.match(p,text)
for p in patterns)

统计:

1
2
3
4
5
6
toc_lines = sum(
is_toc_line(line)
for line in lines
)

ratio = toc_lines / len(lines)

经验值:

1
ratio > 0.3

高度怀疑目录页。

版面特征分析

目录通常具有:

1
2
3
左侧:标题

右侧:页码

例如:

1
2
第一章 总则...............1
第二章 组织架构...........8

OCR坐标:

1
2
3
4
5
6
7
8
9
{
"text":"第一章 总则",
"x":50
}

{
"text":"1",
"x":850
}

统计:

左侧文本聚类

1
left_x_std < threshold

右侧数字聚类

1
right_x_std < threshold

如果:

1
大部分数字都在页面右边缘

目录概率增加。

跨页连续目录判断

目录通常跨越多页:

1
2
3
4
5
目录 第1页

目录 第2页

目录 第3页

特征:

1
2
3
4
5
6
7
页码递增:

1
8
15
21
……

且:

1
2
当前页和下一页
都满足目录特征

可连续扩展:

1
2
while next_page.is_toc():
mark_toc()

XGBoost目录页分类器

定义分类目标

二分类问题:

1
2
0 = 非目录页
1 = 目录页

训练样本:

1
2
目录页      300页
非目录页 1000页

特征设计

  • 目录标题特征
    判断是否出现
    1
    2
    3
    4
    目录
    目 录
    contents
    table of contents
  • 目录行占比
    目录行:
    1
    2
    第一章 总则..........1
    第二章 架构..........8
    计算目录行的占比:
    1
    toc_ratio = toc_line_count / line_count
  • 点线密度
    统计:
    1
    2
    ..........
    ………………
    的占比:
    1
    dot_density = total_dot_count / total_character_count
  • 数字密度
    目录页存在很多页码数字,计算页码占比:
    1
    digit_density = digit_count / character_count
  • 右侧数字聚集度
    目录页的页码数字集中在页面右侧,计算:
    1
    page_number_x_std
    数字越小,越像目录
  • 目录行长度方差
    目录的长度较一致:
    1
    2
    3
    第一章.........1
    第二章.........15
    第三章.........100
    而正文长度差异较大,计算:
    1
    line_length_std
  • 标题比例
    目录中经常出现各级标题:
    1
    2
    3
    4
    5
    1
    1.1
    1.2
    2
    2.1
    匹配:
    1
    ^\d+(\.\d+)*$
    统计标题占比:
    1
    heading_ratio = heading_count / line_count
  • 顶部空白比例
    目录页第一页顶部通常较空:
    1
    2
    3
    目录

    第一章……
    计算OCR坐标:
    1
    top_margin = first_text_y / page_height
    最终获得8个特征
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [
    has_catalog,
    toc_ratio,
    dot_density,
    digit_density,
    page_number_x_std,
    line_length_std,
    heading_ratio,
    top_margin
    ]

    XGBoost训练

最后更新: 2026年09月10日 11:10