GASで画像をAIで解析し
alt属性を自動生成する方法

Googleドライブの画像をClaude APIに直接渡し、alt属性(代替テキスト)とキャプションを自動生成してスプレッドシートに記録する仕組みを、動くコードで解説します。 画像1枚ずつ説明文を書く手間をなくし、アクセシビリティとSEOをまとめて底上げする、実務向けの自動化です。

|対象: GAS / DriveApp / Claude API(画像入力)

Table of Contents

なぜalt属性をAIで自動生成するのか

alt属性(代替テキスト)は、画像が表示できないときに代わりに読まれる説明文です。 画面読み上げソフトを使う人に画像の内容を伝えるアクセシビリティ対応であり、 検索エンジンが画像の中身を理解する手がかりにもなるため、画像検索やSEOにも効きます。

とはいえ、商品画像やブログの写真が数百枚あると、1枚ずつ説明文を書くのは現実的ではありません。 そこでGoogleドライブの画像をClaude APIに渡し、AIに中身を見てもらって、短いalt属性と少し詳しいキャプションをまとめて生成します。 生成結果はスプレッドシートに一覧化するので、そのままHTMLやCMSに転記できます。

手入力ゼロ

フォルダに入れておくだけで、画像を1枚ずつ見て説明文を書く手間がなくなる

SEO・画像検索に強く

画像の内容を表す代替テキストが付き、検索エンジンが中身を理解しやすくなる

アクセシビリティ対応

読み上げソフトの利用者に画像の内容が伝わり、誰にでも使いやすくなる

一覧で管理

ファイル名・alt・キャプション・確信度をシートにまとめ、転記や見直しが楽

APIキーを安全に保管する

使うのはClaude APIキーです。コードに直接書くと共有時に漏れる恐れがあるため、PropertiesService(スクリプトごとの設定値を安全に保管する仕組み)のスクリプトプロパティに保存します。 コードからはgetProperty()で読み込みます。

// スクリプトプロパティにAPIキーを保存(初回1回だけ実行)
function saveApiKey() {
  PropertiesService.getScriptProperties()
    .setProperty("CLAUDE_API_KEY", "sk-ant-xxxxxxxx");
}

function getApiKey() {
  const key = PropertiesService.getScriptProperties().getProperty("CLAUDE_API_KEY");
  if (!key) throw new Error("APIキーが未設定です。saveApiKey を実行してください。");
  return key;
}

Claude APIの基本的な呼び出し方は、GASからClaude APIを呼び出して問い合わせを自動分類する方法で詳しく解説しています。

対象フォルダと記録シートを用意する

alt属性を付けたい画像は、Googleドライブの1つのフォルダにまとめておきます。 フォルダのID(URLの末尾にある文字列)を指定し、生成結果を書き込むスプレッドシートのシートも用意します。 シートには、ファイル名・ファイルID・alt属性・キャプション・確信度・処理日時の列を並べます。

// 対象フォルダのIDと、結果を書き込むシートを設定する
const FOLDER_ID = "ここに画像フォルダのIDを貼る"; // フォルダURLの末尾の文字列
const SHEET_NAME = "alt一覧";

// 記録シートを用意する(無ければ作り、見出し行を入れる)
function getResultSheet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
    sheet.appendRow(["ファイル名", "ファイルID", "alt属性", "キャプション", "確信度", "処理日時"]);
  }
  return sheet;
}

ファイルIDを記録しておくのは、次回以降に同じ画像を二度処理しないためです。 このIDを使って、あとで処理済みかどうかを判定します。

画像をBase64でClaude APIに渡す

ここが今回の中心です。ドライブのファイルからgetBlob()でバイト列を取り出し、Utilities.base64Encode()でBase64(バイナリを文字列で表す方式)に変換して、APIのメッセージに画像として含めます。 Claude APIは、テキストと画像を混ぜたcontent配列を受け取れるので、画像ブロックと指示文ブロックを並べて送ります。

