PythonとPillowでブログ画像をWebPに一括変換するCLIツールを作る
以前、npmのimageminを使ったWebP変換の記事を書きましたが、今回はPythonでやってみます。Pythonだとargparseを使えばCLIツールとしてかなり柔軟に仕上げられるので、日常的に使うツールとして便利です。
目次
なぜWebPに変換するのか
WebPはGoogleが開発した画像フォーマットで、JPEGやPNGと比べてファイルサイズを大幅に削減できます。具体的にはこんな感じです。
- JPEGと比べて25〜35%ファイルサイズが小さい(同等画質で)
- PNGと比べて26%以上小さい(ロスレスの場合)
- 写真をPNGで保存してる場合は、WebPにするだけで劇的に軽くなる
たとえば1920×1080の写真だと、JPEGの品質85で約250KBのところ、WebPだと約180KBになります。ブログに大量の画像を貼っている場合、これはかなり効いてきます。
ブラウザの対応状況も2026年現在では97%以上をカバーしていて、Chrome・Firefox・Safari(14以降)・Edge・Operaすべて対応済みです。IE以外は問題なく表示できるので、もう実用上は心配いりません。
Pillowのインストール
PythonでWebP変換をするにはPillowというライブラリを使います。Python標準のPIL(Python Imaging Library)の後継で、画像処理の定番ですね。
pip install Pillow
インストールはこれだけです。Python 3.8以上なら問題なく動きます。
まずは1枚だけ変換してみる
いきなり一括変換に行く前に、まずは1枚だけWebPに変換するコードを見てみます。
from PIL import Image
# 画像を開く
img = Image.open("photo.jpg")
# WebPとして保存(品質80)
img.save("photo.webp", "WEBP", quality=80)
print("変換完了!")
たったこれだけです。Image.open()で画像を読み込んで、save()でWebP形式を指定して保存するだけ。Pillowが内部的にフォーマットの変換をやってくれます。
qualityパラメータは0〜100で指定でき、数値が大きいほど高画質(=ファイルサイズも大きい)になります。ブログ用途なら75〜85あたりがバランスが良いです。
ディレクトリ内の画像を一括変換する
1枚ずつ変換するのは面倒なので、指定したディレクトリ内のJPGとPNGをまとめてWebPに変換するスクリプトを作ります。
from PIL import Image
from pathlib import Path
def convert_to_webp(input_dir, output_dir, quality=80):
"""指定ディレクトリ内のJPG/PNG画像をWebPに一括変換する"""
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
# 対象の拡張子
extensions = {".jpg", ".jpeg", ".png"}
converted = 0
for file in input_path.iterdir():
if file.suffix.lower() in extensions:
img = Image.open(file)
# PNGのRGBAモードはそのまま、JPEGはRGBで処理
if img.mode == "RGBA":
pass # 透過を保持
else:
img = img.convert("RGB")
# 出力ファイルパス(拡張子を.webpに変更)
output_file = output_path / f"{file.stem}.webp"
img.save(output_file, "WEBP", quality=quality)
# ファイルサイズの比較
original_size = file.stat().st_size
new_size = output_file.stat().st_size
ratio = (1 - new_size / original_size) * 100
print(f"✓ {file.name} → {output_file.name}")
print(f" {original_size:,} bytes → {new_size:,} bytes ({ratio:.1f}% 削減)")
converted += 1
print(f"\n合計 {converted} ファイルを変換しました")
# 使い方
convert_to_webp("./images", "./images_webp", quality=80)
ポイントはimg.modeのチェックです。PNGには透過(アルファチャンネル)を持つRGBAモードのものがあって、これをそのままWebPに変換すれば透過も保持されます。JPEGはRGBモードなのでそのまま変換すればOKです。
変換のたびにbefore/afterのファイルサイズと削減率を表示するようにしているので、どれくらい効果があったか一目でわかります。
オプション機能を追加する
基本の変換ができたら、もう少し便利な機能を追加していきます。
リサイズ機能
ブログ用の画像は横幅1200px程度あれば十分なことが多いです。大きすぎる画像はリサイズしてから変換するとさらにファイルサイズが小さくなります。
def resize_image(img, max_width):
"""最大幅を指定してリサイズ(アスペクト比を維持)"""
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.LANCZOS)
return img
Image.LANCZOSは高品質なリサイズアルゴリズムで、縮小時にきれいな結果が得られます。
EXIF情報(メタデータ)の保持
写真にはEXIF情報(撮影日時やカメラ情報)が含まれていることがあります。必要に応じてWebPにも引き継ぐことができます。
# EXIF情報を保持して保存
exif_data = img.info.get("exif", None)
if exif_data:
img.save(output_file, "WEBP", quality=quality, exif=exif_data)
else:
img.save(output_file, "WEBP", quality=quality)
ブログ用途ではEXIF情報は不要なことが多いですが、位置情報など個人情報を含む場合もあるので、むしろ削除したほうが安全なケースもあります。用途に応じて使い分ける感じですね。
元ファイルの扱い
変換後に元のJPG/PNGファイルをどうするかも重要です。削除するか残すかをオプションで選べるようにしておくと安心です。
import os
def remove_original(file_path, delete=False):
"""元ファイルを削除する(オプション)"""
if delete:
os.remove(file_path)
print(f" 元ファイルを削除しました: {file_path.name}")
個人的には、まず出力先を別ディレクトリにして確認してから、問題なければ元ファイルを消す、というフローがおすすめです。いきなり削除は怖いですからね。
CLIツールとして仕上げる(argparse対応)
ここまでの機能をすべてまとめて、コマンドラインから使えるCLIツールに仕上げます。argparseを使うと、引数でオプションを柔軟に指定できるようになります。
#!/usr/bin/env python3
"""
画像をWebPに一括変換するCLIツール
対応形式: JPG, JPEG, PNG → WebP
"""
import argparse
import os
import sys
from pathlib import Path
from PIL import Image
def resize_image(img, max_width):
"""最大幅を指定してリサイズ(アスペクト比を維持)"""
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.LANCZOS)
return img
def convert_single(file_path, output_dir, quality, max_width, keep_exif, delete_original):
"""1ファイルをWebPに変換する"""
file_path = Path(file_path)
output_path = Path(output_dir) if output_dir else file_path.parent
img = Image.open(file_path)
# リサイズ
if max_width:
img = resize_image(img, max_width)
# カラーモード処理
if img.mode == "RGBA":
pass # 透過を保持
elif img.mode == "P":
img = img.convert("RGBA") # パレットモードはRGBAに変換
else:
img = img.convert("RGB")
# 出力先ディレクトリを作成
output_path.mkdir(parents=True, exist_ok=True)
output_file = output_path / f"{file_path.stem}.webp"
# 保存オプション
save_kwargs = {"quality": quality, "method": 4}
# EXIF情報の処理
if keep_exif:
exif_data = img.info.get("exif", None)
if exif_data:
save_kwargs["exif"] = exif_data
img.save(output_file, "WEBP", **save_kwargs)
# ファイルサイズ比較
original_size = file_path.stat().st_size
new_size = output_file.stat().st_size
ratio = (1 - new_size / original_size) * 100
print(f"✓ {file_path.name} → {output_file.name}")
print(f" {original_size:,} bytes → {new_size:,} bytes ({ratio:.1f}% 削減)")
# 元ファイル削除
if delete_original:
os.remove(file_path)
print(f" 元ファイルを削除しました")
return original_size, new_size
def main():
parser = argparse.ArgumentParser(
description="画像をWebPに一括変換するツール",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用例:
python webp_converter.py ./images
python webp_converter.py ./images -o ./output -q 85
python webp_converter.py ./images --max-width 1200
python webp_converter.py ./images --delete-original
python webp_converter.py photo.jpg -o ./output
"""
)
parser.add_argument("input", help="変換する画像ファイルまたはディレクトリのパス")
parser.add_argument("-o", "--output", help="出力先ディレクトリ(省略時は入力と同じ場所)")
parser.add_argument("-q", "--quality", type=int, default=80,
help="WebPの品質 0-100(デフォルト: 80)")
parser.add_argument("--max-width", type=int, default=None,
help="最大横幅(px)。超える場合はリサイズ")
parser.add_argument("--keep-exif", action="store_true",
help="EXIF情報を保持する")
parser.add_argument("--delete-original", action="store_true",
help="変換後に元ファイルを削除する")
args = parser.parse_args()
input_path = Path(args.input)
if not input_path.exists():
print(f"エラー: {args.input} が見つかりません", file=sys.stderr)
sys.exit(1)
# 対象の拡張子
extensions = {".jpg", ".jpeg", ".png"}
# ファイルリストを作成
if input_path.is_file():
if input_path.suffix.lower() not in extensions:
print(f"エラー: {input_path.name} は対象外の形式です(JPG/PNG のみ対応)", file=sys.stderr)
sys.exit(1)
files = [input_path]
else:
files = sorted([f for f in input_path.iterdir() if f.suffix.lower() in extensions])
if not files:
print("変換対象のファイルが見つかりませんでした")
sys.exit(0)
print(f"変換対象: {len(files)} ファイル")
print(f"品質: {args.quality}")
if args.max_width:
print(f"最大横幅: {args.max_width}px")
print("---")
total_original = 0
total_new = 0
for file in files:
try:
orig, new = convert_single(
file,
args.output,
args.quality,
args.max_width,
args.keep_exif,
args.delete_original
)
total_original += orig
total_new += new
except Exception as e:
print(f"✗ {file.name}: エラー - {e}", file=sys.stderr)
# サマリー
if total_original > 0:
total_ratio = (1 - total_new / total_original) * 100
print("---")
print(f"合計: {total_original:,} bytes → {total_new:,} bytes ({total_ratio:.1f}% 削減)")
if __name__ == "__main__":
main()
実行例とbefore/afterの比較
実際に使ってみます。まずは基本的な使い方から。
# imagesディレクトリ内のJPG/PNGをWebPに変換(出力先: outputディレクトリ)
python webp_converter.py ./images -o ./output
実行すると、こんな感じで結果が表示されます。
変換対象: 3 ファイル
品質: 80
---
✓ photo1.jpg → photo1.webp
1,245,680 bytes → 384,210 bytes (69.2% 削減)
✓ photo2.jpg → photo2.webp
892,340 bytes → 276,890 bytes (69.0% 削減)
✓ screenshot.png → screenshot.webp
2,456,780 bytes → 198,450 bytes (91.9% 削減)
---
合計: 4,594,800 bytes → 859,550 bytes (81.3% 削減)
JPEGからの変換で約70%削減、PNGからだと90%以上削減されることもあります。特にスクリーンショットなどのPNG画像はWebPとの相性が良くて、劇的にファイルサイズが小さくなりますね。
オプションを組み合わせるとこんな使い方もできます。
# 品質85、横幅1200pxにリサイズして変換
python webp_converter.py ./images -o ./output -q 85 --max-width 1200
# 単体ファイルの変換
python webp_converter.py photo.jpg -o ./output
# EXIF情報を保持して変換
python webp_converter.py ./images -o ./output --keep-exif
# 変換後に元ファイルを削除(注意して使う)
python webp_converter.py ./images -o ./output --delete-original
WordPressでのWebP運用の注意点
Pythonスクリプトで変換したWebP画像をWordPressにアップロードして使う場合、いくつか知っておきたいことがあります。
WordPress 5.8以降ならWebPは標準対応
WordPress 5.8からWebP形式のアップロードが標準でサポートされています。特別な設定なしにメディアライブラリにアップロードできます。
プラグインとの使い分け
WordPressにはEWWW Image OptimizerやConverter for Mediaなど、アップロード時に自動でWebP変換してくれるプラグインがあります。これらとPythonスクリプトの使い分けはこんな感じです。
- プラグインが向いているケース: WordPressの管理画面から直接画像をアップロードする運用。既存画像の一括変換機能もある
- Pythonスクリプトが向いているケース: アップロード前にローカルで画像を整理・加工したい場合。リサイズや品質の細かい調整を自分でコントロールしたい場合。WordPress以外のサイトでも使いたい場合
自分の場合は、ブログ用の画像はアップロード前にローカルで一括変換してからWordPressに上げています。プラグインに頼ると、サーバー側の処理負荷が気になるのと、変換の品質設定を自分で管理したいからです。
OGP画像には注意
SNSでシェアされたときに表示されるOGP画像(アイキャッチ画像)は、一部のSNSクローラーがWebPに対応していない場合があります。アイキャッチ画像だけはJPEGで用意しておくのが無難ですね。
まとめ
PythonとPillowを使えば、数十行のコードでWebP一括変換ツールが作れます。argparseでCLI対応すれば、日常的に使える便利なツールになります。
pip install Pillowだけで導入できる- JPG/PNGからWebPへの変換でファイルサイズを大幅に削減
- リサイズ、品質指定、EXIF保持などのオプションも簡単に追加できる
- argparseでコマンドライン引数に対応させればCLIツールとして完成
以前書いたnpmのimageminを使う方法と比べると、Pythonのほうがコード量は多いですが、そのぶん細かい制御ができます。自分好みにカスタマイズしていけるのがPythonの良いところですね。
今回は以上です!