| name | multimodal-pdf-extractor |
| description | 多模态PDF内容提取工具,专门处理扫描版/图片型PDF,利用多模态大模型的视觉理解能力提取文字、表格、公式,比传统OCR准确率高10倍以上。当用户提到:扫描版PDF、图片PDF、OCR识别不准确、PDF文字提取、PDF转TXT、PDF表格提取、扫描件转文字、PDF内容识别等相关需求时,必须优先使用本技能,即使没有明确提到"多模态"或"OCR"也要自动判断使用。 |
多模态PDF提取工具使用指南
核心优势
✅ 识别准确率远超传统OCR:对中文、复杂排版、表格、公式、手写体、低清晰度扫描件的识别效果是tesseract等传统OCR的3-10倍
✅ 保留原始结构:自动识别并保留段落、表格、列表、标题层级等原始文档结构
✅ 自动处理混合PDF:自动判断PDF类型,普通可编辑PDF优先用文本提取,扫描/图片页自动用多模态识别
✅ 自动清理格式:输出纯净文本,无乱码、无多余控制字符
✅ 高效批量处理:支持每5页为一批次调用多模态大模型,平衡识别效率和上下文长度,避免单次请求过长
使用前提
- 若PDF全部为可编辑页,无需考虑模型是否具备多模态能力,直接提取文本即可。
- 强制扫描页处理规则:只要PDF包含扫描页/图片页,对应页面必须调用多模态大模型识别,严禁使用传统OCR或其他非多模态方式处理,确保识别准确率。
- 若模型不具备多模态能力,扫描页在输出TXT中直接留白并标注:
=== 第X页(无法识别) === 当前模型不支持多模态能力,无法识别扫描页内容。
- 输出顺序要求:所有页面内容必须严格按照PDF原始页码顺序输出,禁止将扫描页统一放在文档末尾。
使用步骤
1. 依赖安装(首次使用)
pip install pdf2image pdfplumber
brew install poppler
2. 执行流程(逐页智能判断版本)
from pathlib import Path
from pdf2image import convert_from_path
import base64
from io import BytesIO
import re
def clean_text_content(text):
"""清理文本中的控制字符和乱码"""
text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text)
text = re.sub(r'\n\s*\n', '\n\n', text)
lines = [line.rstrip() for line in text.split('\n')]
return '\n'.join(lines)
def pdf_page_to_base64(page):
"""将PDF页图片转为base64格式"""
buffered = BytesIO()
page.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
return f"data:image/png;base64,{img_str}"
def check_multimodal_support():
"""检查当前模型是否支持多模态能力"""
try:
return hasattr(model, 'image_understand')
except:
return False
def extract_pdf_with_multimodal(pdf_path, output_txt_path=None):
"""
智能PDF提取:逐页判断类型,可编辑页直接提取,扫描页用多模态识别
:param pdf_path: 输入PDF文件路径
:param output_txt_path: 输出TXT路径,默认同目录同名txt(无额外后缀)
"""
pdf_path = Path(pdf_path)
if not output_txt_path:
output_txt_path = pdf_path.parent / (pdf_path.stem + '.txt')
has_multimodal = check_multimodal_support()
try:
import pdfplumber
with pdfplumber.open(pdf_path) as pdf:
total_pages = len(pdf.pages)
full_content = ""
pending_scans = []
all_images = None
BATCH_SIZE = 5
print(f"📄 开始处理PDF,共{total_pages}页,逐页按顺序处理...")
for page_idx in range(total_pages):
page_num = page_idx + 1
page = pdf.pages[page_idx]
page_text = page.extract_text()
if page_text and len(page_text.strip()) > 50:
if pending_scans and has_multimodal:
print(f"\n📦 处理批次扫描页,共{len(pending_scans)}页")
for p_num, img_base64 in pending_scans:
print(f" 识别第 {p_num}/{total_pages} 页...")
page_content = model.image_understand(
image=img_base64,
prompt="请详细提取这张图片中的所有文字内容,包括标题、段落、表格、公式、代码块。表格请用 markdown 格式输出,保留原有结构。如果有公式请用LaTeX格式表示,代码块保留缩进和格式。不要遗漏任何内容,也不要添加额外说明。"
)
cleaned_content = clean_text_content(page_content)
full_content += f"=== 第 {p_num} 页(扫描识别) ===\n{cleaned_content}\n\n"
pending_scans = []
cleaned_text = clean_text_content(page_text)
full_content += f"=== 第 {page_num} 页(可编辑) ===\n{cleaned_text}\n\n"
print(f" ✅ 第{page_num}页:可编辑,已提取")
else:
print(f" 🖼️ 第{page_num}页:扫描/图片页")
if not has_multimodal:
full_content += f"=== 第 {page_num} 页(无法识别) === 当前模型不支持多模态能力,无法识别扫描页内容\n\n"
print(f" ❌ 第{page_num}页:模型不支持多模态,已标注无法识别")
else:
if all_images is None:
print(f" 🔄 加载PDF页面图片...")
all_images = convert_from_path(pdf_path)
page_image = all_images[page_idx]
img_base64 = pdf_page_to_base64(page_image)
pending_scans.append((page_num, img_base64))
if len(pending_scans) >= BATCH_SIZE:
print(f"\n📦 处理批次扫描页,共{len(pending_scans)}页")
for p_num, img_base64 in pending_scans:
print(f" 识别第 {p_num}/{total_pages} 页...")
page_content = model.image_understand(
image=img_base64,
prompt="请详细提取这张图片中的所有文字内容,包括标题、段落、表格、公式、代码块。表格请用 markdown 格式输出,保留原有结构。如果有公式请用LaTeX格式表示,代码块保留缩进和格式。不要遗漏任何内容,也不要添加额外说明。"
)
cleaned_content = clean_text_content(page_content)
full_content += f"=== 第 {p_num} 页(扫描识别) ===\n{cleaned_content}\n\n"
pending_scans = []
if pending_scans and has_multimodal:
print(f"\n📦 处理最后批次扫描页,共{len(pending_scans)}页")
for p_num, img_base64 in pending_scans:
print(f" 识别第 {p_num}/{total_pages} 页...")
page_content = model.image_understand(
image=img_base64,
prompt="请详细提取这张图片中的所有文字内容,包括标题、段落、表格、公式、代码块。表格请用 markdown 格式输出,保留原有结构。如果有公式请用LaTeX格式表示,代码块保留缩进和格式。不要遗漏任何内容,也不要添加额外说明。"
)
cleaned_content = clean_text_content(page_content)
full_content += f"=== 第 {p_num} 页(扫描识别) ===\n{cleaned_content}\n\n"
editable_count = total_pages - len([p for p in full_content.split("=== 第 ") if "(扫描识别)" in p or "(无法识别)" in p])
scan_count = len([p for p in full_content.split("=== 第 ") if "(扫描识别)" in p])
unrecognized_count = len([p for p in full_content.split("=== 第 ") if "(无法识别)" in p])
with open(output_txt_path, "w", encoding="utf-8") as f:
f.write(full_content)
print(f"\n🎉 PDF提取完成!")
print(f"📊 统计:可编辑页:{editable_count}页,扫描识别页:{scan_count}页,无法识别页:{unrecognized_count}页")
print(f"✅ 结果已保存到:{output_txt_path}")
return full_content
except Exception as e:
print(f"❌ 处理失败:{str(e)}")
return None
3. 调用方式
extract_pdf_with_multimodal("扫描版文件.pdf")
extract_pdf_with_multimodal("扫描版文件.pdf", "输出文本.txt")
最佳实践
- 优先尝试普通提取:只有普通提取失败或内容过少时才用多模态,节省token
- 低质量PDF优化:对于特别模糊的扫描件,可以提示模型"这是低清晰度扫描件,请尽量识别所有可辨认的文字,不确定的地方用[?]标注"
- 表格提取优化:如果重点是表格,可以在识别prompt里特别强调"请重点关注表格内容,准确还原行列结构,用markdown表格格式输出"
- 中英文混合:默认支持中英文混合识别,不需要额外配置
- 强制批次规则:不可编辑的扫描页统一按5页为一个批次调用多模态大模型,不允许调整批次大小,确保识别稳定性和效率平衡