如何使用 Python 从 PDF 书籍中提取目录(含章节与子标题)

千浩酱_2000

千浩酱_2000

2026-06-23

1172人浏览

原创

如何使用 Python 从 PDF 书籍中提取目录(含章节与子标题)

本文介绍两种实用方法:一是利用 PyMuPDF 的 get_toc() 提取嵌入式目录(需 PDF 含逻辑书签),二是基于字体大小统计的启发式算法识别章节标题,适用于无结构目录的扫描版或排版规范的 PDF。

本文介绍两种实用方法:一是利用 pymupdf 的 `get_toc()` 提取嵌入式目录(需 pdf 含逻辑书签),二是基于字体大小统计的启发式算法识别章节标题,适用于无结构目录的扫描版或排版规范的 pdf。

在实际 PDF 文档处理中,“目录”(Table of Contents, TOC)并非总是以显式结构存在。有些 PDF(尤其是由 LaTeX 或专业排版软件生成)会将目录作为可交互的书签(Outline)嵌入文档元数据中;而多数扫描件或简易导出 PDF 则仅含纯文本,目录信息完全依赖视觉排版(如字号、加粗、居中等)。因此,提取目录需分场景应对。

✅ 方法一:提取嵌入式书签目录(推荐优先尝试)

PyMuPDF(fitz)提供了 Document.get_toc() 方法,可直接读取 PDF 内置的逻辑目录树,返回列表形式的层级结构 [level, title, page_number, ...],其中 level 表示缩进层级(1=章,2=节,3=小节等)。

import fitz  # pip install PyMuPDF

def extract_embedded_toc(pdf_path):
    doc = fitz.open(pdf_path)
    toc = doc.get_toc()  # 返回 list of lists: [level, title, page]
    doc.close()
    return toc

# 示例调用
toc = extract_embedded_toc("book.pdf")
for level, title, page in toc:
    indent = "  " * (level - 1)
    print(f"{indent}• {title} → p.{page}")

⚠️ 注意事项:

  • 该方法仅对含 Outline(书签)的 PDF 有效,常见于 Adobe Acrobat 手动添加或 LaTeX hyperref/bookmark 宏包生成的 PDF;
  • 若返回空列表 [],说明 PDF 未嵌入目录结构,需转向方法二;
  • page 为从 0 开始的索引,实际页码通常需 +1(但 PDF 页面编号可能不连续,建议结合 doc[page].get_text() 验证)。

✅ 方法二:基于字体大小的启发式标题检测(适用无结构 PDF)

当 PDF 不含书签时,可利用文本渲染属性(主要是 font_size)进行统计分析:正文通常使用统一字号(如 10–12pt),而标题普遍显著更大(如 16–24pt),且常居中、加粗或独占一行。

Python Hardener
Python Hardener

通过完整的安全审查、代码整洁重构、健壮的错误处理、正确的日志记录以及完整的文档字符串覆盖,强化 Python 脚本。

下载

核心思路:

  1. 提取每页所有文本块及其字体大小(block["size"]);
  2. 统计全书出现频率最高的字号(即“正文基准字号”);
  3. 将显著大于基准字号(如 ≥ 基准 × 1.4)的文本块视为潜在标题;
  4. 按页码和位置排序,辅以规则过滤(如长度 > 3 字符、非页眉页脚区域)。
import fitz

def extract_heuristic_toc(pdf_path, min_title_length=5, size_ratio_threshold=1.4):
    doc = fitz.open(pdf_path)
    all_sizes = []
    page_texts = []

    # 第一遍:收集所有文本块字号,用于计算基准字号
    for page in doc:
        blocks = page.get_text("dict")["blocks"]
        for b in blocks:
            if "lines" in b:
                for line in b["lines"]:
                    for span in line["spans"]:
                        all_sizes.append(round(span["size"], 1))

    if not all_sizes:
        return []

    # 计算众数(最频繁字号)作为正文基准
    from collections import Counter
    mode_size = Counter(all_sizes).most_common(1)[0][0]
    threshold = mode_size * size_ratio_threshold

    # 第二遍:提取超大字号文本作为候选标题
    toc_candidates = []
    for page_num, page in enumerate(doc):
        blocks = page.get_text("dict")["blocks"]
        for b in blocks:
            if "lines" in b:
                for line in b["lines"]:
                    for span in line["spans"]:
                        if span["size"] >= threshold:
                            text = span["text"].strip()
                            if len(text) >= min_title_length and not text.isdigit():
                                toc_candidates.append({
                                    "text": text,
                                    "page": page_num + 1,  # 转为 1-based 页码
                                    "size": span["size"],
                                    "x": span["bbox"][0],   # 左侧坐标,可用于判断是否居中
                                    "y": span["bbox"][1]
                                })

    doc.close()
    # 简单去重(相邻页相同标题可能重复)
    seen = set()
    unique_toc = []
    for item in sorted(toc_candidates, key=lambda x: (x["page"], x["y"])):
        if item["text"] not in seen:
            seen.add(item["text"])
            unique_toc.append((item["text"], item["page"]))

    return unique_toc

# 使用示例
titles = extract_heuristic_toc("book.pdf")
for title, page in titles[:20]:  # 仅显示前20项
    print(f"[p.{page}] {title}")

? 提升准确率的小技巧:

  • 结合 span["flags"] 判断是否加粗(flags & 2**4 != 0 表示 bold);
  • 过滤掉出现在页面顶部/底部固定区域(如页眉页脚)的文本;
  • 对候选标题做简单 NLP 清洗(去除末尾页码、罗马数字编号等);
  • 若章节标题格式统一(如 “Chapter 1: Introduction”),可用正则预筛选。

总结

方法 适用 PDF 类型 准确率 是否需额外训练 推荐场景
get_toc() 含书签的 PDF(LaTeX/Adobe 生成) ★★★★★ 否 优先尝试,零配置即用
字号启发式 排版规范的印刷体 PDF(非扫描件) ★★★☆☆ 否 无书签但标题字号突出
OCR + Layout Analysis 扫描版 PDF ★★☆☆☆ 是(需 pdfplumber + layoutparser) 仅当上述均失效时考虑

建议流程:先运行 get_toc(),若返回非空结果则直接采用;否则启用启发式方法,并人工校验前几章结果——多数技术书籍和教材在此策略下可获得可靠目录结构。

Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1911

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

5344

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1849

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

32477

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3827

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3847

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1263

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

616

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2983

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程