ARTICLE DETAIL

建站实战干货

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

Python提取PDF图片:用PyMuPDF批量导出原图完整指南

2026/9/5 21:55:11 拓冰建站 浏览量
Python提取PDF图片:用PyMuPDF批量导出原图完整指南 很多做文档处理的朋友都会遇到这种需求收到一份 PDF里面有几张插图、架构图或截图希望把它们单独保存成 PNG 或 JPG 文件方便写文章、做 PPT 或走审批流程。如果直接使用 PDF 阅读器“另存为图片”往往只能把整页保存成一张图后期还要裁剪如果截图保存图片质量又会被压缩尺寸不理想。自己用 Python 写一个小脚本就能很干净地把 PDF 里的图片批量提取出来保留原始尺寸和画质顺便还可以处理重复图片、过滤小图标。本文将围绕 python 提取 PDF 中的图片 这个需求展开从环境准备、第三方库选型、完整代码、常见报错到工程建议一次讲清楚。1. 为什么需要提取 PDF 中的图片1.1 先理解 PDF 图片存储方式PDF 文件本质上是一种容器格式里面可以包含文本、字体、矢量图形、注释、多媒体资源以及嵌入的图片对象。每一张进入 PDF 的图片通常都会被保存为一个独立的“图片对象”并分配一个称为 xref 的内部引用编号。这意味着PDF 文件中嵌入了多少张图片我们是可以枚举出来的而且大部分情况下可以直接取得原始图片数据而不是对页面做屏幕截图。不过要注意一个常见误区你在页面上看到的“图形”不一定都是位图。比如流程图、设计稿有时候会用矢量绘制这种对象没有对应 PNG/JPG 原始文件单独“提取位图”就不适用。1.2 常见应用场景Python 提取 PDF 图片的应用场景很丰富从产品手册中提取架构图用于写技术方案。从文献 PDF 中批量保存实验图片和数据截图。从电子合同扫描件中提取身份证复印件、印章图片。从 PPT/Word 导出的 PDF 中回收素材图。对大量 PDF 文件做自动化归档时单独存放图片资产。1.3 哪些场景不适合“提取图片”如果 PDF 本身是通过扫描仪生成的扫描件每一页其实都是整页大图提取后得到的可能是一张超大页面图而不是里面独立的一张表格或图片。如果希望从扫描件中识别文字并提取关键区域需要使用 OCR 技术这属于另一类工程问题本文会在第 6 节补充说明。2. 环境准备与依赖安装2.1 安装 Python如果电脑还没有安装 Python可以去 Python 官网下载安装包。建议使用 Python 3.8 及以上版本下面的依赖和代码在 Python 3.10 环境下表现比较稳定。安装完成后在终端或命令行执行python --version能看到版本号就说明安装成功。不同操作系统注意命令差异Windows 有时是pythonLinux/macOS 有时是python3。2.2 创建虚拟环境推荐隔离依赖是一个很好的习惯避免把整个 Python 环境搞乱。这里以一个pdf_image_demo目录为例mkdir pdf_image_demo cd pdf_image_demo python -m venv venvWindows 系统激活虚拟环境venv\Scripts\activateLinux 或 macOS 激活虚拟环境source venv/bin/activate激活后终端前面会出现(venv)提示符。2.3 安装 PyMuPDF本文主推使用的库是 PyMuPDF。它能解析 PDF 中的图片对象并且可以非常简单地导出原图。pip install pymupdf这个库安装后在代码里既可以使用import fitz也可以使用import pymupdf。为了兼容老项目下面代码统一使用import fitz这是 PyMuPDF 早期保留的模块名。如果你使用的操作系统是 Windows个别旧版本可能出现 DLL 缺失问题升级 PyMuPDF 到较新版本通常就能解决。这里不写死具体版本号安装时建议直接安装最新版本。2.4 准备测试 PDF本文后面的示例都会读取一个 PDF 文件你可以随意找一个包含图片的 PDF 来测试。如果没有可以把 PNG/JPG 插入到 PDF 中生成一个测试文件。下面这段代码生成一份带图片的示例 PDF# 文件路径create_sample_pdf.py import fitz doc fitz.open() page doc.new_page(width595, height842) # 这里换成你自己本地的图片路径 page.insert_image(fitz.Rect(50, 120, 350, 300), filenameblog_logo.png) doc.save(sample_with_image.pdf) doc.close() print(已生成 sample_with_image.pdf)代码中fitz.Rect(50, 120, 350, 300)表示图片在页面中的放置区域参数依次为左上角 x、y、右下角 x、y。3. 主流方案对比用哪个库最适合提取图片实现 PDF 图片提取的常见工具包括 PyMuPDF、pdfplumber、PyPDF2/pypdf以及系统命令行工具。下面我结合实际使用体验做一个横向比较。工具的库能否直接取到原始图片字节使用体验适合场景PyMuPDFfitz可以API 简洁安装顺手速度较快批量提取 PDF 图片首选pdfplumber不能直接导出原始图片只能获取图片位置适合文本、表格解析需要了解图片在页面上的位置和尺寸PyPDF2 / pypdf需要额外解析过滤器和字节流操作繁琐API 面向 PDF 对象层级学习 PDF 内部结构、做页级操作pdfimagesPoppler可以命令行工具依赖系统安装 Poppler不介意命令行时可用但跨平台稍麻烦3.1 为什么优先选择 PyMuPDFPyMuPDF 对图片对象做了很友好的二次封装。直接调用page.get_images(fullTrue)就能拿到该页面引用的图片引用编号列表再调用doc.extract_image(xref)就可以得到图片字节、扩展名、宽高等信息。最重要的是它返回的是原始图片数据而不是页面渲染截图。对于 JPG 图片导出的文件体积和画面质量和 PDF 内部保存的基本一致。3.2 pdfplumber 适合什么情况pdfplumber 更适合做文本、表格和坐标解析。它提供page.images属性可以得到页面图片的边界框、宽度、高度等位置信息但不能直接像 PyMuPDF 那样拿到原图字节。如果你需要知道“第 3 页右上角有一张图片尺寸为多少”可以使用 pdfplumber如果需要把原图保存下来建议直接结合 PyMuPDF。3.3 为什么不用 PyPDF2 提取图片PyPDF2 的能力更多集中在 PDF 页面合并、拆分、旋转、加密解密。虽然 PDF 中图片在页面资源中可以找到但处理过程要面对 filter 解码、DCTDecode 等底层压缩格式调试成本较高。而 PyMuPDF 已经帮我们处理好了这些细节。所以本文不会花大量篇幅讲 PyPDF2 提取图片毕竟工具选型要讲究效率。4. 完整实战使用 PyMuPDF 提取 PDF 中的图片4.1 最小示例统计 PDF 页面中的图片数量先看一个最简代码搞清楚 PDF 中大概有多少张图片# 文件路径count_images.py import fitz pdf_path sample_with_image.pdf doc fitz.open(pdf_path) total_images 0 for page_index in range(len(doc)): page doc.load_page(page_index) image_list page.get_images(fullTrue) print(f第 {page_index 1} 页包含 {len(image_list)} 张图片) total_images len(image_list) print(f整个 PDF 共检测到 {total_images} 张图片) doc.close()如果 PDF 在第 1 页插入过图片输出结果类似第 1 页包含 1 张图片 整个 PDF 共检测到 1 张图片这里page.get_images(fullTrue)返回一个列表每个元素都是包含图片属性信息的元组。我们可以通过元组的第一个元素拿到xref也就是图片对象在 PDF 内部的引用编号。4.2 批量提取并保存图片原图下面是一个完整的批量提取脚本。它会遍历 PDF 每一页把所有嵌入图片逐张保存为提取图片_页码_序号.扩展名文件。# 文件路径extract_pdf_images.py import fitz from pathlib import Path def extract_images_from_pdf(pdf_path, output_diroutput_images): 从 PDF 中提取所有嵌入图片 :param pdf_path: PDF 文件路径 :param output_dir: 图片输出目录 pdf_path Path(pdf_path) output_dir Path(output_dir) output_dir.mkdir(parentsTrue, exist_okTrue) doc fitz.open(pdf_path) total 0 for page_index in range(len(doc)): page doc.load_page(page_index) image_list page.get_images(fullTrue) for img_index, img in enumerate(image_list): xref img[0] # 图片对象的引用编号 try: base_image doc.extract_image(xref) except Exception as e: print(f提取失败页面{page_index 1}xref{xref}错误{e}) continue image_bytes base_image[image] image_ext base_image[ext] filename f{pdf_path.stem}_第{page_index 1}页_第{img_index 1}张.{image_ext} filepath output_dir / filename with open(filepath, wb) as f: f.write(image_bytes) total 1 print(f已提取{filepath}大小{len(image_bytes)} 字节) doc.close() print(f全部完成共提取 {total} 张图片) if __name__ __main__: extract_images_from_pdf(sample_with_image.pdf)4.3 核心代码说明这段代码有几个地方值得展开解释。page.get_images(fullTrue)返回的每个元组中第一个元素img[0]就是xref。xref 可以理解为图片对象在 PDF 交叉引用表中的地址通过它能唯一定位到这张图片。doc.extract_image(xref)是根据 xref 读取出来的图片信息返回结果是一个字典常用字段包括image图片的二进制字节数据用于写入文件。ext图片扩展名可能是 jpg、png、bmp 等。width图片宽度。height图片高度。如果一张图片同时被多个页面引用get_images会在对应页面继续返回它导致输出文件出现重复。去重策略放在第 5 节说明。代码中使用了pathlib.Path这样输出目录如果不存在会自动创建避免手动建文件夹。4.4 运行与验证在终端执行python extract_pdf_images.py正常情况下输出目录output_images中会出现提取出来的图片文件。如果 PDF 中嵌入的是 JPG保存后可以直接用看图软件打开如果扩展名是jp2、jb2部分普通看图软件可能无法预览建议后续用 PIL 做一次统一格式转换。5. 进阶过滤小图片、去重并规范化输出实际 PDF 中除了正文插图还可能有 logo、装饰线、背景图等小图片。这些图片不一定是我们想要的所以在工程脚本里通常需要增加过滤逻辑。5.1 过滤尺寸过小的图片doc.extract_image(xref)返回结果中带有width和height字段。我们可以只保留宽度和高度都不小于某个阈值的图片例如 50 x 50 像素width base_image[width] height base_image[height] if width 50 or height 50: print(f跳过小图xref{xref}尺寸{width}x{height}) continue这样可以在一定程度上避免把页面 logo 或 CSS 背景图提取出来。5.2 使用 xref 去重同一张图片可能在多页复用。比如每页顶部都有相同的 logo如果按页面遍历就会提取很多重复文件。解决办法是维护一个已经处理过的 xref 集合因为同一个图片对象即使被多个页面引用它的 xref 不变seen_xrefs set() for page_index in range(len(doc)): page doc.load_page(page_index) image_list page.get_images(fullTrue) for img_index, img in enumerate(image_list): xref img[0] if xref in seen_xrefs: continue seen_xrefs.add(xref) base_image doc.extract_image(xref) # 后续保存逻辑相同如果希望更严谨可以在读取图片字节后计算 MD5 哈希再做二次去重。因为有时 PDF 内部会复制同样的图片到不同 xref 中看起来是两张实际内容相同。5.3 规范文件名和使用 PIL 转换统一格式如果希望把所有图片统一保存为 PNG可以结合 Pillow 做转换pip install pillow转换思路如下from PIL import Image import io image_bytes base_image[image] image_ext base_image[ext] # 转成 PNG img Image.open(io.BytesIO(image_bytes)).convert(RGB) output_png_path output_dir / f{name}.png img.save(output_png_path, PNG)如果图片本身包含透明通道直接把convert(RGB)会丢失透明背景。可以把convert(RGB)改成convert(RGBA)再根据保存格式决定是否保留 alpha。对于 JPG 输出格式本身就不支持透明通道必须拼接白色背景这里不做过度展开。5.4 一个相对完整的批量脚本把过滤小图、xref 去重、安全文件名整合后得到以下示例# 文件路径extract_pdf_images_advanced.py import fitz from pathlib import Path from PIL import Image import io def safe_name(name: str) - str: 去掉文件名中的非法字符 for ch in [\\, /, :, *, ?, , , , |, ]: name name.replace(ch, _) return name def extract_images(pdf_path, output_diroutput_images, min_width50, min_height50): pdf_path Path(pdf_path) output_dir Path(output_dir) output_dir.mkdir(parentsTrue, exist_okTrue) doc fitz.open(pdf_path) seen_xrefs set() total 0 for page_index in range(len(doc)): page doc.load_page(page_index) image_list page.get_images(fullTrue) for img_index, img in enumerate(image_list): xref img[0] if xref in seen_xrefs: continue seen_xrefs.add(xref) try: base_image doc.extract_image(xref) except Exception as e: print(f提取失败 xref{xref}{e}) continue width base_image[width] height base_image[height] # 过滤小图 if width min_width or height min_height: print(f跳过小图 xref{xref}尺寸{width}x{height}) continue image_bytes base_image[image] ext base_image[ext] name safe_name(f{pdf_path.stem}_第{page_index 1}页_{width}x{height}) img_path output_dir / f{name}.{ext} with open(img_path, wb) as f: f.write(image_bytes) total 1 print(f已提取{img_path}尺寸{width}x{height}) doc.close() print(f共提取 {total} 张图片) if __name__ __main__: extract_images(sample_with_image.pdf)6. 当 PDF 是扫描件或矢量图形时怎么办6.1 扫描件 PDF依然能提取整页图片扫描件 PDF 通常是把纸质文件扫描成图片后合成的。这种情况下get_images()也能工作但提取出来的往往是整页的大 JPG页面上的文字、印章、表格会合成在同一张图片里并不方便直接复用。如果你真正需要的是“从扫描件中识别并裁剪出关键图”一般路径是将该页渲染成高分辨率 PNG。使用图像处理或 OCR 定位目标区域。按区域坐标裁剪图片。这已经进入 OCR 工程范围不是简单的原图提取。6.2 矢量图get_images 可能返回空如果 PDF 中的图形是用矢量绘图生成的例如设计师用代码绘制的虚线框、矢量图标那么 PDF 内部可能没有嵌入任何位图对象get_images()可能会返回空列表。但页面上确实有内容这时可以整体渲染该页为一张图片# 文件路径render_pdf_page.py import fitz pdf_path sample_with_image.pdf doc fitz.open(pdf_path) page doc.load_page(0) # 2 倍缩放渲染提高清晰度 zoom 2 matrix fitz.Matrix(zoom, zoom) pix page.get_pixmap(matrixmatrix) pix.save(page_1.png) print(已渲染第 1 页为 page_1.png) doc.close()这种做法的缺点是把文字和图片合成一张图后续如果想裁切单个图片需要额外做区域识别。6.3 提取透明通道图片要注意有些 PNG 图片在 PDF 中会带有 SMask透明度遮罩。如果直接使用Pixmap(doc, xref)保存可能会发现背景是黑色或图片通道异常。使用doc.extract_image(xref)在多数情况下已经处理了透明度问题不过如果图片包含 JPEG 曲线透明度建议保存后人工抽检。对于包含 CMYK 颜色的 PDF图片转换后颜色可能偏色。如果需要高质量还原最好在色彩管理方面做进一步校验必要时转换为 RGB 再输出。7. 常见问题与排查思路下面整理几个高频问题如果你在实际运行中遇到类似现象可以参考排查。问题现象常见原因解决思路ModuleNotFoundError: No module named fitz没有安装 PyMuPDF执行 pip install pymupdf提取出的图片扩展名为 unknownPDF 使用特殊压缩格式或非标准编码尝试用 doc.extract_image 获取扩展名或用 Pillow 打开字节流后再保存图片是黑色块或空白图图片带透明度遮罩、ICC 色彩配置文件或特殊颜色空间使用 Pixmap 重新渲染必要时做颜色空间转换图片数量比预想多背景图、重复 logo 也被统计增加按 xref 去重和最小尺寸过滤图片数量比预想少页面图片属于矢量图改用 page.get_pixmap 渲染页面目标区域文件名为中文时乱码终端编码问题Windows 可先执行 chcp 65001或代码内使用 pathlib 代替字符串拼接打开加密 PDF 报错PDF 设置了解密密码在合法授权前提下使用 doc fitz.open(path, password密码)7.1 为什么提取出来多张透明黑色图片这种问题通常涉及 SMask。PyMuPDF 的extract_image()字段中包含smask表示该图片有软遮罩通道。如果场景比较复杂可以直接基于 xref 生成 Pixmap 再拼接 alpha 通道但这已经进入 PDF 底层图像处理范畴普通需求不建议深挖。简单检查代码base_image doc.extract_image(xref) print(smask:, base_image.get(smask)) print(colorspace:, base_image.get(colorspace))如果输出中有 smask 值说明图片带透明层需要保留 PNG 格式或做遮罩合成。7.2 如何解决中文路径打不开 PDFWindows 下中文文件路径偶尔会引发fitz.open报错尤其是在旧版本中。可以用如下方式先读取文件字节再交给 PyMuPDFimport fitz pdf_path 测试文档.pdf with open(pdf_path, rb) as f: file_bytes f.read() doc fitz.open(pdf, file_bytes)这种方式可以有效规避部分系统路径编码问题。7.3 报错“document has not been decrypted”这说明 PDF 有打开密码PyMuPDF 不允许在未提供密码的情况下读取内容。合法操作是在获得授权后传入密码doc fitz.open(protected.pdf, passwordyour_password)再次强调不要对没有权限的加密 PDF 做任何绕过操作技术工具应当用在合法合规的场景。8. 最佳实践与工程落地建议8.1 大文件处理不要一次性遍历过多页面如果 PDF 页数很多、图片很多Python 脚本默认会占用较大内存。建议边遍历边释放对象。例如每处理完一页就可以关闭该页临时资源不过 PyMuPDF 中doc.load_page()返回的页面对象不需要额外 close核心内存消耗主要集中在读取图片字节时。更好的做法是分批处理不要试图把整个 PDF 的所有图片字节都读入列表再写文件。上面的示例已经是一边读取一边写入适合大多数场景。8.2 输出目录和日志设计工程化脚本需要妥善处理输出目录建议统一放在当前项目下的output/子目录并记录提取日志。可以这样实现一个简单的日志追加import logging logging.basicConfig( filenameextract.log, levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s ) logging.info(开始处理 %s, pdf_path)日志能帮助后续排查哪些页面提取失败。8.3 使用临时目录避免半成品污染如果你是先写临时文件、最后统一移动到正式目录可以在异常中断时保留部分结果便于排查。也可以直接用tempfile.TemporaryDirectory()作为中间目录。8.4 遵守文档授权与版权要求这是工程应用中容易被忽略的一点。PDF 里面图片的版权仍然属于原作者或文档发布方。如果你要提取图片用于商业宣传、公开发文、再传播请先确认自己是否拥有相应授权。从技术角度看PyMuPDF 能正常打开、提取内容并不代表我们可以绕过文档的安全限制。如果 PDF 设置了不能复制、不能打印等权限应当遵循文档本身的授权约定。对于加密文档只应在合法授权和正确密码下打开。8.5 输出文件整理策略提取完成后建议写一个统计摘要文件例如summary.csv记录每张图片的来源页、尺寸、输出文件名、原始 xref 等信息。这样可以方便后续人工筛选也方便与设计同事沟通是哪一页的哪张图。简单示例import csv with open(image_summary.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([来源页, xref, 宽度, 高度, 文件名]) writer.writerow([1, 12, 800, 600, img_001_800x600.png])utf-8-sig编码在 Windows Excel 中打开 CSV 时不容易出现中文乱码。8.6 注意命名规范与脚本的复用性在一个真实项目中我建议把核心函数设计成“输入 PDF 路径 输出目录”而不依赖全局变量。这样既能单独运行也能作为模块被 Flask、FastAPI 或自动化脚本调用。可以把extract_images提取到独立文件中例如pdf_image_tool.py然后在另一个入口文件中调用from pdf_image_tool import extract_images extract_images(data/合同1.pdf, output/合同1/)这种设计有利于长期维护。9. 下一步可以学什么本文的核心流程可以概括为四个环节使用 PyMuPDF 打开 PDF、通过page.get_images(fullTrue)枚举页面图片、通过doc.extract_image(xref)取回图片字节、把字节写入文件。只掌握提取图片还不够如果你的工作流中常常见到 PDF还可以继续研究用 PyMuPDF 提取 PDF 文字内容做关键词检索与摘要。用 pdfplumber 读取表格结构把 PDF 表格转换为 Excel。将多张提取出的图片做 MD5 去重并建立资源索引。结合 OCR 处理扫描版 PDF 中的印章和文字区域。对于大多数办公、产品和开发场景PyMuPDF 这一个库已经能覆盖大部分 PDF 操作需求学习成本不算高值得在自己的工具箱里长期使用。如果这一篇对你有帮助可以收藏备用。关于 PDF 图片提取如果你有更特殊的需求比如要保留原图坐标信息、需要合并透明通道也可以从本文的extract_image和 Pixmap 方案继续延伸。