// 1枚の画像をClaude APIに渡し、altとキャプションを受け取る
function describeImage(file) {
  const blob = file.getBlob();
  const base64 = Utilities.base64Encode(blob.getBytes()); // 画像を文字列に変換
  const mimeType = blob.getContentType();                 // 例: image/jpeg

  const instruction =
    "あなたは画像の代替テキストを作る専門家です。\n" +
    "この画像について、次の3つを日本語で作ってください。\n" +
    "1) alt: 画面読み上げ用の簡潔な代替テキスト(40字以内、末尾に句点なし)\n" +
    "2) caption: もう少し詳しい説明文(80字以内)\n" +
    "3) confidence: 説明の確信度(high / medium / low)\n" +
    "画像から確実に読み取れることだけを書き、写っていないブランド名や固有名詞は推測しないでください。\n" +
    "余計な説明は出力せず、JSONだけを返してください。\n" +
    '形式: {"alt":"...","caption":"...","confidence":"high"}';

  const payload = {
    model: "claude-sonnet-4-20250514",
    max_tokens: 512,
    messages: [
      {
        role: "user",
        content: [
          { type: "image", source: { type: "base64", media_type: mimeType, data: base64 } },
          { type: "text", text: instruction },
        ],
      },
    ],
  };

  const res = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", {
    method: "post",
    contentType: "application/json",
    headers: {
      "x-api-key": getApiKey(),
      "anthropic-version": "2023-06-01",
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  });

  if (res.getResponseCode() !== 200) {
    throw new Error("API error: " + res.getContentText());
  }

  const responseText = JSON.parse(res.getContentText()).content[0].text;
  return JSON.parse(extractJson(responseText));
}

// レスポンスから { ... } の部分だけを取り出す(前後の余計な文字対策)
function extractJson(text) {
  const start = text.indexOf("{");
  const end = text.lastIndexOf("}");
  return text.slice(start, end + 1);
}

形式(media_type)は、getContentType()で得たMIMEタイプ(例: image/jpeg)をそのまま渡すため、JPEGでもPNGでも同じコードで動きます。 指示文で「写っていない固有名詞は推測しない」と明示し、confidence(確信度)も一緒に受け取ることで、あとで見直すべき画像を絞り込めます。

フォルダを回して処理済みをスキップする

フォルダ内のファイルを順に取り出し、画像形式のものだけを対象にします。 シートに記録済みのファイルIDは処理済みとみなしてスキップし、まだ説明文がない画像だけを処理します。 GASの実行時間制限に収まるよう、1回あたりの処理枚数に上限を設けます。

// フォルダ内の画像を順に処理し、シートに書き込む
function generateAltTexts() {
  const sheet = getResultSheet();
  const done = getProcessedIds(sheet); // 処理済みファイルIDの一覧
  const files = DriveApp.getFolderById(FOLDER_ID).getFiles();

  let count = 0;
  while (files.hasNext() && count < 20) { // 1回あたり最大20枚に絞る
    const file = files.next();
    const mime = file.getBlob().getContentType();

    if (mime.indexOf("image/") !== 0) continue; // 画像形式だけを対象にする
    if (done[file.getId()]) continue;           // 処理済みはスキップ

    const result = describeWithRetry(file);
    sheet.appendRow([
      file.getName(),
      file.getId(),
      result.alt || "",
      result.caption || "",
      result.confidence || "",
      new Date(),
    ]);
    count++;
    Utilities.sleep(500); // 連続呼び出しの間隔を少し空ける
  }

  Logger.log(count + "枚のalt属性を生成しました。");
}

// 既にシートに記録済みのファイルIDを集める
function getProcessedIds(sheet) {
  const values = sheet.getDataRange().getValues();
  const map = {};
  for (let i = 1; i < values.length; i++) {
    map[values[i][1]] = true; // B列 = ファイルID
  }
  return map;
}

画像形式かどうかは、MIMEタイプがimage/で始まるかで判定します。処理済みIDは、シートのファイルID列(B列)を読み込んで一覧化しておきます。 こうすれば、毎日実行しても新しく追加された画像だけが対象になります。

リトライで一時的なエラーに備える

APIは、混雑や通信の乱れで一時的に失敗することがあります。 1回の失敗で止めず、少し待ってから再試行するリトライを挟むと、まとめて処理するときに安定します。 何度も失敗する画像は、空のaltと「生成失敗」を記録して次へ進め、処理全体を止めないようにします。

// 一時的なAPIエラー時に最大3回まで再試行する
function describeWithRetry(file, maxRetry) {
  const limit = maxRetry || 3;
  for (let attempt = 1; attempt <= limit; attempt++) {
    try {
      return describeImage(file);
    } catch (e) {
      Logger.log("試行" + attempt + "回目で失敗: " + e.message);
      if (attempt === limit) {
        return { alt: "", caption: "生成失敗", confidence: "low" };
      }
      Utilities.sleep(1000 * attempt); // 待ち時間を伸ばして再試行
    }
  }
}

定期トリガーで自動生成する

