使用pdfplumber提取PDF文本时如何判断页面是否存在多栏布局
多栏PDF动态识别与文本提取解决方案
预处理:过滤页眉页脚等无关元素
基于pdfplumber的字符级坐标能力做初步过滤:
- 调用
page.chars获取页面所有字符的x0(左边界)、x1(右边界)、top(上边界)、bottom(下边界)、size(字号)属性 - 按垂直分布过滤页眉页脚:默认剔除页面上下各5%高度范围内的字符,也可结合页码、固定抬头等特征做精准匹配,仅保留正文区域字符用于后续判断
动态栏数识别逻辑
核心利用栏间空白远大于普通字间距的特征判断:
- 统计正文字符的平均字号,将正文字号的2倍设为栏间空白的最小阈值(可根据PDF类型微调,学术文献可设为3倍)
- 生成水平方向的字符覆盖密度:将页面水平方向按1px粒度拆分,统计每个x坐标点是否有字符覆盖
- 提取连续空白区间:长度达到阈值的连续空白即为栏间分隔带,分隔带的数量+1就是页面的实际栏数
- 异常修正:如果识别到的各栏宽度差超过30%,说明存在跨栏标题、浮动图片等元素,可先剔除大字号跨栏元素后重新做识别
分栏文本提取实现
识别栏数后按以下逻辑提取避免跨栏拼接:
- 取所有栏间分隔带的中点作为拆分坐标,将正文区域切分为对应数量的独立垂直栏块
- 调用
page.crop(bbox)方法裁剪出单个栏块的区域,按默认的从上到下顺序提取单栏文本 - 按从左到右的顺序拼接所有栏的提取结果,再做跨行合并、指定短语匹配即可
简化实现代码
import pdfplumber def extract_dynamic_layout_text(page, edge_cut_ratio=0.05, gap_factor=2): # 提取所有字符并过滤页眉页脚 all_chars = page.chars page_h = page.height valid_min_y = page_h * edge_cut_ratio valid_max_y = page_h * (1 - edge_cut_ratio) valid_chars = [c for c in all_chars if valid_min_y < c["top"] < valid_max_y] if not valid_chars: return "" # 计算栏间空白阈值 avg_font_size = sum(c["size"] for c in valid_chars) / len(valid_chars) min_gap_width = avg_font_size * gap_factor # 统计水平覆盖情况 min_x = min(c["x0"] for c in valid_chars) max_x = max(c["x1"] for c in valid_chars) x_cover = [False] * (int(max_x) + 2) for c in valid_chars: for x in range(int(c["x0"]), int(c["x1"]) + 1): x_cover[x] = True # 识别栏间分隔点 split_points = [min_x] current_gap = 0 for x in range(int(min_x), int(max_x) + 1): if not x_cover[x]: current_gap += 1 else: if current_gap >= min_gap_width: split_points.append(x - current_gap / 2) current_gap = 0 split_points.append(max_x) # 分栏提取文本并拼接 full_text = [] for i in range(len(split_points) - 1): col_bbox = (split_points[i], valid_min_y, split_points[i+1], valid_max_y) col_text = page.crop(col_bbox).extract_text() if col_text: full_text.append(col_text) return "\n".join(full_text)
注意事项
- 处理包含大量图片的PDF时,可先过滤掉图片区域的坐标再做栏数识别,避免干扰
- 阈值参数可根据目标PDF的共性特征调整,比如政府公文多为单栏、学术期刊多为2-3栏,可针对性优化阈值减少误判
内容的提问来源于stack exchange,提问作者StressedBoi69420
相关产品推荐
相关产品推荐

