
PDF 里有一堆高清图片但想批量拿出来的时候往往只能“截图另存”截完还容易丢分辨率。整理报告、抽取资料配图、把扫描版材料和图片型 PDF 归档时很多人都遇到过这个问题。用 Python 做“PDF 图片提取”并不复杂而且不需要 Web 服务和独立显卡普通电脑就能跑通。这篇文章以一个可复制的 Python 提取脚本为主线演示如何用 PyMuPDF 把 PDF 页面中内嵌的图片按原始格式导出同时处理重复图片、过滤无效小图、批量扫描整个目录。处理完图片后还可以顺手接到 OCR 或图片归档流程里适合 Python 入门者、办公自动化和文档处理开发者参考。1. python 提取 PDF 图片的核心思路很多人会有一个误解PDF 里的“图片”就是肉眼看到的一张图。实际上PDF 页面是对象系统页面里显示的图片通常以 XObject 形式内嵌在内容流中有的 PDF 还会对图片做裁剪、旋转或蒙版处理。所以“提取图片”不等于“页面截图”。截图得到的是渲染后的像素结果而提取原图要直接读取 PDF 内部保存的图片对象。这样导出的图片质量和原始嵌入文件一致不会因为页面缩放、显示倍率而降低清晰度。实现这条思路主要有三类工具方案原理适合场景PyMuPDFfitz遍历页面图片对象解码并保存需要保留原始图片质量、批量处理Poppler 的 pdfimages命令行提取图片不想写 Python 代码时的快速提取pypdf / PyPDF2通过资源配置读取 XObject轻量集成功能相对有限PyMuPDF 是相对省心的选择因为它的 API 封装了图片对象解析、颜色空间转换和常见格式保存一段脚本就能把整个 PDF 的图片一次性导出。它的 Python 导入名称是fitz实际使用中这两个名字对应同一个库。下面这个最小示例就能理解核心逻辑import fitz doc fitz.open(input.pdf) page doc[0] images page.get_images(fullTrue) print(images) doc.close()images中每一条记录都对应页面中的一个图片对象其中第一个元素是图片的 xref 编号。拿到 xref 后再用doc.extract_image(xref)就能取出图片字节和扩展名。这就完成了“从 PDF 内提取原始图片”的全过程。2. 使用场景与版权合规边界以 Python 提取 PDF 图片这套脚本最常见的应用场景是这几类从 PDF 格式的行业报告、产品手册中提取图表和配置图。归档扫描版文档时需要把内嵌的扫描照片单独整理出来。处理课件、PPT 转出的 PDF批量保留其中的示意图片。准备训练数据或素材库时从有权使用的 PDF 中抽取图片。把提取结果接入 OCR 工具对图片中的文字做进一步识别。这套流程不适合的场景也很明确如果 PDF 页面里的视觉内容不是图片对象而是矢量绘制的流程图、CAD 线稿、转曲后的文字那么用提取图片的方式拿不到完整内容必须改用页面渲染成 PNG 的方式才能复制视觉结果。使用边界方面需要特别注意文件操作类脚本很容易被用于处理他人成果务必只处理自己创建的、已获得授权的、或公开声明可编辑使用的 PDF。PDF 如果设置了访问密码或限制权限而你没有合法访问授权不能通过自动化脚本去绕过限制。提取出的图片如果用于公开内容或商业交付需要根据原文档协议确认授权范围。3. Python 提取 PDF 图片的环境准备整条方案以 PyMuPDF 为核心环境配置很轻普通办公电脑即可。3.1 环境要求建议环境如下Windows 10/11、Ubuntu 20.04 或 macOS。Python 3.8 以上推荐 3.10 或 3.11。磁盘空间安装依赖约需 100 MB 以内实际看输出图片体积。无需 GPUCPU 即可运行。可以先检查 Python 是否可用python --version pip --version如果机器同时存在多个 Python 版本建议使用虚拟环境避免依赖互相干扰python -m venv venvWindows 激活方式venv\Scripts\activatemacOS / Linux 激活方式source venv/bin/activate3.2 安装 PyMuPDF安装命令很简单pip install PyMuPDF检查是否安装成功python -c import fitz; print(fitz.__doc__)正常会输出 PyMuPDF 的版本和导入信息。如果提示找不到fitz优先检查是否确实安装了 PyMuPDF而不是其他名字相似的库。个别旧项目会残留fitz旧模块遮蔽实际安装包这种时候建议在干净虚拟环境中重新安装。这里不写死具体版本号因为 PyMuPDF 迭代比较快。建议使用较新稳定版新版本对 PDF 1.7 和更多图片格式支持更好。4. 基础提取脚本与运行验证把下面的脚本保存为extract_pdf_images.py然后用命令行传参运行即可。import sys from pathlib import Path import fitz def extract_images_from_pdf(pdf_path: str, output_dir: str) - None: pdf_path Path(pdf_path) output_path Path(output_dir) output_path.mkdir(parentsTrue, exist_okTrue) doc fitz.open(pdf_path) print(f已打开 PDF: {pdf_path.name}共 {doc.page_count} 页) for page_index, page in enumerate(doc, start1): images page.get_images(fullTrue) if not images: continue for image_index, image_info in enumerate(images, start1): xref image_info[0] try: base_image doc.extract_image(xref) except Exception as exc: print(f第 {page_index} 页图片 {xref} 提取失败: {exc}) continue image_bytes base_image[image] image_ext base_image[ext] file_stem pdf_path.stem output_file output_path / f{file_stem}_page{page_index:03d}_num{image_index:03d}.{image_ext} with open(output_file, wb) as f: f.write(image_bytes) size_kb len(image_bytes) / 1024 print(f已保存: {output_file.name} | {size_kb:.1f} KB) doc.close() if __name__ __main__: if len(sys.argv) ! 3: print(用法: python extract_pdf_images.py 输入PDF 输出目录) sys.exit(1) extract_images_from_pdf(sys.argv[1], sys.argv[2])运行方式python extract_pdf_images.py example.pdf extracted_images脚本的逻辑并不复杂先逐页调用page.get_images(fullTrue)拿到图片对象列表再通过doc.extract_image(xref)解出原始图片字节和扩展名。输出文件命名规则包含了页数和图片序号保证多个文件不会互相覆盖。判断是否成功的标准有三个输出目录中出现了 PNG、JPEG 等图片文件。文件大小明显大于 0 字节而不是空文件。图片能正常打开且尺寸与 PDF 原始页面中的图片一致。如果脚本没有任何输出说明 PDF 页面中可能不存在可直接提取的位图对象这种情况会在后面章节单独处理。5. 批量提取与去重过滤实际使用 PDF 时几页的小文档不算难常见的麻烦是几个问题一个 PDF 有几百页导出的图片数量巨大。同一张公司 Logo 或背景图在每一页都出现提取后大量重复。页面上有大量装饰性小图标不是真正需要的图片。需要批量处理一个目录下的几十个 PDF。5.1 按图片引用去重PDF 内部经常复用同一张图片导致不同页面的 xref 指向同一个资源对象。只需要维护一个xref集合已经保存过的 xref 就不再写入第二次。下面的代码在上一节脚本基础上增加了去重逻辑unique_xrefs set() for page_index, page in enumerate(doc, start1): for image_info in page.get_images(fullTrue): xref image_info[0] if xref in unique_xrefs: continue unique_xrefs.add(xref) base_image doc.extract_image(xref) # 后续保存逻辑和上面一致这一步能显著减少重复文件尤其是页眉页脚包含 Logo 的 PDF。5.2 过滤小尺寸图片page.get_images(fullTrue)返回的图像信息中通常包含宽高字段常见位置是第 3 和第 4 个元素。可以根据实际需求过滤小于某一尺寸的图片。width image_info[2] height image_info[3] if width 50 or height 50: continue这里 50 x 50 只是一个示例值。处理实际文档时可以先不过滤跑一遍观察导出结果后再根据最小有效图片尺寸设置边界。5.3 批量处理目录中的 PDF当目录中有一批 PDF 时可以用pathlib遍历from pathlib import Path from concurrent.futures import ThreadPoolExecutor pdf_dir Path(./pdfs) output_dir Path(./outputs) output_dir.mkdir(exist_okTrue) pdf_files list(pdf_dir.glob(*.pdf)) print(f发现 {len(pdf_files)} 个 PDF 待处理) for pdf_file in pdf_files: target_dir output_dir / pdf_file.stem target_dir.mkdir(exist_okTrue) extract_images_from_pdf(str(pdf_file), str(target_dir))批量任务最好加入日志记录每个 PDF 的图片数量、总耗时和失败信息。文件多的时候可以用ThreadPoolExecutor同时对多个 PDF 执行提取但要注意每个任务内部需要独立打开和关闭文档不能共享同一个doc对象。从稳定角度看首次大规模处理时建议先串行确认输出逻辑没问题再根据 CPU 核数决定是否并行。6. 提取结果质量验证提取完成后不要急着收工至少做三件事验证输出质量。第一用 Python 读取图片文件确认可以正常解码。from PIL import Image img Image.open(extracted_images/example_page001_num001.png) print(img.size, img.format, img.mode)如果 Pillow 没有安装先执行pip install Pillow第二检查文件大小。PDF 原始嵌入图片如果是高分辨率扫描图导出文件通常有几百 KB 到几十 MB。如果一张图片只有几 KB 且尺寸很小大概率是装饰图可以在后续处理中过滤。第三抽查页数与文件数量的对应关系。可以打开 PDF 每一页目测可见位图数量再对比脚本输出的命名序号判断是否有页面没有导出成功。常见的情况是PDF 中有个别页面嵌入了大量平铺小图数量比预期多好几倍这时不必认为脚本异常先看图片尺寸是否符合需求。如果导出的文件扩展名比较冷门比如.jpx、.jb2e需要检查后续工具链能否直接处理。大多数看图软件可以打开常见格式PyMuPDF 的extract_image并不会把图片统一转成 PNG而是尽量保持原始格式。7. 处理特殊页面渲染备用方案并不是每一个 PDF 页面里的“视觉图形”都能通过图片对象拿到。7.1 被裁剪或旋转的图片PDF 页面可以对图片对象设置裁剪区域、旋转角度和遮挡关系。直接extract_image拿到的是 PDF 内部存储的完整原始图片有可能比页面上看到的区域更大。例如页面上展示了图片的一部分但内部存储的是整张扫描原图。这种情况不算异常想要的是页面展示区域时就需要用渲染方式而不是提取图片对象。7.2 矢量图形和扫描版 PDF流程图、CAD 线稿、文字转曲后的 Logo本质是 PDF 内部矢量路径不是图片 XObject。扫描版 PDF 看起来像图片其实整个页面可能只有一张图片对象这种情况提取是有效的。真正的难点是矢量页。解决方案是把整页或某个区域渲染成位图page doc[0] zoom 2.0 matrix fitz.Matrix(zoom, zoom) pixmap page.get_pixmap(matrixmatrix) pixmap.save(output.png)zoom2.0表示按 2 倍分辨率渲染。需要导出多少清晰度就调整这个系数这一步受 CPU 性能影响PDF 页面越复杂耗时越长。7.3 使用 Poppler 的 pdfimages如果不想用 Python也可以使用 Poppler 工具集的pdfimages。前提是系统安装了 poppler-utils。Linux 下通过包管理器安装macOS 可以用 HomebrewWindows 需要下载对应的 Poppler 运行时并配置可执行路径。命令格式pdfimages -png input.pdf output_prefix执行后会在当前目录生成output_prefix-000.png这类文件。pdfimages的优势是专门做图片抽取适合命令行快速处理但它的输出命名不带页码遇到图片数量较多的 PDF 时需要额外对照pdfimages -list查看位置。PyMuPDF 的优势在于一个库同时完成解析、保存、过滤和批量任务后续还能把图片路径直接传给 OCR不需要维护额外系统依赖所以本文的主流程仍以 PyMuPDF 为主。8. 常见问题与排查方法实际运行过程中问题大多集中在环境、PDF 属性和输出文件三个方面。问题现象可能原因排查与解决安装 PyMuPDF 后无法 import fitz库未安装成功或环境不对重新安装pip install PyMuPDF进入虚拟环境后验证使用python命令找不到模块多个 Python 版本pip 装到了另一个环境改用python -m pip install PyMuPDF安装运行脚本后没有输出PDF 页面没有位图对象只有矢量图形逐页渲染为 PNG或者用pdfimages -list查看对象提取结果中出现大量几千字节的小图PDF 每页包含重复 Logo、背景图按 xref 去重并按宽高尺寸过滤输出图片数量和预期不符图片被 PDF 内部重复引用或裁剪处理先不过滤跑一次统计重复文件和图片尺寸再调整extract_image报错xref 对象不是图片或资源已损坏捕获异常并记录页码和 xref 编号文件占用内存过高单页包含超清大图或整个文档图片密集逐页处理及时doc.close()不要一次性保存所有图片字节输出带有密码的 PDF 失败文档受保护且没有密码如果拥有合法密码在fitz.open时传入 password 参数没有授权则不要绕过保护输出图片无法用看图软件打开扩展名冷门或图片颜色空间特殊用 Pillow 尝试打开无法使用就改用渲染方式导出这里需要单独强调密码问题。PyMuPDF 支持传入密码打开部分受保护文档但前提是你有权访问该 PDF。如果文档设置了限制编辑、复制、打印的权限不能通过脚本绕过这些限制去提取内容这点在企业和教育场景中尤其重要。9. 批量任务性能观察与目录规范提取图片的整体消耗来自两个环节读取 PDF 内部对象和写入文件。正常几百页的 PDF只要不是包含海量超清扫描图都能在几秒到几十秒内完成。真正影响性能的是大面积渲染整个页面因此优先“直接提取原图”而不是逐页渲染。CPU 和内存表现会受 PDF 自身结构影响。单个 PDF 中若嵌入了多张几十 MB 的扫描图extract_image会把解码后的图片数据加载进内存如果脚本把所有内容都保存在列表中再统一写盘内存可能会飙升。比较好的做法是每处理一个 xref立即写入文件并释放引用。而不是先收集全部图片字节再写入。处理完一个大的 PDF 后调用doc.close()。对于超大目录分批执行并记录断点避免一次任务失败后从头开始。从工程化习惯看建议把文件目录整理成这样pdf_processing/ ├── input_pdfs/ # 原始 PDF ├── extracted_pdfs/ # 每个 PDF 一个子目录的图片结果 ├── logs/ # 提取日志 └── scripts/ # python 脚本日志文件至少记录三部分PDF 文件名、本次提取图片数、失败对象的 xref。后续如果有人反馈某个文件没提取出来可以直接通过日志定位。10. 把提取能力封装为 API 服务提取脚本本身是离线任务但如果要把能力提供给其他同事或者接入自己的自动化处理平台可以包一层简易 HTTP API。这类接口不需要 AI 模型也不吃显卡普通 Python Web 服务就能承载。这里给出一个通用结构具体路由和参数需要根据现有后端技术栈调整。from pathlib import Path from tempfile import NamedTemporaryFile from fastapi import FastAPI, File, UploadFile from extract_pdf_images import extract_images_from_pdf app FastAPI() app.post(/extract) async def extract_pdf_images_api(file: UploadFile File(...)): with NamedTemporaryFile(deleteFalse, suffix.pdf) as tmp_file: content await file.read() tmp_file.write(content) tmp_pdf_path tmp_file.name output_dir tempfile.mkdtemp(prefixextracted_) extract_images_from_pdf(tmp_pdf_path, output_dir) # 后续可以将 output_dir 压缩为 zip 返回 return { file_name: file.filename, output_dir: output_dir }依赖安装pip install fastapi uvicorn python-multipart启动服务uvicorn api_server:app --host 127.0.0.1 --port 8000需要留意的是文件上传接口如果部署在可公网访问的服务器上会面临恶意上传、磁盘占用和路径穿越风险。更稳妥的方案是限制上传文件类型和体积、设置访问密钥、只允许内网调用并且不直接把用户输入拼接到输出路径中。建议只把该 API 用于受信任的内网环境。下一步可以扩展的方向也很明确提取出的图片如果包含文字可以接 OCR 模型做文字识别如果图片是截图或照片可以接图像分类做素材整理如果涉及批量历史 PDF则建议增加断点续跑和可视化任务队列。第一次使用时建议先在 10 页以内的测试 PDF 上跑通脚本确认输出图片质量没有问题再正式处理大量文件。