GASでキーワードからAIで
ブログ記事タイトル案を量産する方法

スプレッドシートに並べたSEOキーワードをClaude APIに渡し、1つのキーワードにつき狙いの違うタイトル案を複数まとめて自動生成する仕組みを、動くコードで解説します。 「案を出す」ところをAIに任せ、人は「選ぶ・整える」に集中する、記事づくりの下ごしらえを自動化する実務向けのレシピです。

|対象: GAS / スプレッドシート / Claude API

Table of Contents

なぜタイトル案づくりをAIで量産するのか

記事タイトルは、検索結果でクリックされるかどうかを大きく左右します。 同じ内容でも、切り口や言い回しを少し変えるだけで印象が変わるため、本来は複数案を出して選びたいところです。 とはいえ、キーワードが数十・数百とあると、1つずつタイトルを考えるのは骨が折れます。

そこで、スプレッドシートに並べたキーワードをClaude APIに渡し、1キーワードにつき「網羅型」「数字型」「疑問型」など狙いの違うタイトル案をまとめて生成します。 人は出そろった案から一番良いものを選び、必要なら一言足すだけ。 「案を出す」作業をAIに任せることで、記事の量産ペースを保ちながら質も落とさずに進められます。

選ぶ前提で数を出す

1キーワードで狙いの違う複数案を作り、A/Bやシリーズ記事に振り分けやすくする

SEOの型を反映

数字入り・疑問形など、クリックされやすい型をプロンプトで指定できる

文字数の目安を担保

検索結果で省略されにくい文字数を指示し、GAS側でも長すぎる案に印を付ける

一覧で管理

キーワードと案をシートに横並びで残し、そのまま編集会議の材料にできる

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を呼び出して問い合わせを自動分類する方法で詳しく解説しています。

キーワードシートと出力欄を用意する

まず、タイトルを作りたいキーワードをA列に1件ずつ並べたシートを用意します。 B列は状態(未処理/完了)を記録する欄、C列以降がタイトル案の出力欄です。 1キーワードあたりに作る案の数と、狙いの文字数もここで設定しておきます。

// キーワードを並べるシートと、案の数などの設定
const SHEET_NAME = "キーワード";
const IDEAS_PER_KEYWORD = 5; // 1キーワードあたりに作るタイトル案の数
const MAX_TITLE_LENGTH = 32; // 検索結果で省略されにくい目安の文字数

// シートを用意する(無ければ作り、見出し行を入れる)
// A列: キーワード / B列: 状態 / C列以降: タイトル案(案1, 案2, ...)
function getKeywordSheet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
    const header = ["キーワード", "状態"];
    for (let i = 1; i <= IDEAS_PER_KEYWORD; i++) header.push("案" + i);
    sheet.appendRow(header);
  }
  return sheet;
}

B列で状態を管理するのは、次に実行したときに、まだ作っていないキーワードだけを対象にするためです。 こうしておくと、キーワードを後から追記しても、既に生成済みの行を二度処理せずに済みます。

1キーワードから複数案をJSON配列で受け取る

ここが今回の中心です。キーワードを1つ渡し、狙い(type)とタイトル(title)をセットにした案を、 JSON配列(複数の値を角かっこで並べる形式)でまとめて受け取ります。 プロンプトで「狙いを変える」「文字数以内」「誇大表現は避ける」と条件を固定するのがポイントです。

// 1つのキーワードから、狙い別のタイトル案をまとめて受け取る
function generateTitles(keyword) {
  const instruction =
    "あなたはSEOに強い日本語のWeb編集者です。\n" +
    "次のキーワードで検索するユーザーがクリックしたくなる、" +
    "ブログ記事のタイトル案を" + IDEAS_PER_KEYWORD + "個作ってください。\n" +
    "キーワード: " + keyword + "\n" +
    "条件:\n" +
    "・各案は全角" + MAX_TITLE_LENGTH + "文字以内。\n" +
    "・キーワードを自然に含める。\n" +
    "・狙い(type)を変える(例: 初心者向けの網羅型 / 数字で具体化する型 / 疑問形で悩みに寄り添う型 / 手順を示す型)。\n" +
    "・誇大表現や事実と異なる断定は避ける。\n" +
    "余計な説明は出力せず、JSON配列だけを返してください。\n" +
    '形式: [{"type":"狙い","title":"タイトル"}]';

  const payload = {
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    messages: [{ role: "user", content: 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)); // [{type, title}, ...]
}

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

AIの返答には、まれに前後の説明文が混ざります。そのためextractJson()で最初の[から最後の]までを切り出してから解析し、崩れにくくしています。 モデル名やパラメータの詳細は、利用中のプランやAPIの最新仕様に合わせて調整してください。

シートを回して処理済みをスキップする

シートを上から順に見て、キーワードが入っていて、かつ状態が「完了」ではない行だけを処理します。 受け取った案はC列以降に横並びで書き込み、書き終えたらB列に「完了」を記録します。 GASの実行時間制限に収まるよう、1回あたりの処理件数に上限を設けます。

// シートを回して、未処理のキーワードだけタイトル案を作る
function generateAllTitles() {
  const sheet = getKeywordSheet();
  const values = sheet.getDataRange().getValues();

  let count = 0;
  for (let row = 1; row < values.length && count < 15; row++) { // 1回最大15件
    const keyword = String(values[row][0]).trim();
    const status = String(values[row][1]).trim();
    if (!keyword) continue;      // 空行はスキップ
    if (status === "完了") continue; // 生成済みはスキップ

    const ideas = generateWithRetry(keyword);

    // C列以降にタイトル案を書き込む(長い案には印を付ける)
    for (let i = 0; i < IDEAS_PER_KEYWORD; i++) {
      const idea = ideas[i];
      let cell = "";
      if (idea && idea.title) {
        const over = [...idea.title].length > MAX_TITLE_LENGTH ? " ⚠️長い" : "";
        cell = "[" + (idea.type || "案") + "] " + idea.title + over;
      }
      sheet.getRange(row + 1, 3 + i).setValue(cell);
    }
    sheet.getRange(row + 1, 2).setValue("完了"); // B列に状態を記録

    count++;
    Utilities.sleep(500); // 連続呼び出しの間隔を少し空ける
  }

  Logger.log(count + "件のキーワードでタイトル案を生成しました。");
}

文字数は[...idea.title].lengthで数えています。文字列をスプレッド構文で1文字ずつに分けると、絵文字などを含む場合でも見た目に近い文字数を数えられます。 目安を超えた案には「⚠️長い」と印を付けるので、選ぶときに一目で分かります。

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

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

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

カスタムメニューから一括実行する

タイトル案づくりは、思いついたタイミングで動かしたいことが多いので、 スプレッドシートのメニューから実行できるようにしておくと便利です。onOpen()でカスタムメニューを追加すれば、シートを開くだけで「タイトル生成」メニューが並びます。

// スプレッドシートを開いたときにカスタムメニューを追加する
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("タイトル生成")
    .addItem("未処理のキーワードを一括生成", "generateAllTitles")
    .addToUi();
}