最後に、時間主導トリガー(時刻や間隔で自動実行する仕組み)を設定します。 フォルダに画像を追加しておけば、毎日深夜に自動でalt属性が生成され、朝にはシートで確認できます。 処理済みはスキップするので、何度動いても同じ画像を二度処理することはありません。

// 毎日深夜に自動実行するトリガーを作る(初回1回だけ実行)
function createDailyTrigger() {
  ScriptApp.newTrigger("generateAltTexts")
    .timeBased()
    .atHour(2)    // 2〜3時の間に実行される
    .everyDays(1) // 毎日
    .create();
}

枚数が多い場合は、1回の上限(この記事では20枚)で少しずつ消化されます。 追加ペースに合わせて上限や実行間隔を調整してください。

実務での注意点

1. 公開前に確信度が低いものだけ確認する

AIは画像に写っているものを説明しますが、商品の正式名称・型番・ブランドなど、画像だけでは分からない固有情報は取り違えることがあります。confidenceが low の行だけ人が確認すれば、少ない手間で品質を保てます。

2. alt属性は簡潔に保つ

alt属性は読み上げ時に長すぎると聞きづらくなります。詳しい説明はキャプション側に寄せ、altは要点だけの短い文にします。 この記事では「40字以内・句点なし」と指示して、altが冗長にならないようにしています。

3. 大きな画像は事前に縮小する

画像が大きいほど送信データとトークン消費が増え、実行時間も延びます。 写真の解像度が高すぎる場合は、あらかじめ縮小しておくと安定し、コストも抑えられます。

4. 実行時間の上限に注意する

GASには1回あたり6分の実行時間制限があります。1回の処理枚数に上限を設け、残りは次回のトリガー実行で消化する形にすると、大量の画像でも止まらずに処理できます。

まとめ

画像のalt属性づくりは、ドライブの画像をBase64でClaude APIに渡し、altとキャプションをJSONで受け取り、処理済みをスキップしながらシートに記録すれば、まとめて自動化できます。 確信度を一緒に受け取って低いものだけ人が確認する運用にすれば、少ない手間でアクセシビリティとSEOをまとめて底上げできます。 固有名詞は推測させない・altは簡潔に保つ、という前提だけ守って活用してください。

よくある質問

alt属性は、画像が表示できないときの代わりに読まれる説明文で、HTMLの img タグに書きます。画面読み上げソフトを使う人に画像の内容を伝えるアクセシビリティ対応であり、検索エンジンが画像の中身を理解する手がかりにもなるためSEOにも効きます。ただ、商品画像やブログ画像が数百枚あると1枚ずつ手で書くのは大変です。GASとClaude APIを組み合わせれば、画像の内容をAIが見て、短い代替テキストと少し詳しいキャプションをまとめて生成できます。

できます。Claude APIは画像を直接受け取れるため、Googleドライブの画像をBase64(バイナリを文字列で表す方式)に変換してリクエストに含めれば、AIが画像の内容を解析して説明文を返します。この記事のコードでは、ドライブのファイルを取得して getBlob() でバイト列を取り出し、Utilities.base64Encode() で文字列化してから送っています。テキストだけでなく画像を理解させられるのがポイントです。

多くの画像はそのまま使えますが、公開前に一度目を通すことをおすすめします。AIは画像に写っているものを説明しますが、商品の正式名称・型番・ブランドなど画像だけでは分からない固有情報は取り違えることがあります。この記事では確信度(confidence)も一緒に受け取り、低いものだけ人が確認する運用にしています。alt属性は簡潔さも大切なので、長すぎる場合は要点だけに整えると読み上げ時に聞きやすくなります。

JPEG・PNG・GIF・WebPに対応しています。ファイルのMIMEタイプ(形式を表す情報)をそのままAPIに渡すので、形式ごとに処理を分ける必要はありません。ただし画像が大きいほど送信データが増え、実行時間とトークン消費が増えます。極端に大きい写真は事前に縮小しておくと安定します。GASには1回6分の実行時間制限があるため、1回の処理枚数に上限を設けています。

直接書くのは避けてください。スクリプトを共有した際に漏れる恐れがあります。Claude APIキーは PropertiesService(スクリプトごとの設定値を安全に保管する仕組み)のスクリプトプロパティに保存し、コードからは getProperty() で読み込む方法が安全です。

画像処理の
自動化を相談する。

alt属性の生成以外にも、画像からの情報抽出・OCR・商品説明文の生成など、AIと画像を組み合わせた業務自動化をGASでご相談いただけます。

AI×GAS自動化を見る