基于Python提取多格式PDF特定文本的实现方案咨询
多格式PDF定向文本提取落地方案
针对多排版PDF定向提取适配难、规则不统一的问题,直接采用带格式元数据提取+可配置规则集的方案即可,不需要堆叠多个独立库,核心用PyMuPDF就能覆盖绝大多数场景,比纯文本正则的适配性高一个量级。
核心实现逻辑
你之前调用PyMuPDF、pdfplumber只取了纯文本内容,完全浪费了库能拿到的格式元数据:每个文本片段的字体大小、是否加粗、坐标位置、特殊字符标记、字体类型这些信息,才是做定向区间提取的核心依据,比纯文本关键词匹配靠谱得多。
- 第一类PDF(从Case 1提取到加粗菱形◆标记):不需要写正则硬找菱形符号,直接遍历所有文本片段,当片段包含
◆字符且属于加粗格式时,标记为区间终止点;找到文本以"Case"加数字开头的片段位置作为起始点,拼接两个位置之间的所有文本即可,完全不受换行、缩进、跨页拆分的影响。 - 第二类PDF(按history/examination/investigations章节切分):这类医学文档的章节标题普遍有固定格式特征(加粗、字号大于正文、单独占行),先把所有符合标题特征的文本块按从上到下的阅读顺序排序,再按标题名切分相邻标题之间的内容块即可,不会因为不同PDF的标题大小写、前后多余空格、标点差异匹配失败。
多格式适配方案
不要把提取逻辑硬编码在脚本里,做一层可配置规则层,核心提取逻辑完全复用,新增PDF格式只需要加一条规则配置,不需要重写代码:
- 每类PDF的规则配置仅需定义3类信息:起始位置匹配规则(支持关键词、加粗/字号等格式特征)、终止/切分位置匹配规则、需要导出的字段名
- 配置示例:
# 第一类PDF提取规则 PDF_TYPE1_RULE = { "match_type": "range", "start": {"keyword": "Case", "is_bold": False}, "end": {"keyword": "◆", "is_bold": True}, "export_fields": ["case_content"] } # 第二类PDF提取规则 PDF_TYPE2_RULE = { "match_type": "section_split", "section_titles": ["history", "examination", "investigations"], "title_format": {"is_bold": True}, "export_fields": ["history", "investigation"] }
代码启动时先做PDF类型自动识别(可通过文档抬头、固定位置的特殊标识判断),自动加载对应规则执行提取即可,你手里剩下的5种以上格式PDF,每加一种只需要写10行以内的配置,维护成本极低。
兜底处理方案
- 遇到扫描版PDF:直接用PyMuPDF内置的OCR能力对接Tesseract,识别时保留每个文本块的坐标、置信度信息,和可编辑PDF的处理逻辑完全打通,不需要单独写一套OCR提取代码。
- 遇到文本块顺序错乱的PDF:所有提取到的文本块先按纵向坐标(上下位置)优先、横向坐标(缩进位置)次之排序,再做区间匹配,能解决90%以上排版错乱导致的内容顺序不对问题。
核心代码示例
基于PyMuPDF实现,替换了你之前只提取纯文本的逻辑,支持格式判断:
import fitz import pandas as pd def load_all_spans(pdf_path): """加载PDF所有文本片段,附带格式属性""" doc = fitz.open(pdf_path) all_spans = [] for page in doc: page_blocks = page.get_text("dict")["blocks"] for block in page_blocks: if "lines" not in block: continue for line in block["lines"]: for span in line["spans"]: # 追加加粗判断属性 span["is_bold"] = bool(span["flags"] & 16) span["text_stripped"] = span["text"].strip().lower() all_spans.append(span) doc.close() return all_spans def extract_by_rule(spans, rule): """按配置规则提取目标内容""" result = {field: "" for field in rule["export_fields"]} if rule["match_type"] == "range": # 区间提取逻辑 start_idx = None end_idx = None for idx, span in enumerate(spans): if start_idx is None and rule["start"]["keyword"] in span["text"] and span["is_bold"] == rule["start"]["is_bold"]: start_idx = idx if start_idx is not None and rule["end"]["keyword"] in span["text"] and span["is_bold"] == rule["end"]["is_bold"]: end_idx = idx break if start_idx is not None and end_idx is not None: result[rule["export_fields"][0]] = "".join([s["text"] for s in spans[start_idx:end_idx]]).strip() elif rule["match_type"] == "section_split": # 章节切分逻辑 title_pos = [] for idx, span in enumerate(spans): if span["text_stripped"] in rule["section_titles"] and span["is_bold"] == rule["title_format"]["is_bold"]: title_pos.append((idx, span["text_stripped"])) for i in range(len(title_pos)-1): cur_idx, cur_title = title_pos[i] next_idx = title_pos[i+1][0] if cur_title in result: result[cur_title] = "".join([s["text"] for s in spans[cur_idx+1:next_idx]]).strip() return result if __name__ == "__main__": all_extract_result = [] # 此处可加自动识别PDF类型、加载对应规则的逻辑 test_rule = PDF_TYPE2_RULE spans = load_all_spans("target_file.pdf") all_extract_result.append(extract_by_rule(spans, test_rule)) # 导出到Excel pd.DataFrame(all_extract_result).to_excel("extract_output.xlsx", index=False)
避坑提示:不要把纯文本正则作为核心匹配逻辑,纯文本丢失了所有格式信息,只要PDF出现换行、多余空格、特殊字符编码差异就会匹配失效,结合格式特征的匹配准确率普遍能达到95%以上。
内容的提问来源于stack exchange,提问作者Arvind Singh
相关产品推荐
相关产品推荐

