ARTICLE DETAIL

建站实战干货

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

Python批量提取PDF图片:工具选择与代码实战

2026/9/5 21:55:11 拓冰建站 浏览量
Python批量提取PDF图片:工具选择与代码实战 最近有朋友在处理 PDF 资料时遇到一个麻烦文件里插图很多想存成独立图片又不想装一堆图形软件更不想一张张截图。这类需求其实很容易用 Python 解决而且可用的库不少常见的有 PyMuPDF、pdfplumber、pypdf 等。本文会直接按“选型 - 装环境 - 写代码 - 批量跑 - 排查问题”的顺序展开目标是让你拿到一段可靠代码就能把 PDF 中的图片批量提取到本地。先说结论如果只是想把 PDF 里嵌的位图完整抽出来优先用 PyMuPDF它内置了get_page_images和extract_image流程简洁还支持 JPEG、PNG、JBIG2 等常见格式的解码如果需要同时知道图片在页面上的位置、大小或者想按版面区域裁剪用 pdfplumber 更顺手如果遇到的是扫描型 PDF则通常需要先把整页渲染成图像再结合 OCR 或其他工具处理。下面这套内容会覆盖这些方法并给出可直接套用的代码。这里要说明一点图片提取本身不挑显卡普通 CPU 就能完成除非后续接 OCR 模型识别图片内容才会用到 GPU。所以不要被“Python 提取 PDF 图片”这种需求吓到只要按步骤装好依赖一条命令行就能处理大量文件。对于关心批量和自动化的读者最后还会给一套目录批处理示例。1. 为什么提取 PDF 图片不是简单的“解压”先理解一下 PDF 的结构会更方便排查问题。PDF 并不像 Word 那样把图片作为“附件”挂在文档里而是将图片作为页面内容流的资源对象保存位置、尺寸、颜色空间、压缩方式等信息。图片可能以 XObject 形式嵌入也可能以内联图像形式直接写在流里所以不能用一个普通的解压工具把所有资源列表倒出来。绝大多数提取工具的思路都是先读取页面再从页面资源中定位图片对象。对象里保存的可能是原始编码的 JPEG 流也可能是经过 FlateDecode 压缩后的图像数据甚至可能是带 alpha 遮罩的透明图片。PyMuPDF 的extract_image做了底层解码会把对象解析成可直接保存的文件字节因此从使用角度来说是性价比最高的一条路。如果你的目标是“质量越高越好”“不要失真”还需要留意 PDF 内部可能对图片做了二次采样或色彩空间转换。有些页面显示效果很好但内嵌图片本身分辨率很低放大导出后依然模糊。这是 PDF 文件本身的问题不是提取工具的过错。遇到这种情况可以结合整页高清渲染方案或与原稿校对。2. 提取方案的核心能力速览能力项说明主要场景从 PDF 中批量保存嵌入图片、按版面区域切图、扫描页高清转图片依赖库PyMuPDF、pdfplumber、pypdf按需选择运行平台Windows、Linux、macOS理论上均可是否依赖 GPU不依赖提取过程 CPU 即可完成是否支持批量支持自行遍历 PDF 目录即可是否支持 API无固定 API 服务可封装成命令行脚本供内部调用关键输出原始图片文件、裁剪图片、整页渲染 PNG常见限制无法提取纯矢量图图片被二次采样时需要额外渲染方案选择库时可以参考下表工具优点不适合的场景PyMuPDF提取速度快内置图像对象解析代码量少需要获得图片相对文本段落的复杂版面语义时不够灵活pdfplumber能拿到页面文本、表格、图片位置容易做版面规则对非常复杂的 PDF 图像对象需要结合 PyMuPDF 或原始解析pypdf纯 Python安装轻量适合简单遍历处理某些压缩流、高级滤镜图像时能力有限3. 适用场景与安全边界这类工具的典型使用场景包括几个方向从产品说明书、论文预印本、课件 PPT 导出的 PDF 中整理配图从素材 PDF 中批量抽取高清底图把扫描书页按页转成图片方便接入 OCR 或做版面标注给自动化文档处理流程增加“图片归档”能力。如果你需要处理的是大量论文或扫描件建议把提取脚本和后续识别脚本拆开先做一轮过滤。使用边界也很清楚不要用脚本提取不拥有版权的加密 PDF 内容也不要批量下载并拆分他人付费资料中的图片更不要提取涉及个人隐私的证件扫描件后随意传播。虽然本文介绍的是技术方案但技术可以用来处理合法数据也可以用来违规操作。合法授权和隐私保护必须由使用者自己确认。4. 环境准备与依赖安装在开始之前建议先确认 Python 版本。近年来较通用的 Python 3.9 到 3.12 环境都可以运行这些库3.12 以上版本安装某些旧版本依赖时可能出现编译兼容问题更稳妥的方式是使用虚拟环境。python -m venv venvWindows 下激活虚拟环境venv\Scripts\activateLinux 和 macOS 下激活虚拟环境source venv/bin/activate然后安装依赖pip install --upgrade pip pip install pymupdf pdfplumber如果还需要处理 OCR 或图像裁剪可继续安装 Pillowpypdf 也可以作为备用pip install Pillow pypdf安装完成后建议建一个工作目录把 PDF 文件和输出目录分开管理pdf_image_extract/ ├── venv/ ├── input/ │ └── sample.pdf ├── output/ │ └── extracted/ └── extract_images.py这样的目录结构在批量任务里尤其重要避免脚本生成的图片和后续文件混在一起。5. 方案一PyMuPDF 提取嵌入图片这里先提供一套完整脚本。它的逻辑很简单按页遍历通过page.get_images(fullTrue)拿到当前页面引用的图片信息再用doc.extract_image(xref)解出图片字节和扩展名。为了避免一张图片在多页中被反复使用导致重复导出脚本会把已经处理过的 xref 记录下来xref 是 PDF 内部对象的引用编号同一张图片即使出现在多个页面xref 通常也是同一个。import fitz from pathlib import Path def extract_images_from_pdf(pdf_path, out_diroutput/extracted, min_width200, min_height200): 从 PDF 中提取嵌入图片支持宽高过滤。 out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) doc fitz.open(pdf_path) seen_xrefs set() image_no 0 for page_index in range(doc.page_count): page doc[page_index] images page.get_images(fullTrue) if not images: continue for image_info in images: xref image_info[0] if xref in seen_xrefs: continue seen_xrefs.add(xref) try: ext_image doc.extract_image(xref) except Exception as exc: print(f第 {page_index 1} 页的 xref{xref} 提取失败{exc}) continue width ext_image.get(width, 0) height ext_image.get(height, 0) # 小到不包含有效内容的图片可以跳过数值按需调整 if width min_width or height min_height: continue ext ext_image.get(ext, png) image_bytes ext_image[image] image_no 1 image_path out_dir / fpdf_image_{image_no:04d}.{ext} image_path.write_bytes(image_bytes) print(f已保存{image_path}尺寸{width}x{height}) doc.close() print(f完成共导出 {image_no} 张图片。) if __name__ __main__: extract_images_from_pdf(input/sample.pdf)运行脚本python extract_images.py如果当前目录下的input/sample.pdf不存在或者目录结构不同需要相应修改脚本里的路径参数。这里判断成功有三个标准输出文件夹里出现了文件文件名能被常规看图软件打开图片尺寸和 PDF 原图中的实际像素尺寸一致。关于min_width和min_height两个参数建议第一次先设为0跑一轮看看 PDF 里到底有多少图片、尺寸分布如何再决定要不要过滤。有些论文图片虽然显示出来很小但原始分辨率很高过早过滤会丢素材。6. 方案二pdfplumber 提取版面图片区域PyMuPDF 方案适合“把图片文件直接抠出来”。但有些需求不是抠原图而是“把页面某个区域裁剪出来”例如保留图片周围的图注、保留表格线、或者按视觉坐标把某一栏的图片截图存下来。这时 pdfplumber 会更直观因为它能给出图片在页面上的坐标。import pdfplumber from PIL import Image pdf_path input/sample.pdf with pdfplumber.open(pdf_path) as pdf: print(总页数, len(pdf.pages)) for page_index, page in enumerate(pdf.pages, start1): images page.images if not images: continue for img in images: x0 img.get(x0) top img.get(top) x1 img.get(x1) bottom img.get(bottom) # img 中可能带 srcsize也就是图片原始像素尺寸 src_width, src_height img.get(srcsize, (0, 0)) print( f第 {page_index} 页图片 f坐标({x0:.1f}, {top:.1f}, {x1:.1f}, {bottom:.1f}) f原始尺寸{src_width}x{src_height} )这里page.images是页面图片对象的列表属性里包含位置信息。要注意的是pdfplumber 的坐标系统以页面左上角为原点x0、top、x1、bottom分别对应图片的左右边界和上下边界。这样我们就可以按坐标裁剪。如果需要把定位到的图片区域保存为截图可以用 pdfplumber 的page.to_image()或 Pillow 组合处理。下面是裁剪单页内第一个图片区域的示例import pdfplumber with pdfplumber.open(input/sample.pdf) as pdf: page pdf.pages[0] images page.images if images: # 取第一张图片的坐标 bbox (images[0][x0], images[0][top], images[0][x1], images[0][bottom]) crop page.crop(bbox) im crop.to_image(resolution150) im.save(output/first_image_area.png)这种方式的优点是不依赖原始嵌入格式而是按 PDF 渲染效果截图。适合对截图要求不高、主要需要“某块区域长什么样”的场景。缺点也很明显如果图片本身在 PDF 里显示尺寸较小即使把分辨率调高导出的有效信息像素还是有限。7. 方案三扫描型 PDF 批量转整页图像有些 PDF 本质上是扫描件每一页就是一张图片。针对这类文件直接用page.get_images(fullTrue)也能导出嵌入的整页位图但有时候 PDF 生产者会把页面切割成多块图块提取出来反而是碎片。更常见的做法是把每页直接渲染成 PNG这样页面内容不会被拆散。import fitz from pathlib import Path source_pdf input/scanned_book.pdf output_dir Path(output/pages) output_dir.mkdir(parentsTrue, exist_okTrue) doc fitz.open(source_pdf) for page_index in range(doc.page_count): page doc[page_index] # 分辨率按需调整推荐 150 到 300 dpi pixmap page.get_pixmap(dpi300) output_path output_dir / fpage_{page_index 1:04d}.png pixmap.save(output_path) print(f已保存{output_path}大小{pixmap.width}x{pixmap.height}) doc.close()这个方案会遇到一个问题输出的是整页图像如果扫描件里包含图片和文字混排那么后续需要通过 OCR 才能知道文本内容OCR 工具可以用 PaddleOCR 或 Tesseract但需要单独安装识别模型。这里先不做扩展核心思路是先把页面转化为干净的图像再让识别任务按页排队。8. 批量目录任务与命令行封装处理单份 PDF 只是第一步。实际工作中经常遇到一个文件夹里有几十个 PDF都希望能自动提取图片。这时建议把脚本封装成带参数的命令行小工具再配合批量遍历。可以先封装一个便于复用的函数from pathlib import Path import fitz def extract_images_to_dir(pdf_path, out_dir, min_width0, min_height0): out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) doc fitz.open(pdf_path) seen_xrefs set() image_no 0 for page_index in range(doc.page_count): for image_info in doc[page_index].get_images(fullTrue): xref image_info[0] if xref in seen_xrefs: continue seen_xrefs.add(xref) try: img_data doc.extract_image(xref) except Exception as exc: print(f{pdf_path} 页 {page_index 1} 素材失败{exc}) continue width img_data.get(width, 0) height img_data.get(height, 0) if width min_width or height min_height: continue ext img_data.get(ext, png) image_bytes img_data[image] image_no 1 target out_dir / f{Path(pdf_path).stem}_{image_no:03d}.{ext} target.write_bytes(image_bytes) doc.close() return image_no然后写一个遍历目录的入口import argparse from pathlib import Path from extract_images import extract_images_to_dir def run_batch(input_dir, output_dir, min_width0, min_height0): input_path Path(input_dir) output_path Path(output_dir) pdf_files list(input_path.rglob(*.pdf)) total 0 for pdf_file in pdf_files: relative_output output_path / pdf_file.parent.relative_to(input_path) count extract_images_to_dir( pdf_file, relative_output, min_widthmin_width, min_heightmin_height, ) total count print(f{pdf_file} 导出 {count} 张累计 {total} 张) if __name__ __main__: parser argparse.ArgumentParser(description批量提取 PDF 图片) parser.add_argument(--input, defaultinput, helpPDF 目录) parser.add_argument(--output, defaultoutput, help输出目录) parser.add_argument(--min-width, typeint, default0) parser.add_argument(--min-height, typeint, default0) args parser.parse_args() run_batch(args.input, args.output, args.min_width, args.min_height)执行方式python batch_extract.py --input ./pdf_files --output ./extracted --min-width 100 --min-height 100这里要注意一个问题批量任务建议每个 PDF 单独写日志不能只靠终端打印。尤其当脚本处理上百个文件时终端输出很容易滚动丢失出错信息无法回溯。简单做法是给run_batch增加一个写入文本日志的逻辑把每个文件的处理结果追加到一个run.log这样第二天可以看到完整记录。9. 资源占用、图片质量与去重整理图片提取主要消耗 CPU 和内存GPU 在这里不是必需品。如果你处理的 PDF 单本很小内存占用通常可以忽略但如果遇到上千页的大型 PDF包含大量高清大图需要留意fitz.open(pdf_path)会持有文档对象脚本逐页遍历时提取出的图片字节会暂时存在内存里。图片数量特别多时建议处理完一张就立即写入文件并及时释放变量。上面代码里已经直接通过write_bytes落盘整体是稳定的。性能观察可以从两个角度入手一是单页图片数量二是图片压缩复杂度。普通文本 PDF 含少量图片时几十页也能在数秒内完成扫描件转整页 PNG 时才需要关注渲染时长因为 300 dpi 渲染会明显增加 CPU 计算量。为了验证性能可以简单记录运行时间time python batch_extract.py --input ./pdf_files --output ./extracted如果提取后发现有大量重复图片可以增加图片哈希去重。同一个 PDF 里经常有重复 logo、背景图或重复页眉完全保留会浪费存储空间。常用做法是计算图片内容的前若干字节哈希例如sha1然后维护一个哈希集合import hashlib def sha1_file_bytes(data): return hashlib.sha1(data).hexdigest()在保存之前先判断哈希是否已存在。要注意的是哈希去重只对完全相同的文件字节有效如果 PDF 内部对同一张图做了二次编码字节可能不同但视觉效果一样这种情况需要更复杂的感知哈希去重这里不展开。图片质量方面extract_image导出的是 PDF 内嵌的原始图像数据。如果 PDF 中嵌的是 JPEG导出后依然是 JPEG如果是带透明通道的图片可能需要额外注意 alpha 遮罩PyMuPDF 会把部分遮罩作为独立图像对象保存。对绝大多数场景来说导出的图片已经可以直接使用。10. 常见问题与排查建议问题现象可能原因排查方式解决方案import fitz报错PyMuPDF 未安装或虚拟环境未激活执行pip show pymupdf重新安装pymupdf提取出的图片重复很多同一 xref 被多页引用或 logo 重复打印 xref 和图片尺寸使用 xref 集合或哈希去重某张图片导出后打不开图片对象包含遮罩或异常滤镜查看extract_image抛出的异常改用page.get_pixmap区域渲染提取结果只有零张页面内容是扫描整页图或纯文字先渲染整页确认使用get_pixmap(dpi300)另存页面输出图形颜色偏色PDF 使用特定颜色空间对比原始 PDF 显示效果用渲染截图方式替代直接取流处理大 PDF 时内存上涨使用extract_image后未立刻释放观察进程内存变化分文件处理加 try/finally 清理扫描件提取出来是多块碎片PDF 生产者将页面切成多个 XObject查看每页图片数量用整页渲染裁剪替代直接取图批量脚本中途崩溃单页出现异常导致进程终止查看终端错误信息给单页处理加异常捕获代码中出现import fitz无法导入的时候先检查是否在虚拟环境里因为有些环境里安装包名为pymupdf代码导入名却是fitz。新版 PyMuPDF 也可以使用import pymupdf但老项目更常见的是import fitz为了兼容建议统一按库文档推荐的写法。如果某些 PDF 是从网页打印导出页面里可能把图片嵌成超大尺寸背景导出的原图会很大。看到有图片尺寸达到几万像素时不要惊讶这是文件本身包含的高分辨率底图可以先记录尺寸再决定是否导出。11. 合规使用与运行建议无论处理的是公开资料、内部文档还是从业务系统导出的报告都要先确认是否具备处理权限。提取图片虽然只是一种文件操作但它可以让文档中的内容脱离原文件一旦涉及版权图片、人脸照片、身份证扫描件、商业设计素材风险就会明显增加。建议在项目目录里保留一个 README 说明数据来源、处理目的、输出文件用途和保留期限别把临时脚本直接用到敏感生产数据上。实操角度有几条建议很值得养成习惯第一次处理某个 PDF 时先用较小文件跑通流程再切到批量输出文件按“页码 序号 扩展名”命名不要把图片全部堆到一个没有结果的output/目录下时间久了很难回溯脚本完成后保留一份原始 PDF 的目录和文件名对照采用哈希或文件名映射把输出图片对应到源 PDF。整套流程投入很小但能让批量提取任务长期可维护。更进一步的扩展方向包括提取图片后自动识别图片中的文字、用版面和坐标信息把论文里的彩色插图与公式图分开保存、把批量提取封装成内部 API 服务、给前端页面提供 PDF 图片预览缩略图。对普通文档处理需求来说本文这套 PyMuPDF 与 pdfplumber 的组合已经能覆盖绝大多数场景建议先把提取脚本跑通再按自己业务调整参数。