基于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)
代码说明
- 加载JSON:读取API返回的结构化数据
- 标题检测:
- 先统计正文平均字体大小,避免固定阈值适配性差的问题
- 结合字体大小、粗细、缩进判断标题层级
- 过滤非段落元素和过短文本,提升准确性
- 内容分块:
- 处理前置内容,避免标题前的正文丢失
- 按标题顺序划分内容块,保留列表项的格式
- 无标题时自动合并所有内容为一个块
优化建议
- 如果API返回
Color字段,可以加入颜色判断(比如标题用深色或特定颜色) - 对于多页PDF,可以结合
Page字段按页处理后再合并 - 可以通过
Path字段识别同一逻辑段落的分散文本块,合并后再判断标题
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

