批量图片转格式 + 缩放:80 行 Pillow 脚本,多页 TIF 拆帧、透明图不翻车

2026-10-05 · 喵窝资源铺

发网站、发公众号、发给打印店之前,手里的图往往是这种状态:扫描仪吐出来的多页 TIF、截图工具存的带透明通道 PNG、相机直出的超大分辨率 JPG,还混着一两个传坏的文件。一篇篇开 PS 转格式不现实。这篇给一个 Pillow 批量脚本:递归整个目录,统一转 jpg/png、最长边超限自动缩放、多页 TIF 拆成独立图片、坏文件不崩全部记报告。文中代码实跑验证,真实输出贴在下面。

它做的四件事

需求脚本的处理
统一格式全部转成 jpg(或 --fmt png),RGBA/调色板图自动铺白底再转
限制尺寸最长边超过 --max 才缩放,等比、LANCZOS 高质量重采样,小图不动
多页 TIF读 n_frames,逐帧导出为 名字_p1/p2/p3... 独立文件
烂文件不崩损坏图片自动跳过并写进报告,不中断整批任务

三个实跑才抓到的坑

坑1:RGBA 转 JPG 直接 save 会翻车。透明通道的 PNG 直接存成 JPG,要么抛 OSError,要么透明部分变成黑块。正确做法是先 convert("RGBA"),再新建一张白底 RGB 图、用 alpha 通道当 mask 粘贴过去——透明处自然变成白色。
坑2:坏文件要 im.load() 提前探明。Image.open() 是惰性的,头部能读就不报错,真正炸在 save() 那一步——这时前面处理的可能已经写了一半。打开后立刻 load() 把像素读进内存,坏文件当场现形、干净跳过。
坑3:多页 TIF 用 seek() 遍历时必须 copy()。单个 Image 对象是可变的游标,seek(i) 之后拿到的"页"在下一次 seek 后内容会变。处理前 im.copy() 固化当前帧,否则输出全是最后一帧。

脚本全文(img_batch.py)

# -*- coding: utf-8 -*-
"""批量图片整理:格式转换 + 多页TIF拆帧 + 限制最长边缩放 + 运行报告。
用法: python3 img_batch.py <输入目录> <输出目录> [--fmt jpg|png] [--max 1600]
只依赖 Pillow。坏文件不崩,全部记进报告。
"""
import os, argparse
from PIL import Image

def prep(im, fmt):
    # RGBA/P/La 转 JPG 必须先铺白底,否则 save 抛 OSError(透明的部分会变黑块)
    if fmt in ("JPEG", "JPG") and im.mode in ("RGBA", "LA", "P"):
        im = im.convert("RGBA")
        bg = Image.new("RGB", im.size, (255, 255, 255))
        bg.paste(im, mask=im.split()[3])
        return bg
    if im.mode not in ("RGB", "L"):
        return im.convert("RGB")
    return im

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("src"); ap.add_argument("dst")
    ap.add_argument("--fmt", default="jpg")
    ap.add_argument("--max", type=int, default=1600)
    a = ap.parse_args()
    os.makedirs(a.dst, exist_ok=True)
    ok, skipped, resized = [], [], []

    def handle(im, rel, base, frame=None):
        fmt = a.fmt.upper()
        im = prep(im, fmt)
        w, h = im.size
        scale = a.max / max(w, h)
        if scale < 1:
            im = im.resize((round(w*scale), round(h*scale)), Image.LANCZOS)
        suffix = f"_p{frame}" if frame else ""
        out = os.path.join(a.dst, os.path.splitext(base)[0] + suffix + "." + a.fmt)
        os.makedirs(os.path.dirname(out), exist_ok=True)
        im.save(out, quality=90)
        tag = f"{rel}{suffix}"
        ok.append(tag)
        if scale < 1:
            resized.append(f"{tag}: {w}x{h}->{im.size[0]}x{im.size[1]}")

    for root, _, files in os.walk(a.src):
        for fn in files:
            p = os.path.join(root, fn)
            rel = os.path.relpath(p, a.src)
            try:
                im = Image.open(p); im.load()   # 坏文件在这一步才炸,先探明
            except Exception as e:
                skipped.append((rel, str(e)[:60])); continue
            frames = getattr(im, "n_frames", 1)
            if frames > 1:                       # 多页 TIF/TIFF 逐帧拆开
                for i in range(frames):
                    im.seek(i)
                    handle(im.copy(), rel, fn, frame=i+1)
            else:
                handle(im.copy(), rel, fn)
    print(f"输出 {len(ok)} 个文件 | 跳过 {len(skipped)} | 缩放 {len(resized)}")
    for s, e in skipped: print(f"  [跳过] {s}: {e}")
    for r in resized: print(f"  [缩放] {r}")

if __name__ == "__main__":
    main()

实跑验证(不是"应该能跑")

构造了 6 个测试文件:3 帧多页 TIF、RGBA 透明图、1200×800 大图、损坏的假 jpg、灰度图(放子目录)。跑 python3 img_batch.py raw out --fmt jpg --max 300:

输出 6 个文件 | 跳过 1 | 缩放 1
  [跳过] d_broken.jpg: cannot identify image file 'raw/d_broken.jpg'
  [缩放] c_big.png: 1200x800->300x200

逐个回读验证输出:

输出预期实测
a_multi_p1/p2/p3.jpg多页TIF拆成3个独立文件,帧序正确✅ 3个文件,像素分别为白/红/蓝
b_alpha.jpgRGBA透明图转jpg,透明处变白✅ 角落像素 (255,255,255)
c_big.jpg1200×800 缩到最长边300✅ 300×200
sub/e_gray.jpg灰度图、子目录递归✅ out/sub/ 下正确生成
d_broken.jpg坏文件跳过不崩✅ 记入跳过列表

再跑 --fmt png 全部正常输出 png,格式参数双向可用。

什么时候自己写不如找人写

这个脚本覆盖的是"标准整理"场景。如果你的需求再往前一步——比如要保留 EXIF 拍摄时间、按拍摄日期归档进文件夹、批量加圆角/压缩到指定体积以内、对接现成的文件夹自动监控——每一条都是要单独调试的活,而且错一个参数(比如 EXIF 一丢,几百张照片的时间线就没了)很难肉眼发现。

不想自己折腾?我们代写。这类批量图片/文件处理小脚本属于「定制脚本 ¥19.9」档:你说清楚输入输出规则,我们写好、用你给的样例文件实测验证后再交付,不改到跑对不算完。

👉 查看服务详情与下单(虚拟物品交付后不退款;未收到或未达承诺免费重做)

其他可直接抄走的干货:批量PDF去空白页+水印 · 乱编码CSV合并进Excel · 批量重命名