PIL合并JPG图片生成PDF时脚本中途自动终止的解决方案
故障根因
你判断的没错,脚本跑一半被自动终止就是内存占满触发系统OOM(内存不足)查杀机制了。现有代码的核心问题是把所有resize完的图片对象全堆在im_list里驻留内存,等所有图都处理完才一次性写入PDF,图片量稍大时内存占用会随图片数线性上涨,最终超出系统可用阈值被强制结束。
另外还有两处问题会额外推高内存占用:
- 图片、PDF读写对象用完未主动释放,依赖Python自动垃圾回收会导致内存无法及时回收
- PIL的
save是同步阻塞方法,执行返回时文件已经落盘,后面写的循环轮询文件是否存在的逻辑完全冗余,白占系统资源
修复方法
- 禁止全量缓存图片对象:不要把所有处理完的图片存在列表里等最后一次性写入,改为逐张/逐小批次追加写入PDF,处理完的图片立刻释放对应内存。如果要处理的图片量特别大(大几百上千张),可以先把每张图转成单页临时PDF存在本地磁盘,最后统一合并后删除临时文件,内存占用能压到单张图片的水平。
- 主动释放资源:所有图片、PDF读写操作优先用
with上下文管理器编写,出作用域自动关闭句柄释放内存;手动创建的对象用完主动调用close()方法,避免内存泄漏。 - 平衡速度和内存:逐张追加写入PDF的速度偏慢,可以调整为每攒20~30张处理好的图片就追加写入一次,不用攒全量,也不用每张都重新打开PDF文件。
参考修正代码
import os import glob import time from PIL import Image from PyPDF2 import PdfFileMerger, PdfFileReader # 用os.path.join拼接路径,避免不同系统斜杠兼容问题 imageList = sorted(glob.glob(os.path.join(root, files, '*.jpg'))) exist_pdf_list = glob.glob(os.path.join(root, "*", "*.pdf")) noImages = len(imageList) factorL = 0.25 factorP = 0.5 start = time.time() # 处理第一页作为PDF基础页 with Image.open(imageList[0]) as first_im: w, h = first_im.size new_size = (int(w*factorP), int(h*factorP)) if h > w else (int(w*factorL), int(h*factorL)) base_im = first_im.resize(new_size).convert("RGB") namePath = os.path.join(rootPDF, f"{pdf_id}.pdf") # 逐张处理后续页面,不攒全量列表 append_buffer = [] for idx in range(1, noImages): img_path = imageList[idx] print(f"处理进度: {idx+1}/{noImages},当前文件: {img_path}") with Image.open(img_path) as im: w, h = im.size new_size = (int(w*factorP), int(h*factorP)) if h > w else (int(w*factorL), int(h*factorL)) resized_im = im.resize(new_size).convert("RGB") append_buffer.append(resized_im) # 攒够20张就写一次,平衡内存和速度 if len(append_buffer) >= 20 or idx == noImages -1: base_im.save( namePath, "PDF", quality=40, save_all=True, append_images=append_buffer ) # 写完清空缓存,释放内存 for im_item in append_buffer: im_item.close() append_buffer.clear() base_im.close() # 合并已有PDF,用完主动关闭merger对象 if exist_pdf_list: exist_pdf_list.append(namePath) merger = PdfFileMerger() try: for pdf_path in exist_pdf_list: with open(pdf_path, 'rb') as f: merger.append(PdfFileReader(f)) with open(namePath, 'wb') as new_file: merger.write(new_file) finally: merger.close() # 写操作日志 with open('pdfcreated_log.txt', 'a', encoding='utf-8') as f: f.write(f"{pdf_id}\n") cost = time.time() - start print(f"处理完成,总耗时: {round(cost, 2)}s")
内容的提问来源于stack exchange,提问作者Martha
相关产品推荐
相关产品推荐

