ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python提取PDF图片实战:PyMuPDF与多方案选型指南

2026/9/5 6:58:30 拓冰建站 浏览量
Python提取PDF图片实战:PyMuPDF与多方案选型指南 在 PDF 处理这件事上有一个高频需求常年挂在技术论坛和办公效率帖里怎么把 PDF 里的图片提取出来做 PPT 想复用报告里的架构图却发现 PDF 是从网上文档打印导出的整理合同附件需要把客户盖章的扫描页单独存成图片训练数据集想要批量提取论文 PDF 中的所有插图。这些场景的解决方案很多人第一反应是打开 PDF 编辑器手动一页页查看、截图再用图片工具裁剪。结果就是图片变形、分辨率损失、效率极低而实际上 Python 生态里已经有一整套成熟方案。这篇文章想讲清楚一件事Python 提取 PDF 中的图片不是一个“一行代码搞定一切”的万能填空题而是一道需要根据 PDF 构造方式选择方案的判断题。文章会从最常用的 PyMuPDF 入手给你一份可以直接运行的提取代码再对比 pdfplumber、pdf2image 等方案的适配边界最后补充工程中真正会遇到的过滤小图、处理扫描件、批量命名等实用细节。如果你曾经“提取出来一堆碎图”或者“明明页面上有图却提取不到”这篇一定要读完原因不在代码不够强而在于对 PDF 内部结构少了一层认知。1. 提取 PDF 图片为什么一个“简单需求”却常翻车先看一个很容易被忽略的事实PDF 并不是一种类似 Word 那样把图片“直接放在页面里”的文档格式。它本质上是一种基于页面描述语言的文档结构每个页面由一组图形对象、文字对象和资源对象组成。当你在阅读器里看到一张插图时图片可能以原始嵌入对象的形式存放在 PDF 资源字典中也可能是被旋转过、裁剪过、缩放过的复杂图形块。这就带来了两种完全不同的提取思路第一种思路读取 PDF 内部的图像对象XObject把图片从原始资源里取出来不做渲染。这种方式的优点是拿到的是原始分辨率图片无损、干净适合论文插图、报告截图、复杂图表的还原。缺点也很明显如果图片被 PDF 作者用旋转矩阵包了一层或某个图表其实是由线条、色块矢量绘制的那么“图像对象”里根本没有这张图。第二种思路把 PDF 页面渲染成位图然后切割、保存成 PNG 或 JPG。这种方式拿到的是“视觉上整页的图片”适合扫描件、无法直接提取内嵌图片的文档缺点是你在页面上看到什么最后保存的就是什么如果你只想保存图片本身需要额外做裁剪定位还可能因为页面缩放导致分辨率不如预期。很多人翻车的原因就是把第一种思路当成唯一解法。运行 PyMuPDF 提取时发现只导出零星几张图或操作 Excel 报告的思路写出来的 PDF 里大量元素是表格线条和文字而非嵌入图片于是得出“这个方法不行”的结论。事实上不是方法不行是方法与被提取文件的“构造类型”不匹配。从工程实践角度来说我们总是先做这样一道判断如果文档来源是教材、网页保存的 PDF、InDesign 排版的宣传册优先走图像对象提取如果文档来源是扫描仪、手机扫描 App、传真存档优先走页面渲染提取如果排查后仍想要“看到什么存什么”那就老老实实按页面渲染后裁剪。2. Python PDF 图片提取常用库盘点既然要从 Python 中提取 PDF 图片有几个名字会反复出现。这里先做一个横向比对方便实际选型。需要注意这些库的侧重点不同不是互相替代的关系很多项目里其实是配合使用的。库名定位图片提取方式适用场景是否需额外依赖PyMuPDFfitzPDF 解析与渲染库可直接读取页面图像对象也可渲染页面主流方案速度快、API 完整仅安装 Python 包pdfplumberPDF 文本与图形元素解析库可拿到页面 images 列表及位置信息需要同时分析图片坐标、版面信息时仅安装 Python 包pdf2imagePDF 页面渲染库将整个页面转为 PIL 图像扫描件与页面截图场景依赖系统 popplerPyPDF2 / pypdfPDF 基础操作库部分版本支持图片提取但能力弱合并、拆分、加密等操作仅安装 Python 包pdfimagespoppler 工具命令行工具编译级提取所有图像对象Linux/macOS 批量处理系统安装 poppler细看这张表你会注意到我推荐的方案是 PyMuPDF。原因很明确它既支持直接提取图像对象又能按需渲染页面。安装时只需一个包不需要额外安装系统二进制组件对 Windows 用户尤其友好而这恰好是大量 Python 办公脚本的运行环境。pdfplumber 在处理“需要知道图片在哪块位置”时更有优势比如你要根据环绕文本把图片切出来或分析 PDF 图片的排版坐标它的对象模型更直观。而 pdf2image 的优势在扫描件场景它不关心 PDF 内部有没有“可提取的图片对象”因为它直接把整页变成图片。3. 环境准备安装依赖与验证以下代码均在 Python 环境下执行。建议先确认机器上 Python 版本不低于 3.8并使用虚拟环境或直接使用 conda 环境进行隔离避免多个项目互相污染依赖。3.1 安装 PyMuPDFPyMuPDF 的安装方式非常简单pip install PyMuPDF这里有一个容易踩坑的知识点PyMuPDF 的 Python 导入名并不是pymupdf而是fitz。这是历史遗留命名习惯但不影响使用。如果你在代码中写成import PyMuPDF会直接报错。验证安装是否成功python -c import fitz; print(fitz.__doc__ or PyMuPDF installed)如果输出正常说明环境已就绪。3.2 安装 pdfplumber可选如果你需要处理图片坐标定位或版面分析可以一并安装pip install pdfplumber3.3 安装 pdf2image 与 poppler可选在扫描件场景会用到 pdf2imagepip install pdf2imageWindows 用户需要去 poppler 官网下载对应版本解压后将 bin 目录加入系统环境变量 PATHmacOS 用户可以用 Homebrew 安装brew install popplerLinux 用户通常使用 apt 或 yumsudo apt-get install poppler-utils注意如果你当前不涉及扫描件提取这里可以跳过不会影响主流程。4. 用 PyMuPDF 提取整本 PDF 的图片可直接复用的代码现在进入核心实操环节。下面是一份完整的 Python 脚本它能在任意 PDF 文件中提取所有内嵌图片对象并按“原始质量”保存到指定目录。4.1 完整脚本# -*- coding: utf-8 -*- extract_pdf_images.py 使用 PyMuPDF 提取 PDF 中的图片对象 用法 python extract_pdf_images.py input.pdf output_dir import os import sys import fitz # PyMuPDF def extract_images_from_pdf(pdf_path, output_dir, min_width0, min_height0): 从 PDF 中提取所有图片对象并保存。 :param pdf_path: PDF 文件路径 :param output_dir: 输出目录 :param min_width: 最小图片宽度小于该值的图片忽略用于过滤图标/水印 :param min_height: 最小图片高度小于该值的图片忽略 :return: 提取成功的图片数量 # 1. 参数检查 if not os.path.exists(pdf_path): raise FileNotFoundError(fPDF 文件不存在{pdf_path}) os.makedirs(output_dir, exist_okTrue) # 2. 打开 PDF 文档 doc fitz.open(pdf_path) total_extracted 0 seen_xrefs set() # 3. 遍历每一页 for page_index in range(len(doc)): page doc[page_index] # 获取页面上的图片对象引用列表 image_list page.get_images(fullTrue) if not image_list: continue # 4. 遍历当前页面上的每个图片引用 for img_index, img_info in enumerate(image_list): xref img_info[0] # 如果该图片 xref 已经被提取过跳过避免同一张图重复保存 if xref in seen_xrefs: continue seen_xrefs.add(xref) try: # 5. 提取图片的二进制数据与扩展名信息 base_image doc.extract_image(xref) image_bytes base_image[image] image_ext base_image[ext] image_width base_image[width] image_height base_image[height] # 6. 按尺寸过滤过滤过小的装饰图、图标、背景纹理 if image_width min_width or image_height min_height: print(f跳过过小图片xref{xref}, size{image_width}x{image_height}) continue # 7. 构造输出文件名 filename fpage{page_index 1}_img{img_index 1}_xref{xref}.{image_ext} filepath os.path.join(output_dir, filename) # 8. 保存图片 with open(filepath, wb) as img_file: img_file.write(image_bytes) print(f已提取{filepath} ({image_width}x{image_height}, {len(image_bytes)} bytes)) total_extracted 1 except Exception as exc: print(f提取第 {page_index 1} 页图片失败xref{xref}错误{exc}) doc.close() print(f\n提取完成共保存 {total_extracted} 张不重复图片到{output_dir}) return total_extracted if __name__ __main__: if len(sys.argv) 3: print(用法python extract_pdf_images.py input.pdf output_dir) sys.exit(1) input_pdf sys.argv[1] output_folder sys.argv[2] extract_images_from_pdf(input_pdf, output_folder)4.2 代码关键逻辑解释这份脚本并不复杂但每个环节都针对真实项目中反复出现的问题做了处理。第 2 步fitz.open()打开 PDF 文档这是所有操作的前提。在较新版本的 PyMuPDF 中打开文件句柄时建议直接传入路径代码执行结束后调用doc.close()释放资源。第 3 步page.get_images(fullTrue)返回当前页面的图片引用列表。每个元素是类似这样的元组(175, 0, 303, 230, 8, Image, , XObject)第一个元素就是 xref交叉引用编号它是图片对象在 PDF 内部资源字典中的标识。同一个图片如果被多页复用xref 往往是相同的所以代码里用seen_xrefs集合去重避免提取出几十张重复的同一 Logo。第 5 步doc.extract_image(xref)是 PyMuPDF 最核心的 API它返回一个字典包含图片的原始字节image、扩展名ext、宽高width、height等信息。使用这个 API 提取出来的图片是 PDF 文档中内嵌资源的原始质量不会因为页面渲染而损失。第 6 步min_width和min_height参数主要用于过滤很小的装饰图像。比如 PDF 页面底部的小 Logo、背景水印、列表项目符号这些通常不是你要找的核心图片保留反而会让输出目录变得杂乱。4.3 运行方式与预期结果准备一个包含图片的 PDF 文件例如sample.pdf然后执行python extract_pdf_images.py sample.pdf ./output运行正常的预期输出类似已提取./output/page1_img1_xref6.png (1200x800, 432567 bytes) 已提取./output/page2_img1_xref12.jpg (800x600, 102456 bytes) 已提取./output/page2_img2_xref13.png (640x480, 87321 bytes) 提取完成共保存 3 张不重复图片到./output此时你打开输出目录应当能看到对应图片文件。用图片查看器随机打开一张图片确认它与你从 PDF 阅读器看到的插图一致。如果图片内容能正常显示说明提取流程已经跑通。5. 其他方案适用场景pdfplumber 与 pdf2image 完整示例主流程已经足够应对一大批 PDF但为了处理边缘情况有必要了解另外两种思路。5.1 pdfplumber同时获取图片与位置信息pdfplumber 的典型卖点是它的“对象模型”。它把 PDF 里的图片当作page.images列表中的对象处理每个对象包含左上角坐标x0、top右下角坐标x1、bottom以及相关图片流数据。如果你想知道某张图片在页面上的大致位置它能提供清晰答案。# -*- coding: utf-8 -*- import pdfplumber with pdfplumber.open(sample.pdf) as pdf: for page_index, page in enumerate(pdf.pages): images page.images if not images: continue print(f第 {page_index 1} 页包含 {len(images)} 张图片) for pos, img in enumerate(images): x0 img.get(x0, 0) y0 img.get(top, 0) x1 img.get(x1, 0) y1 img.get(bottom, 0) print(f 图片 {pos 1}: 坐标区域 ({x0:.1f}, {y0:.1f}) - ({x1:.1f}, {y1:.1f}))有的 pdfplumber 版本对图片对象的底层字段不同不同版本的字典键可能略有差异。实际使用时建议用交互式环境打印page.images[0].keys()查看字段。这个方案的适用场景是你打算按坐标从页面中裁剪图片或需要根据图片位置重排文档。需要注意pdfplumber 重点在于“理解页面结构”并不侧重“把原始图片字节导出”。如果你用了 pdfplumber 去取图片数据发现比较复杂很可能是因为你选错了工具。5.2 pdf2image扫描件和“所见即所得”的兜底方案扫描版 PDF 的本质是什么是一整页的位图被 PDF 外壳包了起来。这种文件没有所谓的“inner image object”可供你逐个提取因为每页就是一张大图。此时更合理的方式是把整页 PDF 渲染成图像。# -*- coding: utf-8 -*- import os import pdf2image poppler_path rC:\poppler\bin # Windows 用户配置macOS/Linux 可留空 output_dir ./scan_output os.makedirs(output_dir, exist_okTrue) images pdf2image.convert_from_path( scan.pdf, dpi200, output_folderNone, poppler_pathpoppler_path if os.path.exists(poppler_path) else None, ) for i, image in enumerate(images): image.save(os.path.join(output_dir, fscan_page_{i 1}.png), PNG) print(f已保存第 {i 1} 页图片)这个方案的价值不在“提取嵌入图片”而在于当提取嵌入对象行不通时你至少能拿到页面级别的渲染图——文字、图表、印章、签名都会原样保留。从实际业务看合同、档案、票据类的扫描 PDF基本都是用这种方式批量转成图片存档。6. 进阶过滤、命名与批量工程化处理跑通最小示例后接下来是被很多人忽略但无比关键的工程化处理。因为手工整理一批 PDF 也许能靠手动但成百上千个 PDF 时问题就变成了输出目录里图片命名是否可读、有没有大量垃圾水印和小图干扰、重复图片是否占满磁盘。6.1 用图片尺寸和类型做过滤前面脚本里的 min_width 参数已经解决了部分小图过滤问题。但在很多图解书籍里正文配图一般在 300px 以上而图标只有几十像素因此可以调节至合适阈值python extract_pdf_images.py sample.pdf ./output 300 200如果你想同时限制只保存 JPG 和 PNG# 在上方循环里继续使用 base_image 的 ext 字段 if image_ext.lower() not in (jpg, jpeg, png): continue6.2 分析重复图片Logitech 标志、页眉背景这类图片在同一 PDF 中会被反复引用。xref 去重解决的是“引用相同”的重复但也有另一种情况从不同来源或不同页码插入的相同图片xref 不一样。更严格的相似度去重可以使用哈希算法判断。如果是大规模批处理最省时间的办法是先不保存图片而是将所有图片的 md5 记录到一个集合中import hashlib def cal_md5(data: bytes) - str: return hashlib.md5(data).hexdigest()然后将每个图片的 md5 与已保存列表比对若完全重复则跳过。6.3 输出目录与文件命名策略使用带页码的文件名如 page1_img1_xref6.png会让人很快定位到原始来源。但如果单页图片很多建议补充图片在页面上的像素位置前缀# 文件路径build_filename.py img_rect page.get_image_bbox(img) # 获取图片在页面上的区域 filename fp{page_index 1}_x{int(img_rect.x0)}_y{int(img_rect.y0)}_{xref}.{image_ext}这样做的好处是当目录里有上百张图片时你可以知道某张图原本位于左上方还是右下方这对画册排版还原场景尤其有用。6.4 批量处理整个目录import glob for pdf_file in glob.glob(reports/*.pdf): basename os.path.splitext(os.path.basename(pdf_file))[0] extract_images_from_pdf(pdf_file, os.path.join(output, basename))批量执行时有两点建议处理每个文件时单独输出到一个子目录捕获并记录失败文件而不是让脚本中断。使用try...except包裹单文件处理会比较稳妥。7. 运行结果与效果验证如何判断提取成功不少新手在提取后只看到“代码跑通了”却没法验证图片数量是否正确。这里给一套简单有效的验证流程。7.1 人工抽查用 PDF 阅读器先打开 PDF例如找到第 3 页那里有 2 张插图然后检查输出目录发现第 3 页对应文件名中有page3_img1和page3_img2并且内容一致说明基础提取成功。7.2 图片文件完整性检查用 Python 遍历输出目录统计图片数量、文件大小并尝试用 PIL 打开查看是否有损坏文件from PIL import Image import os count 0 for filename in os.listdir(./output): filepath os.path.join(./output, filename) try: with Image.open(filepath) as im: im.verify() count 1 except Exception as exc: print(f文件可能损坏{filename}错误{exc}) print(f共验证 {count} 张图片)如果 PIL 尚未安装执行pip install pillow。7.3 与页面渲染结果比对需要怀疑提取是否缺少内容时可以用 PyMuPDF 把某一页渲染成整页图片然后与提取出的单张图片做视觉对比page fitz.open(sample.pdf)[2] pix page.get_pixmap(dpi150) pix.save(page3_render.png)这不是为了日常运行而是在你怀疑“页面上的某张图好像没提取出来”时的排查手段。如果页面图片上确实有图但渲染图里有、内嵌图片列表里却没有就说明该图可能不是嵌入对象而是由矢量路径绘制而成或来自某个 OCG可选内容组图层。8. 常见问题与问题排查思路这里整理运行过程中最高频的几个问题建议直接收藏这张表。问题现象可能原因排查方式解决方案运行报错ModuleNotFoundError: No module named fitz未安装 PyMuPDF 或安装后导入名错误执行pip show PyMuPDF检查安装安装 PyMuPDF代码中使用import fitz能提取但输出的是整张页面图片页面本身是扫描件内嵌元素就是整页位图用 PDF 阅读器放大查看是否能选中图片文字改为 pdf2image 页面渲染方案或用 OCR 进一步处理PDF 有图片但提取数量为 0图片可能是矢量图形、FlateDecode 内嵌的特殊对象查看page.get_drawings()是否有绘制对象用页面渲染后裁剪或整页转图提取出的图片很模糊PDF 里嵌入的图片原始分辨率就不高缩放只是视觉放大查看图片宽高数值无法从低分辨率 PDF 中凭空恢复高分辨率建议删除原图并重新排版图片文件大小异常小且打不开部分 PDF 图片使用未解压的位图extract_image 返回数据不完整检查 output 目录用 PIL verify 验证尝试将 xref 图片写入后用 pdfimages 或更换解析库同一张图被重复提取多次图片在不同页面反复引用且符合不同上下文打印 xref 列表分析使用 xref 去重或使用 md5 去重运行时内存占用过高大尺寸 PDF、几百个高分辨率图片读取查看任务管理器或命令行监测分批处理、控制 dpi、优先提取后立即写入文件Windows 下 pdf2image 报缺 poppler未安装或未配置 poppler 环境变量检查 PATH 中是否有 bin 目录下载 poppler 配置 PATH 后重启终端针对其中最容易误判的一种情况补充说明有人用完整脚本提取一本扫描版 PDF 时发现每个页面导出的图片与页面内容一致且一张 PDF 提取了几十张大图。许多教程把这直接归为“失败”其实它反而印证了文件的扫描属性。此时如果项目目标是提取图片那结果通常已经是对的——你要的“图片”本来就是一整页内容除非你想从整页中进一步裁剪图片区域这时才需要借助文本 OCR 和图像检测。9. Python 提取 PDF 图片的最佳实践与工程建议当脚本运行到第 20 个 PDF 后很多人才会意识到代码不是最麻烦的部分真正影响体验的是设计上的细节。以下建议来自日常项目经验可以直接应用到你的批处理流程中。9.1 先做小样本预览再跑全量不要一上来就处理整个文件夹。先拿 1 到 2 个代表性文件跑通查看提取的图片数量、尺寸和种类确认 PDF 中没有大量装饰性图形干扰再扩大到全量。这一步能避免你生成几百个无意义的目录。9.2 处理文件时的异常隔离单一 PDF 的损坏不应中断整个批处理。把单文件处理封装成函数使用 try/except 捕获异常将错误信息记录到日志文件而不是直接打印到屏幕。9.3 对输出目录做归档清理多次运行时建议每次都新建带时间戳的输出目录from datetime import datetime timestamp datetime.now().strftime(%Y%m%d_%H%M%S) output_dir fextracted_{timestamp}这样不会覆盖上一次提取结果方便对比不同参数下的效果。如果磁盘空间紧张可以在确认结果后删除之前目录。9.4 尽量保持图片原格式而不是统一转 JPGPDF 中嵌入的图片可能是 PNG、JPEG 甚至 JPEG2000。一些代码为了方便直接按固定扩展名保存会导致文件格式损坏。建议始终使用extract_image返回的 ext 字段确保数据与扩展名匹配。9.5 文件名请带上页码与坐标信息当输出有几十张图而且需要根据 Word 或 PPT 的配图顺序排布时页码是唯一可回溯信息。带坐标前缀则让后处理变得容易例如可以按从上到下、从左到右的顺序重命名。9.6 涉及版权与使用边界从 PDF 中提取图片会涉及文档版权。提取和保存仅应发生在你有权处理的文件范围内例如自己制作的工作文档、已获授权的数据、公开且允许二次引用的论文配图。不要把自动提取图片做成绕过版权保护的工具也不要用在未授权数据上。文档被加密保护时代码也可能无法读取嵌入图片这是正常现象不能视为漏洞去绕过。对外分发时需确认图片的来源许可是否允许再次使用。10. 总结与下一步学习方向本文从“为什么提取 PDF 图片会翻车”讲起对比了图像对象提取和页面渲染两种思路给出了基于 PyMuPDF 的完整提取脚本并用 pdfplumber 与 pdf2image 做了互补最后覆盖了过滤、批量命名、常见异常和版权边界。读到这里最值得记住的判断是遇到提取图片的需求先弄清楚 PDF 的生成方式再决定用哪个工具。如果文档是排版输出且包含独立图片选择 PyMuPDFextract_image()能看到比较好的效果如果遇到扫描件则直接用 pdf2image 把页面变成图像而不是纠结“为什么没有对象让你提取”。如果你接下来想继续深入这个方向可以参考这几条路径学习 PyMuPDF 的get_image_bbox与page.get_drawings()理解 PDF 页面对象从坐标到渲染的完整过程。研究与图片坐标结合的 OCR用 Tesseract 识别扫描图片中的文字区域再用坐标系组合成可搜索 PDF。尝试用对象检测模型从渲染后的页面图片中自动定位插图区域需要提取的大型复杂 PDF 会自动分割出候选区域。了解 PDF 内部结构Content Stream、Object Dictionary、XObject能在解析引擎报错时快速定位问题层级。如果这篇文章能帮你把“从 PDF 中提取图片”从手工截图提升到可批量执行的自动化脚本那它就算达到了目的。建议先把小脚本跑通一次再根据文件类型慢慢调整过滤参数。