この記事は、以下のような方を想定しています。

  • Adobe Stockコントリビューターで、英語タイトル・キーワード入力が面倒だと感じている方
  • Tampermonkeyと外部AI APIを組み合わせた自動化に興味がある方
  • Groq APIのレート制限やJSONモードのエラーハンドリングで悩んでいる方
  • AIストックフォト自動化シリーズを読んでいる方

以前の記事で紹介した通り、画像生成から品質チェック、審査結果の追跡までかなりの部分を自動化してきました。 ただ、Adobe Stockへのアップロード作業そのもの、特に「英語タイトルを考える」「キーワードを最低5個〜最大49個入力する」という部分は、 結局まだ手作業で残っていました。今回はここをTampermonkey+Groq Vision APIで自動化した話です。

Adobe Stockのタイトル・キーワード入力が地味に大変だった問題

Adobe Stockのコントリビューター画面では、画像1点ごとに英語のタイトルと、5〜49個のキーワードを入力する必要があります。 英語が堪能ではない自分にとって、パッと見て情景を的確な英単語で表現するのは想像以上に時間がかかる作業でした。 もちろんCSVで一括アップロードすることで大部分は問題回避できるのですが、やはり微修正等が発生して都度確認する手間は省けませんでした。

特にキーワードは、関連性の高いものを適切な順番で入力する必要があるため、毎回30〜40個を考えるのはかなりの負担でした。 1日にアップロードする枚数が増えるほど、この作業だけで数十分溶けていくような状態です。

画像を見て内容を理解し、それを英語のキーワードに変換する作業は、まさに画像認識AIが得意とする領域です。 「これは自動化できるはず」と思い立ち、Tampermonkey+Groq Vision APIでの自動化に着手しました。

実装の全体像

やりたいことはシンプルです。Adobe Stockの画面上に選択中の画像から、AIがタイトル・キーワード・カテゴリ候補を生成し、フォームに反映するパネルを追加します。

  1. 選択中のサムネイル画像のURLを取得
  2. GM_xmlhttpRequestで画像をBase64として取得
  3. Groqの画像対応モデルに画像を送信し、JSONモードでタイトル・キーワード(英語+日本語訳)・カテゴリ候補を生成
  4. 結果をパネルに表示し、確認・取捨選択した上でタイトル欄・キーワード欄に反映

ここまでは想定通りに進んだのですが、実際にAdobe Stockの画面で動かしてみると、想定していなかったエラーに次々と遭遇しました。 ここからが本記事の本題です。

つまずいたエラーたち

実装〜運用の過程で遭遇した主なエラーと、その原因・対策を表にまとめました。

エラー 原因 対応方法
「エラー: undefined」 Tampermonkeyの@connectに画像CDN(ftcdn.net)のドメインを登録しておらず、画像取得リクエストが拒否されていた。エラーオブジェクトに.messageが無いため「undefined」とだけ表示されていた @connect ftcdn.netを追加。あわせてonerrorのエラーメッセージを具体化し、以降同様の問題を特定しやすくした
「タイトル欄が見つかりませんでした」 class名を直接指定していたが、Adobe側のCSSクラスがビルドごとにハッシュ化されて変わる可能性があった name="title"やaria-labelなど、変わりにくい属性を優先するセレクタに変更。複数の要素がヒットする場合は、実際に画面表示されている要素を優先して選ぶ処理も追加
キーワードが意図通り入らない 「1語ずつEnterキーで追加するチップ入力欄」だと思い込んで実装していたが、実際は「カンマ区切りテキストをまとめてペーストする単一のtextarea」だった devtoolsで実際のDOM構造(placeholder="キーワードをペースト...")を確認し、カンマ区切り文字列を一括で流し込む処理に修正
Groqのレート制限エラー Groq側にはモデルや利用ティアに応じたレート制限があり、今回の処理では画像入力によるトークン消費も影響していました あえて画面表示用の小さいサムネイル画像を送るよう変更。あわせてエラーメッセージから待機秒数を読み取り、自動的に待ってから再試行する処理を追加
「Failed to generate JSON」/「Failed to validate JSON」 GroqのJSONモードが、稀に有効なJSONを生成できずに失敗する一時的な事象。文言が「generate」と「validate」の2パターンあり、最初は片方しか検知できていなかった 両方の文言、および共通するfailed_generationという語句にもマッチするよう正規表現を広げ、自動リトライの対象にした

特に印象的だったのは「キーワードが意図通り入らない」問題です。 Adobe Stockの一般的な入力欄のイメージから「タグ入力=1語ずつEnterで追加」だろうと勝手に決めつけて実装したところ、 実際のプレースホルダーは「キーワードをペースト...」となっていました。 見た目のイメージだけで実装を進めず、実際のDOM構造をdevtoolsで確認することの大切さを改めて感じました。

自動リトライ処理の中身

レート制限エラーとJSON生成失敗、どちらも「少し待ってからもう一度送れば大抵うまくいく」という共通点があったため、 エラーメッセージの内容を判定して自動的に待機&リトライする処理にまとめました。 アラートでユーザーの操作を止めるのではなく、ボタンのテキストに「トークン解放待ち… (18s)」のようにカウントダウンを表示する形にしています。