キーワードが大量にあり、放っておいて処理させたい場合は、時間主導トリガーで定期実行する形にもできます。1回の上限で少しずつ消化されるので、実行時間制限に引っかかりません。

実務での注意点

1. 最後は人が選び、中身と一致させる

タイトルは記事の中身と一致していることが最優先です。AIは魅力的な言い回しを作るのが得意ですが、 中身より大げさなタイトルはクリック後の離脱や信頼低下につながります。生成された案から選び、記事内容に合わせて整えてください。

2. 文字数は指示と後チェックの両方で守る

検索結果のタイトルは、全角でおおよそ30〜32文字を超えると末尾が省略されやすくなります。 プロンプトで文字数を指示しても超えることがあるため、GAS側でも数えて印を付け、そのまま使える案を見分けやすくしています。

3. 同じキーワードで案が似すぎるとき

案が似通うときは、プロンプトの「狙い(type)」の例を増やしたり、想定読者(初心者/担当者/経営者など)を明記すると、案のばらつきが出ます。 自社の強みや実績を一言プロンプトに足すのも効果的です。

4. 実行時間とコストの上限に注意する

GASには1回あたり6分の実行時間制限があります。1キーワードごとにAPIを呼ぶため、 件数が多いほど時間とトークン消費が増えます。1回の処理件数に上限を設け、残りは次回に回す形にすると安定します。

まとめ

タイトル案づくりは、キーワードをClaude APIに渡し、狙い付きの案をJSON配列で受け取り、処理済みをスキップしながらシートに横並びで書き出せば、まとめて自動化できます。 文字数はプロンプトとGAS側の両方で見張り、最後は人が中身に合わせて選ぶ運用にすれば、記事の量産ペースを保ちつつ質も守れます。 「案を出す」下ごしらえをAIに任せ、人は「選ぶ・磨く」に集中してください。

よくある質問

たたき台としては十分使えますが、そのまま公開する前に一度目を通すことをおすすめします。AIは検索されやすい言い回しや数字入りの型を量産するのは得意ですが、自社の商品名・実績・独自の切り口といった中身までは分かりません。この記事では1キーワードにつき複数案を生成するので、その中から一番自社らしいものを選び、必要なら一言足す運用が現実的です。タイトルは記事の内容と一致していることが最優先なので、中身と食い違う誇張タイトルは避けてください。

タイトルは1本の記事でも切り口によって印象が大きく変わるためです。「初心者向けの網羅型」「数字で具体性を出す型」「疑問形で悩みに寄り添う型」など狙いを分けて複数案を出すと、A/Bで比べたり、シリーズ記事に振り分けたりしやすくなります。この記事のコードでは、狙い(type)とタイトル(title)と文字数(length)をセットでJSON配列で受け取り、シートに横並びで書き出します。選ぶ前提で数を出すのがコツです。

あります。検索結果に表示されるタイトルは、全角でおおよそ30〜32文字を超えると末尾が「…」で省略されやすくなります。プロンプトで「32文字以内」と指示しても、AIは時々超えてくるため、この記事ではGAS側でも文字数を数えて、長すぎる案には印を付けています。指示と後チェックの両方を入れておくと、そのまま使える案の割合が上がります。

GASには1回あたり6分の実行時間制限があるため、この記事では1回の実行で処理するキーワード数に上限(例では15件)を設けています。1キーワードごとにAPIを1回呼ぶので、件数が多いほど時間がかかります。数百件のキーワードがある場合は、処理済みをスキップする仕組みと定期トリガー(または複数回のメニュー実行)で少しずつ消化する設計にすると、制限に引っかからず安定します。

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

コンテンツ制作の
自動化を相談する。

タイトル案づくり以外にも、商品説明文・要約・分類など、AIとGASを組み合わせたコンテンツ・業務の自動化をご相談いただけます。

AI×GAS自動化を見る