You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Python从PDF文件中提取层级标题、子标题及段落内容

基于Python的PDF层级标题与对应段落提取实现

依赖选型

  • 常规可直接提取文本的PDF优先选用PyMuPDF(fitz),解析速度快,可精准获取文本块的位置、字号、字重属性,比通用PDF库的识别精度更适配标题层级判断场景
  • 文本排布不规则、跨列错位的PDF可搭配pdfplumber做二次文本块位置校验,减少提取错位问题

带层级编号文档的标题识别规则

针对示例中这类包含1. Abstract、2. Introduction、2.1. Background格式的学术类文档,不需要引入复杂的视觉识别模型,用正则规则即可完成标题候选定位:

  • 一级标题匹配逻辑:匹配行首为单组数字+点号+空格开头的独立行,对应类似3. Methods、4. References这类最高层级的标题
  • 二级标题匹配逻辑:匹配行首为两组数字(数字.数字)+点号+空格开头的独立行,对应类似2.1. Background、2.2. Reviews这类子层级标题
  • 可叠加字号校验规则:同类型标题的字号通常比正文大1-2号,能过滤掉正文中偶然出现数字编号的无效匹配项

结构化输出实现逻辑

核心处理流程

  1. 遍历PDF每一页的文本块,按从上到下、从左到右的阅读顺序排序拼接,形成连续的全文档文本流,过程中过滤掉页眉、页脚、页码这类固定位置的噪音内容
  2. 用提前定义的正则规则扫描全文,定位所有符合格式的标题条目在文本流中的位置
  3. 按照文档顺序整理两个独立列表:
    • headings列表:按出现顺序存放所有层级的标题文本,可选择保留原始的编号字符串,也可单独提取标题名称部分
    • paragraphs列表:和headings列表一一对应,每个位置存储相邻两个标题之间夹着的全部文本,也就是对应标题下的段落内容

核心实现代码示例:

import fitz
import re

# 定义不同层级标题的匹配规则
heading_regex = [
    re.compile(r'^(\d+\.\s+[^\d\n]+)$'),  # 一级标题
    re.compile(r'^(\d+\.\d+\.\s+[^\d\n]+)$')  # 二级标题
]

def parse_target_pdf(pdf_path):
    doc = fitz.open(pdf_path)
    full_text = ""
    # 逐页提取文本块
    for page in doc:
        blocks = page.get_text("blocks")
        # 按块的坐标排序,避免跨列顺序错乱
        for block in sorted(blocks, key=lambda x: (x[1], x[0])):
            block_text = block[4].strip()
            # 过滤过短的噪音内容
            if len(block_text) > 2:
                full_text += block_text + "\n"
    
    # 定位所有标题的位置
    headings = []
    heading_positions = []
    for line in full_text.split("\n"):
        line = line.strip()
        for pattern in heading_regex:
            match_result = pattern.match(line)
            if match_result:
                headings.append(match_result.group(1))
                heading_positions.append(full_text.find(line))
                break
    
    # 提取每个标题对应的段落
    paragraphs = []
    for index, pos in enumerate(heading_positions):
        # 当前段落到下一个标题开始位置的区间就是对应段落
        para_end = heading_positions[index+1] if index + 1 < len(heading_positions) else len(full_text)
        # 去掉标题本身的文本,只保留段落内容
        para_content = full_text[pos + len(headings[index]):para_end].strip()
        paragraphs.append(para_content)
    
    return headings, paragraphs

适配调整说明

  • 跨页出现的标题需要在文本拼接时做断行拼接,避免因分页切断标题导致正则匹配失败
  • 如果文档的编号格式不统一(比如部分二级标题写作2.1没有末尾点号),只需要同步调整对应的正则匹配规则即可
  • 没有标准编号的文档,可以把加粗字重、固定缩进位置作为补充识别条件,和编号规则搭配提升识别准确率

内容的提问来源于stack exchange,提问作者KentAnderson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.23 04:15:07