サイトリニューアル案件では、旧サーバーから画像素材を回収する作業が発生することがあります。数が少なければ手動でも対応できますが、数十〜数百枚になると手作業では非効率です。

今回は、CSVファイルにまとめた画像URLからPythonで一括ダウンロードするスクリプトを紹介します。

必要なライブラリのインストール

まずは必要なライブラリをインストールします。

pip install requests pandas

CSVファイルの形式

CSVファイルは以下のような形式を想定しています。

filename,url
image001.jpg,https://example.com/images/sample1.jpg
image002.png,https://example.com/images/sample2.png
image003.jpg,https://example.com/images/sample3.jpg

画像一括ダウンロードスクリプト

import os
import requests
import pandas as pd
from urllib.parse import urlparse
import time

def download_images_from_csv(csv_file_path, download_folder='downloaded_images'):
    """
    CSVファイルから画像URLを読み込み、一括ダウンロードする
    
    Args:
        csv_file_path (str): CSVファイルのパス
        download_folder (str): ダウンロード先フォルダ
    """
    
    # ダウンロードフォルダを作成
    if not os.path.exists(download_folder):
        os.makedirs(download_folder)
    
    # CSVファイルを読み込み
    try:
        df = pd.read_csv(csv_file_path)
    except Exception as e:
        print(f"CSVファイルの読み込みに失敗しました: {e}")
        return
    
    # 必要な列があるかチェック
    if 'url' not in df.columns:
        print("CSVファイルに'url'列が見つかりません")
        return
    
    # ファイル名列がない場合はURLから自動生成
    if 'filename' not in df.columns:
        df['filename'] = df['url'].apply(lambda x: os.path.basename(urlparse(x).path))
    
    success_count = 0
    error_count = 0
    
    print(f"画像ダウンロードを開始します({len(df)}件)")
    
    for index, row in df.iterrows():
        url = row['url']
        filename = row['filename']
        
        # ファイル名が空の場合はURLから取得
        if pd.isna(filename) or filename == '':
            filename = os.path.basename(urlparse(url).path)
        
        # ファイル名に拡張子がない場合の処理
        if '.' not in filename:
            filename += '.jpg'  # デフォルトでjpg拡張子を付与
        
        file_path = os.path.join(download_folder, filename)
        
        try:
            # 画像をダウンロード
            response = requests.get(url, timeout=30)
            response.raise_for_status()
            
            # ファイルに保存
            with open(file_path, 'wb') as f:
                f.write(response.content)
            
            print(f"✓ {filename} をダウンロードしました")
            success_count += 1
            
        except requests.exceptions.RequestException as e:
            print(f"✗ {filename} のダウンロードに失敗しました: {e}")
            error_count += 1
        
        except Exception as e:
            print(f"✗ {filename} の保存に失敗しました: {e}")
            error_count += 1
        
        # サーバーに負荷をかけないよう少し待機
        time.sleep(0.5)
    
    print(f"\n処理完了: 成功 {success_count}件 / 失敗 {error_count}件")

# 使用例
if __name__ == "__main__":
    # CSVファイルから画像をダウンロード
    download_images_from_csv('image_urls.csv', 'images')

より高機能なバージョン

エラーハンドリングや進捗表示を強化したバージョンです。

import os
import requests
import pandas as pd
from urllib.parse import urlparse
import time
from datetime import datetime

