跳至主要內容
問題與解答
圖片的批次處理

圖片的批次處理

問:
怎麼批次縮小圖片? 手機拍的照片為什麼是橫的? 照片裡有 GPS 座標要移除嗎? 怎麼批次轉成 WebP?
答:

批次處理的價值

網站上線前常常需要處理數十到數百張圖片:縮到適當尺寸、壓縮、轉換格式、統一命名。

手動處理耗時且容易遺漏,寫一次腳本可以重複使用。

圖片規格的原則見圖片尺寸與壓縮

基本的縮圖與壓縮

from PIL import Image
from pathlib import Path

def resize_image(src, dst, max_width=1200, quality=82):
    with Image.open(src) as im:
        # 依 EXIF 方向資訊自動轉正
        from PIL import ImageOps
        im = ImageOps.exif_transpose(im)

        if im.width > max_width:
            ratio = max_width / im.width
            new_size = (max_width, int(im.height * ratio))
            im = im.resize(new_size, Image.LANCZOS)

        # JPEG 不支援透明,需先轉換
        if im.mode in ('RGBA', 'P'):
            im = im.convert('RGB')

        im.save(dst, 'JPEG', quality=quality, optimize=True,
                progressive=True)

四個重點

  • exif_transpose——手機拍的照片常帶有方向資訊,不處理會呈現橫倒
  • LANCZOS——縮圖品質較好的演算法
  • 模式轉換——含透明度的圖存成 JPEG 會出錯
  • progressive=True——漸進式載入,大圖的體感較好

批次處理整個資料夾

def batch_resize(src_dir, dst_dir, max_width=1200):
    src_dir, dst_dir = Path(src_dir), Path(dst_dir)
    dst_dir.mkdir(parents=True, exist_ok=True)

    exts = {'.jpg', '.jpeg', '.png', '.webp'}
    count = 0
    for f in src_dir.iterdir():
        if f.suffix.lower() not in exts:
            continue
        try:
            resize_image(f, dst_dir / f'{f.stem}.jpg', max_width)
            count += 1
        except Exception as e:
            print(f'處理失敗 {f.name}: {e}')
    print(f'完成 {count} 張')

輸出到不同的目錄,不要覆蓋原檔。 原始檔應保留,因為壓縮是不可逆的。

產生多種尺寸

網站常需要同一張圖的不同尺寸——列表縮圖、內容圖、主視覺。

SIZES = {
    'thumb': 400,
    'medium': 800,
    'large': 1600,
}

def make_variants(src, dst_dir):
    dst_dir = Path(dst_dir)
    for name, width in SIZES.items():
        out = dst_dir / f'{Path(src).stem}-{name}.jpg'
        resize_image(src, out, max_width=width)

只縮小不放大——原圖比目標小時應保持原尺寸,放大只會變模糊又增加檔案大小。

轉換為較新的格式

def to_webp(src, dst, quality=80):
    with Image.open(src) as im:
        im.save(dst, 'WEBP', quality=quality, method=6)

method=6 是壓縮力度,數字越大檔案越小但處理越慢。批次處理時值得用。

關於 AVIF

壓縮率通常更好,但需要額外的套件支援,且處理速度較慢。建議先確認目標環境的支援情況。

實務建議

保留原始格式作為備援,同時產生新格式,讓網頁端依瀏覽器支援情況選擇。

檢查與報告

處理前先了解現況,往往能發現問題:

def audit(folder):
    rows = []
    for f in Path(folder).rglob('*'):
        if f.suffix.lower() not in {'.jpg', '.jpeg', '.png', '.webp'}:
            continue
        try:
            with Image.open(f) as im:
                rows.append({
                    'path': str(f),
                    'width': im.width,
                    'height': im.height,
                    'kb': round(f.stat().st_size / 1024, 1),
                })
        except Exception:
            rows.append({'path': str(f), 'error': '無法讀取'})
    # 依檔案大小排序,找出最需要處理的
    rows.sort(key=lambda r: r.get('kb', 0), reverse=True)
    return rows

把結果輸出成 CSV,就能快速看出哪些圖片過大。 通常前二十名就佔了大部分的空間。

移除 EXIF 資訊

手機拍攝的照片可能包含拍攝地點的座標。放上網站前建議移除。

def strip_exif(src, dst):
    with Image.open(src) as im:
        from PIL import ImageOps
        im = ImageOps.exif_transpose(im)   # 先依方向轉正
        data = list(im.getdata())
        clean = Image.new(im.mode, im.size)
        clean.putdata(data)
        clean.save(dst)

注意順序:要先依方向資訊轉正,再移除,否則圖片會變成橫的。

為什麼重要

不動產、餐飲、居家服務等行業,若把含座標的照片上傳,等於公開了拍攝地點——可能涉及客戶的隱私。

加上浮水印

from PIL import Image

def add_watermark(src, dst, mark_path, opacity=128, margin=20):
    with Image.open(src).convert('RGBA') as base:
        with Image.open(mark_path).convert('RGBA') as mark:
            # 浮水印寬度設為主圖的六分之一
            w = base.width // 6
            ratio = w / mark.width
            mark = mark.resize((w, int(mark.height * ratio)))

            alpha = mark.split()[3].point(lambda p: p * opacity // 255)
            mark.putalpha(alpha)

            pos = (base.width - mark.width - margin,
                   base.height - mark.height - margin)
            base.alpha_composite(mark, pos)
            base.convert('RGB').save(dst, 'JPEG', quality=85)

浮水印無法防止盜用,但能在被轉載時保留來源標示

處理前的安全習慣

  1. 永遠輸出到新目錄,不要就地覆蓋
  2. 先用少量檔案測試,確認結果符合預期
  3. 處理前備份原始檔
  4. 記錄處理結果——成功幾張、失敗哪些

壓縮與縮圖都是不可逆的,原始檔一旦覆蓋就回不去了。

效能考量

大量圖片處理時,可用多行程加速:

from concurrent.futures import ProcessPoolExecutor

def batch_parallel(files, workers=4):
    with ProcessPoolExecutor(max_workers=workers) as ex:
        list(ex.map(process_one, files))

行程數不要超過 CPU 核心數太多——圖片處理是運算密集的工作,開太多反而互相競爭。

在正式主機上執行時要特別注意,避免影響網站的正常服務。建議在離峰時段或另一台機器處理。

本文的範例以 Python 3 為例,實際的套件版本與 API 可能隨版本更新而異。在正式環境執行任何批次處理前,請先以少量資料測試並確實備份。

發表於2026-08-10   更新於2026-08-24
免費諮詢 · 1 個工作天內回覆

準備好讓網站 開始幫你帶生意了嗎?

不論是要做新網站、救舊網站,還是只想先聊聊方向——先諮詢,不用先付錢,我們照實給你建議。

1 個工作天回覆免費諮詢與報價費用白紙黑字26 年找得到人
免費諮詢
免費諮詢 LINE諮詢 03-4020420 臉書傳訊
免費諮詢 LINE諮詢 03-4020420 臉書傳訊
免費諮詢
免費諮詢 LINE諮詢 03-4020420 臉書傳訊
免費諮詢 LINE諮詢 03-4020420 臉書傳訊