let result = null;
let attempt = 0;
const MAX_RETRIES = 4;
while (result === null) {
  try {
    result = await callGroq(base64);
  } catch (e) {
    attempt++;
    const waitMatch = /try again in ([\d.]+)s/i.exec(e.message);
    const isRateLimit = /rate limit/i.test(e.message) || waitMatch;
    const isJsonFail = /Failed to (generate|validate) JSON|failed_generation/i.test(e.message);
    if (attempt > MAX_RETRIES || !(isRateLimit || isJsonFail)) {
      throw e; // 想定外のエラーはそのまま投げてユーザーに通知
    }
    let waitSec = waitMatch ? Math.ceil(parseFloat(waitMatch[1])) + 1 : 3;
    for (let s = waitSec; s > 0; s--) {
      btn.textContent = `待機中… (${s}s)`;
      await sleep(1000);
    }
  }
}

ポイントは、レート制限とJSON生成失敗という「性質の異なる2種類のエラー」を、同じリトライループの中で扱っていることです。 エラーメッセージの文言を正規表現で判定し、待機秒数が分かる場合はその秒数、分からない場合は数秒のデフォルト待機、という形にすることで、 実装をシンプルに保ちながら両方に対応できました。

最終成果物は、Leonardo画像自動ダウンロードプラグインと同様にいずれGitHub等で公開したいと考えています。

導入してどれくらい楽になったか

導入前は、画像1点あたりタイトル・キーワード入力に3〜5分程度かかっていました。 導入後は、画像を選んでボタンを押し、生成されたキーワードのON/OFFを軽く確認して反映するだけなので、体感としては1点あたり30秒〜1分程度まで短縮できています。

自動化してよかったこと

  • 英単語をゼロから考える作業がなくなった
  • キーワードの数を稼ぐのが楽になった(40個以上を安定して提案してくれる)
  • 日本語訳が併記されるので、生成された英語キーワードの意味を取り違えにくい
  • カテゴリの候補も表示されるので、プルダウン選択の迷いが減った

実際の操作感は以下のようなイメージです。対象の画像を選択し、「選択中の画像から生成」というボタンをクリックすると、タイトルとキーワードが作成されます。 次に「タイトルに反映」、「キーワードに反映」等をクリックして、必要な部分をブラウザ側に反映します。非常に楽になりました。

★Adobe Stockタイトル自動入力ソフトの自動判定前の画面

Adobe Stockタイトル自動入力ソフトの自動判定前の画面

★Adobe Stockタイトル自動入力ソフトの自動判定後の画面

Adobe Stockタイトル自動入力ソフトの自動判定後の画面

一方で、Groqの無料枠を使っている以上、レート制限には引き続き注意が必要です。 連続してたくさんの画像を処理したい場合は、多少の待ち時間が発生することは前提として運用しています。

今後の課題

現状のスクリプトにも、まだ改善の余地が残っています。

  • Adobe Stock側のDOM構造が変わった場合、セレクタの再調整が必要になる(今回のトラブルの多くもここに起因していました)
  • キーワードの生成数が毎回ばらつく(40〜49個の間で変動する)
  • Groqの無料枠に依存しているため、大量処理には向いていない

特に1点目については、Adobe Stock側の仕様変更を検知する仕組みまでは作り込めていません。 今後、他の自動化ツールと同様に「動かなくなったら記事のコメントで教えてもらう」運用でカバーしていく予定です。

FAQ

Groq APIとは何ですか?

Groqは高速な推論基盤を提供するAI APIサービスです。画像を認識できるマルチモーダルモデルも提供しており、無料枠でも試すことができます。 ただし1分あたりのトークン数に上限(レート制限)がある点には注意が必要です。

なぜTampermonkeyでフォームに自動入力できるのですか?

TampermonkeyはブラウザにJavaScriptを注入できる拡張機能です。ページ内のタイトル欄・キーワード欄をDOM操作で見つけ出し、 React管理下の入力欄にも変更が伝わるよう、ネイティブのsetterでvalueを書き換えてinputイベントを発火させることで実現しています。

「エラー: undefined」の原因は何でしたか?

Tampermonkeyの@connect設定に画像CDNのドメインを登録していなかったため、画像取得リクエストが拒否され、 エラーオブジェクトにmessageが無いまま「undefined」と表示されていました。@connectに該当ドメインを追加して解決しました。

「Failed to generate JSON」エラーはなぜ起きますか?

GroqのJSONモードで、モデルが有効なJSONを生成できなかった際に返る一時的なエラーです。 多くの場合はもう一度リクエストすれば成功するため、エラー内容を判定して自動的に数秒待ってからリトライする仕組みを実装しました。

レート制限エラーが出たらどうすればいいですか?

Groqの無料枠(on_demandティア)は1分あたりのトークン数に上限があります。 エラーメッセージから待機秒数を読み取り、その時間だけ自動的に待ってから再試行するようにしています。 連続して生成する場合は、画像1枚ごとに間隔を空けると安定します。