def advanced_download_images(csv_file_path, download_folder='downloaded_images', max_retries=3):
    """
    CSVファイルから画像を一括ダウンロード(高機能版)
    
    Args:
        csv_file_path (str): CSVファイルのパス
        download_folder (str): ダウンロード先フォルダ
        max_retries (int): 最大リトライ回数
    """
    
    # ダウンロードフォルダを作成
    if not os.path.exists(download_folder):
        os.makedirs(download_folder)
    
    # ログファイル用のタイムスタンプ
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_file = f"download_log_{timestamp}.txt"
    
    # CSVファイルを読み込み
    try:
        df = pd.read_csv(csv_file_path)
    except Exception as e:
        print(f"CSVファイルの読み込みに失敗しました: {e}")
        return
    
    if 'url' not in df.columns:
        print("CSVファイルに'url'列が見つかりません")
        return
    
    if 'filename' not in df.columns:
        df['filename'] = df['url'].apply(lambda x: os.path.basename(urlparse(x).path))
    
    success_count = 0
    error_count = 0
    skip_count = 0
    
    print(f"画像ダウンロードを開始します({len(df)}件)")
    
    with open(log_file, 'w', encoding='utf-8') as log:
        log.write(f"ダウンロードログ - {datetime.now()}\n\n")
        
        for index, row in df.iterrows():
            url = row['url']
            filename = row['filename']
            
            if pd.isna(filename) or filename == '':
                filename = os.path.basename(urlparse(url).path)
            
            if '.' not in filename:
                filename += '.jpg'
            
            file_path = os.path.join(download_folder, filename)
            
            # すでにファイルが存在する場合はスキップ
            if os.path.exists(file_path):
                print(f"- {filename} はすでに存在するためスキップします")
                log.write(f"SKIP: {filename} (already exists)\n")
                skip_count += 1
                continue
            
            # リトライ機能付きダウンロード
            downloaded = False
            for attempt in range(max_retries):
                try:
                    response = requests.get(url, timeout=30, headers={
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
                    })
                    response.raise_for_status()
                    
                    with open(file_path, 'wb') as f:
                        f.write(response.content)
                    
                    print(f"✓ {filename} をダウンロードしました ({len(response.content)} bytes)")
                    log.write(f"SUCCESS: {filename} - {url}\n")
                    success_count += 1
                    downloaded = True
                    break
                    
                except Exception as e:
                    if attempt < max_retries - 1:
                        print(f"⚠ {filename} の再試行中... ({attempt + 1}/{max_retries})")
                        time.sleep(2)
                    else:
                        print(f"✗ {filename} のダウンロードに失敗しました: {e}")
                        log.write(f"ERROR: {filename} - {url} - {e}\n")
                        error_count += 1
            
            if downloaded:
                time.sleep(0.5)  # サーバー負荷軽減
        
        # 結果をログに記録
        log.write(f"\n処理完了: 成功 {success_count}件 / スキップ {skip_count}件 / 失敗 {error_count}件\n")
    
    print(f"\n処理完了: 成功 {success_count}件 / スキップ {skip_count}件 / 失敗 {error_count}件")
    print(f"詳細ログ: {log_file}")

# 使用例
if __name__ == "__main__":
    advanced_download_images('image_urls.csv', 'images', max_retries=3)

使用方法

スクリプトを使用する手順は以下の通りです。

1. CSVファイルの準備

「filename」と「url」の列を持つCSVファイルを用意します。filenameが空の場合はURLから自動で取得されます。

2. スクリプトの実行

Python環境でスクリプトを実行します。

python download_images.py

3. 結果の確認

指定したフォルダに画像ファイルがダウンロードされ、処理結果がコンソールに表示されます。

スクリプトの主な機能

  • 自動フォルダ作成: ダウンロード先フォルダが存在しない場合は自動作成
  • 重複チェック: 既にファイルが存在する場合はスキップ
  • リトライ機能: ダウンロードに失敗した場合は指定回数まで再試行
  • 進捗表示: 処理状況をリアルタイムで表示
  • エラーログ: 成功・失敗の詳細をログファイルに記録
  • サーバー負荷軽減: リクエスト間に適切な間隔を設定

まとめ

PythonとPandasを使用することで、CSVファイルに記載された画像URLから効率的に画像を一括ダウンロードできます。リトライ機能やログ出力機能を追加することで、大量の画像処理でも安定して動作するスクリプトを作成できます。