PDF目录提取异常:仅1/50文档可提取,求排查与解决方案
PDF目录提取失败的原因与解决方案
核心原因分析
- PDF无内置书签(TOC):你使用
doc.get_toc()提取的是PDF的官方内置书签,但多数PDF的目录只是页面上的静态文本/链接(视觉上的目录栏),并没有被标记为PDF标准的书签结构,因此get_toc()返回空列表。这是最常见的失效原因。 - 代码存储逻辑缺陷:你的代码用
page作为字典key存储书签,若同一页存在多个书签,后面的条目会直接覆盖前面的,导致信息丢失(即使提取到书签也会不全)。 - 特殊PDF格式限制:少数PDF可能存在加密、损坏情况;若目录是图片形式(扫描件),
get_toc()也无法读取——但你提到目录清晰可识别,扫描件概率较低。加密PDF会直接导致get_toc()无法访问内容,需先解密。
解决方案
1. 兼容内置书签与页面文本目录
针对无内置书签的PDF,需要提取页面文本并解析目录结构,以下是改进后的代码:
from typing import List import os import fitz import re def get_bookmarks(filepath: str) -> List[List[int, str, int]]: """提取PDF目录,优先内置书签,无则尝试解析前3页文本目录""" with fitz.open(filepath) as doc: # 尝试解密(空密码适用于无密码加密的PDF,有密码需传入对应值) if doc.is_encrypted: doc.authenticate("") # 先提取内置书签 toc = doc.get_toc() if toc: return toc # 无内置书签,解析前3页的文本目录 toc = [] for page_num in range(min(3, doc.page_count)): page = doc.load_page(page_num) text = page.get_text("text") lines = text.split("\n") # 正则匹配目录行:标题 + 末尾页码(兼容点、空格等分隔符) pattern = re.compile(r'^\s*(.*?)\s+([\d]+)\s*$') for line in lines: match = pattern.match(line) if match: title = match.group(1).strip() target_page = int(match.group(2)) # 默认层级为1,可根据行首空格数判断子层级(可选) toc.append([1, title, target_page]) return toc def export_toc(filepath: str, toc: List[List[int, str, int]]): """将提取的目录导出为文本文件""" if not toc: return output_path = os.path.splitext(filepath)[0] + "_toc.txt" with open(output_path, "w", encoding="utf-8") as f: for level, title, page in toc: indent = " " * (level - 1) f.write(f"{indent}{title} —— 第{page}页\n") # 批量处理目录 for root, _, files in os.walk("doc/"): for file in files: if not file.lower().endswith(".pdf"): continue file_path = os.path.join(root, file) toc = get_bookmarks(file_path) print(f"文件:{file_path} | 目录条目数:{len(toc)}") export_toc(file_path, toc)
2. 优化书签存储逻辑
放弃用page作为字典key,改用原始的列表格式存储所有书签,避免同一页多书签的覆盖问题。
3. 处理加密PDF
若PDF有密码,需在doc.authenticate()中传入正确密码;若不知道密码,无法提取任何内容。
进阶优化(可选)
- 若目录有层级(如缩进的子标题),可通过统计行首空格/制表符数量,进一步判断目录层级,完善正则或解析逻辑。
- 若PDF目录是图片格式(扫描件),需结合OCR工具(如
pytesseract)提取文本后再解析。
内容的提问来源于stack exchange,提问作者Lorenzo Cutrupi
相关产品推荐
相关产品推荐

