
圖片的批次處理
批次處理的價值
網站上線前常常需要處理數十到數百張圖片:縮到適當尺寸、壓縮、轉換格式、統一命名。
手動處理耗時且容易遺漏,寫一次腳本可以重複使用。
圖片規格的原則見圖片尺寸與壓縮。
基本的縮圖與壓縮
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)
浮水印無法防止盜用,但能在被轉載時保留來源標示。
處理前的安全習慣
- 永遠輸出到新目錄,不要就地覆蓋
- 先用少量檔案測試,確認結果符合預期
- 處理前備份原始檔
- 記錄處理結果——成功幾張、失敗哪些
壓縮與縮圖都是不可逆的,原始檔一旦覆蓋就回不去了。
效能考量
大量圖片處理時,可用多行程加速:
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 可能隨版本更新而異。在正式環境執行任何批次處理前,請先以少量資料測試並確實備份。