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

基于Adobe PDF Extract Python API的PDF标题检测与上下文分块问询

基于Adobe PDF Extract API的标题检测与内容分块实现

核心思路

Adobe PDF Extract API返回的文本元素包含样式(字体大小、粗细)、坐标等元数据,我们可以通过样式特征+位置特征识别标题,再基于标题对内容进行上下文分块。

关键判断规则

标题通常具备以下特征(可根据目标PDF调整):

  • 字体大小显著大于正文(一般是正文的1.1-1.6倍以上)
  • 字体加粗(FontWeight ≥ 700)
  • 左边距缩进更小(或与主标题层级匹配的缩进)
  • 多为独立段落(ElementType为Paragraph)

完整实现代码

import json

def load_pdf_extract_json(file_path):
    """加载Adobe PDF Extract API返回的JSON文件"""
    with open(file_path, 'r', encoding='utf-8') as f:
        return json.load(f)

def detect_headings(elements):
    """从文本元素中检测标题及层级"""
    # 先计算正文的平均字体大小(过滤掉疑似标题的大字体)
    body_font_sizes = []
    for elem in elements:
        if elem.get('ElementType') != 'Paragraph':
            continue
        style = elem.get('Style', {})
        font_size = style.get('FontSize', 0)
        # 先排除明显偏大的字体,统计正文平均大小
        if 10 <= font_size <= 14:
            body_font_sizes.append(font_size)
    avg_body_size = sum(body_font_sizes) / len(body_font_sizes) if body_font_sizes else 12

    headings = []
    for idx, elem in enumerate(elements):
        # 跳过空文本或非段落元素
        if not elem.get('Text') or elem.get('ElementType') != 'Paragraph':
            continue
        text = elem['Text'].strip()
        # 过短的文本大概率不是标题(可调整阈值)
        if len(text) < 2:
            continue
        
        style = elem.get('Style', {})
        font_size = style.get('FontSize', 0)
        font_weight = style.get('FontWeight', 400)
        bounds = elem.get('Bounds', [0, 0, 0, 0])
        left_margin = bounds[0]

        # 基于字体大小和粗细判断标题层级
        heading_level = 0
        if font_size >= avg_body_size * 1.6 and font_weight >= 700:
            heading_level = 1
        elif font_size >= avg_body_size * 1.3 and font_weight >= 700:
            heading_level = 2
        elif font_size >= avg_body_size * 1.1 and font_weight >= 700:
            heading_level = 3
        # 补充缩进判断:子标题通常有更大的左边距
        elif font_size == avg_body_size and font_weight >=700 and left_margin > 50:
            heading_level = 3

        if heading_level > 0:
            headings.append({
                'text': text,
                'level': heading_level,
                'index': idx
            })
    return headings

def chunk_content(elements, headings):
    """基于标题对内容进行分块"""
    chunks = []
    # 按标题出现顺序排序
    sorted_headings = sorted(headings, key=lambda x: x['index'])

    # 处理第一个标题之前的前置内容
    if sorted_headings:
        first_heading_idx = sorted_headings[0]['index']
        if first_heading_idx > 0:
            pre_content = []
            for elem in elements[:first_heading_idx]:
                if elem.get('Text'):
                    pre_content.append(elem['Text'].strip())
            pre_content_str = ' '.join(pre_content)
            if pre_content_str:
                chunks.append({
                    'title': '前置内容',
                    'content': pre_content_str
                })
        
        # 处理每个标题对应的内容块
        for i in range(len(sorted_headings)):
            current_heading = sorted_headings[i]
            start_idx = current_heading['index'] + 1
            # 下一个标题的位置作为当前块的结束
            end_idx = sorted_headings[i+1]['index'] if i < len(sorted_headings)-1 else len(elements)
            
            # 提取当前块的内容(保留列表项格式)
            chunk_content = []
            for elem in elements[start_idx:end_idx]:
                if elem.get('Text'):
                    text = elem['Text'].strip()
                    # 如果是列表项,添加前缀标识
                    if elem.get('ElementType') == 'ListItem':
                        chunk_content.append(f"- {text}")
                    else:
                        chunk_content.append(text)
            chunks.append({
                'title': current_heading['text'],
                'level': current_heading['level'],
                'content': '\n'.join(chunk_content)
            })
    else:
        # 无标题时,将所有内容作为一个块
        full_content = []
        for elem in elements:
            if elem.get('Text'):
                full_content.append(elem['Text'].strip())
        chunks.append({
            'title': '无标题内容',
            'content': '\n'.join(full_content)
        })
    return chunks

if __name__ == '__main__':
    # 替换为你的JSON文件路径
    pdf_json = load_pdf_extract_json('pdf_extract_output.json')
    text_elements = pdf_json.get('elements', [])
    
    detected_headings = detect_headings(text_elements)
    content_chunks = chunk_content(text_elements, detected_headings)
    
    # 打印分块结果
    for chunk in content_chunks:
        print(f"{'#' * chunk['level']} {chunk['title']}")
        print(chunk['content'])
        print("-" * 50)

代码说明

  1. 加载JSON:读取API返回的结构化数据
  2. 标题检测:
    • 先统计正文平均字体大小,避免固定阈值适配性差的问题
    • 结合字体大小、粗细、缩进判断标题层级
    • 过滤非段落元素和过短文本,提升准确性
  3. 内容分块:
    • 处理前置内容,避免标题前的正文丢失
    • 按标题顺序划分内容块,保留列表项的格式
    • 无标题时自动合并所有内容为一个块

优化建议

  • 如果API返回Color字段,可以加入颜色判断(比如标题用深色或特定颜色)
  • 对于多页PDF,可以结合Page字段按页处理后再合并
  • 可以通过Path字段识别同一逻辑段落的分散文本块,合并后再判断标题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 13:33:18