🤖

AIトレンド調査を全自動化!GAS × Gemini API × NotebookLM で構築する最強のナレッジ蓄積術

に公開

はじめに

情報が激流のように流れるAI業界。毎日ニュースを追いかけるだけでも一苦労です。
「最新のニュースを自動で収集し、AIに分析させ、後で読み返しやすい形で蓄積したい」
そんな思いから、Google Apps Script (GAS) と Gemini APIを組み合わせた全自動のトレンド調査システムを構築しました。

この記事では具体的な設定方法についてお伝えします。


1. システムの全体像

このシステムは、以下のステップで指定した時間に自動実行されます。

  1. GAS収集: Google News RSSから特定のキーワード(「AI」など)に関連する最新記事を取得
  2. GASキーワード生成: 記事のタイトル群から、頻出キーワード5個を抽出
  3. GASプロンプト作成: 抽出したキーワードから、プロンプト作成
  4. Gemini予約アクション実行: Geminiの予約アクションで、プロンプトを自動実行
  5. NotebookLMに蓄積: 結果をNotebookLMにコピーし、自分だけのナレッジベースを構築

システムの構成図イメージ


2. 前準備

I. 新規スプレッドシートを作成し、GASを新規作成

スプレッドシートからGASを作成
GASの初期画面

II. Google AI StudioでAPIキーを取得

Google AI Studioにアクセスして、APIキーを下記手順で取得してください。

APIキーの取得


3. AIトレンドキーワード抽出GASの作成

前準備ができたら、いよいよAIトレンドキーワード抽出GASの作成に入りましょう。

と言っても、先ほど取得したAPIキーをスクリプトプロパティの設定(I)に貼り付けて、
プロンプト例を前準備で作成したGASに貼り付ける(II) だけです。

I. スクリプトプロパティの設定

スクリプトプロパティの設定

II. プロンプト例を作成したGASに貼り付け

プロンプト例

const SEARCH_KEYWORD = '人工知能 AI トレンド'; // 毎朝自動で検索されるキーワード
const MODEL_NAME = 'gemini-3-flash-preview';
/**
 * メイン関数:ニュース取得からプロンプト生成まで実行
 */
function automateAiTrendAnalysis() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  
  // 1. Google News RSSから最新記事を取得
  const newsItems = fetchGoogleNews(SEARCH_KEYWORD);
  if (newsItems.length === 0) return;

  // 2. 記事タイトルを結合してGeminiに送るテキストを作成
  const newsContext = newsItems.map(item => `- ${item.title}`).join('\n');

  // 3. Gemini APIで分析
  const analysisResult = analyzeTrendsWithGemini(newsContext);
  
  // 4. スプレッドシートの最終行に書き込み
  const date = new Date();
  sheet.appendRow([
    date, 
    analysisResult.keywords.join(', '), 
    analysisResult.deepResearchPrompt
  ]);

  console.log('トレンド分析が完了しました。');
}

/**
 * Google News RSSから最新1日分の記事を取得
 */
function fetchGoogleNews(keyword) {
  // キーワードに「 when:1d」を付け加えることで、過去24時間以内の記事に限定します
  const timeLimitQuery = `${keyword} when:1d`;
  const url = `https://news.google.com/rss/search?q=${encodeURIComponent(timeLimitQuery)}&hl=ja&gl=JP&ceid=JP:ja`;
  
  const response = UrlFetchApp.fetch(url);
  const xml = XmlService.parse(response.getContentText());
  const items = xml.getRootElement().getChild('channel').getChildren('item');
  
  // 取得件数を少し増やしたい場合は slice(0, 10) を調整してください
  return items.slice(0, 10).map(item => ({
    title: item.getChildText('title'),
    link: item.getChildText('link'),
    pubDate: item.getChildText('pubDate') // 念のため公開日も取得可能
  }));
}

/**
 * Gemini APIを使用して分析を行う
 */
function analyzeTrendsWithGemini(context) {
  const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
  const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL_NAME}:generateContent?key=${apiKey}`;
  const prompt = `
    以下の最新ニュースリストを分析し、必ず以下のJSON形式のみで回答してください。
    余計な解説文は一切含めないでください。

    1. 共通する重要な技術キーワードを3〜5個抽出してください(keywords)。
    2. それらのキーワードを元に、GeminiのDeep Research機能でさらに深掘り調査するための「具体的で高度な調査プロンプト」を1つ作成してください(deepResearchPrompt)。

    ニュースリスト:
    ${context}

    出力形式(JSON):
    {
      "keywords": ["ワード1", "ワード2"],
      "deepResearchPrompt": "ここにプロンプト"
    }
  `;

  const payload = {
    contents: [{ parts: [{ text: prompt }] }],
    // JSONモードを確実に有効化
    generationConfig: { 
      responseMimeType: "application/json" 
    }
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // エラー時に詳細を取得できるようにする
  };

  const res = fetchWithRetry(apiUrl, options);
  const jsonResponse = JSON.parse(res.getContentText());

  // デバッグ用:エラーが出た場合にログで中身を確認できるようにする
  if (jsonResponse.error) {
    console.error('APIエラー詳細:', jsonResponse.error.message);
    throw new Error('Gemini APIでエラーが発生しました: ' + jsonResponse.error.message);
  }

  // 安全にデータを抽出
  if (jsonResponse?.candidates?.[0]?.content?.parts?.[0]?.text) {
    return JSON.parse(jsonResponse.candidates[0].content.parts[0].text);
  } else {
    console.error('予期しないレスポンス形式:', jsonResponse);
    throw new Error('APIからの回答が空でした。');
  }

/**
 * 指数バックオフを用いたリトライ機能付きFetch
 */
function fetchWithRetry(url, options, maxRetries = 5) { // リトライ数5回
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = UrlFetchApp.fetch(url, options);
      const code = response.getResponseCode();
      
      // 成功(200)ならそのまま返す
      if (code === 200) return response;
      
      // 429(混雑)や500系エラーの場合はリトライ対象
      if (code === 429 || code >= 500) {
        console.warn(`一時的なエラー(${code})のためリトライします... (${i + 1}回目)`);
      } else {
        return response; // その他のエラーはそのまま返す
      }
    } catch (e) {
      lastError = e;
      console.warn(`リクエスト失敗: ${e.message} (${i + 1}回目)`);
    }
    
    if (i < maxRetries - 1) {
      // 指数バックオフ + 0〜1秒のランダムな待ち時間を追加(ジッター)
      const sleepTime = (Math.pow(2, i + 1) * 1000) + (Math.random() * 1000);
      Utilities.sleep(sleepTime);
    }
  }
  throw new Error(`最大リトライ回数後も失敗しました。: ${lastError}`);
}
}

III. スプレッドシートへの出力結果

GASを実行すると、スプレッドシートに抽出結果が追加されます。

GASの実行
スプレッドシートに結果が蓄積


4. AIトレンド調査の自動化

手動でも実行できますが、毎日「GASを実行→生成されたプロンプトをコピーしてGeminiで実行」は少しめんどくさいので、そこを自動化しちゃいましょう。

I. GAS時間トリガー設定

下記手順の通りに、GASのトリガー設定を行うと、その時間に自動実行されるようになります。

GAS時間トリガー設定1
GAS時間トリガー設定2

II. Gemini予約アクション設定

下記プロンプト例をGeminiに打ち込むと、予約アクションの設定を行うことができます。

プロンプト例

下記予約アクションを実施してください。

[予約アクションの詳細]
実行時間: 毎朝 07:15

頻度: 毎日(1日ごと)

通知内容: 「AIトレンドプロンプトの回答が完了しました」

実行内容: スプレッドシート『AI関連記事抽出』の最新行から「キーワード5つ」と「プロンプト」を取得し、以下のNotebookLM最適化テンプレートに沿ったMarkdown形式で回答を作成します。

レポートの構成案:
    ## [日付]([曜日]) 「AIトレンド要約」

    > Tags: #(関連ハッシュタグ)

    ### 1. キーワード5つ(箇条書き)

    ### 2. 実行プロンプト(内容をそのまま記載)

    ---

    ### 3. トレンド要約:[タイトル](分析結果)

    ---

    ### 4. 💡 NotebookLMへの問いかけ例(深掘り用)(質問案を3つ程度)


予約結果が下記のように表示されたら成功

Geminiの予約アクション

これで毎日決まった時刻にプロンプトが自動実行されるようになりました。
ゴールイメージ


5. NotebookLMへの最適化アウトプット

このシステムの最終ゴールは、NotebookLMに「良質なソース」として食わせることです。そのため、出力フォーマットを以下のようにMarkdown形式で厳格に定義しました。

## 2026年1月24日(土) 「AIトレンド要約」
> **Tags:** #AIエージェント #NVIDIA #2026トレンド

### 1. キーワード5つ
- [抽出されたキーワード]

### 2. 実行プロンプト
[Gemini_APIが生成した、さらに深い調査用のプロンプト]

### 3. トレンド要約
[構造化された分析内容]

### 4. 💡 NotebookLMへの問いかけ例
[その日の内容を深掘りするための質問案]

そうすることで、下記写真のようにマークダウン形式でもアウトプットしてくれるようになり、NotebookLMにより洗練した形で情報を素早く渡すことができるようになります。

ゴールイメージ2
ゴールイメージ3

6. おわりに

今回紹介した「GAS × Gemini API × NotebookLM」の連携システムは、単なるニュース収集ツールではありません。

日々膨大に生み出される情報の中から、自分にとって重要な「トレンドの芽」を自動で選別し、知識を保管しておいてくれる最強のパートナーです。

  1. GASが、広大なネットの海から情報を掬い上げ、
  2. Geminiが、そこから本質的なキーワードを抽出して整理・構造化し、
  3. NotebookLMに、いつでも引き出し可能な知識として記録させる。

このサイクルが回り始めることで、私たちは「情報の波に溺れる」状態から脱し、「集まった良質な情報を使って何をするか」という、より人間的でクリエイティブな活動に集中できるようになります。

Googleドライブの検索インデックス更新ラグへの対策など、運用上の工夫も紹介しましたが、一度設定してしまえば毎日の情報収集コストはゼロになります。

ぜひこの週末にでも環境を構築して、月曜日の朝から「自分だけの専属AIリサーチャー」が届けてくれるレポートを楽しみに迎えてみてください!

おまけ

以下のようなカスタマイズをしたい方向けに、拡張版のソースコードを用意しました:

  • キーワードのローテーション: 毎日異なるキーワードで検索したい(1週間で自動ローテーション)
  • Gmail通知: プロンプト生成結果を自動でGmailに送信したい

下記のソースコードを参考に、お好みに合わせてカスタマイズしてみてください。

/**
 * 設定: 取得したいキーワードとAPIキー
 */
const KEYWORDS_LIST = [ // 検索されるキーワードリスト
  'Model Context Protocol MCP',
  'Agentic Workflow AIエージェント',
  'RAG 検索拡張生成 グラフラグ',
  'マルチモーダルAI Gemini 活用事例',
  '自律型AIエージェント 開発トレンド',
  '推論モデル Reasoning Models',
  '小型言語モデル SLM エッジAI'
];
const MODEL_NAME = 'gemini-3-flash-preview';
const RECIPIENT_EMAIL = Session.getActiveUser().getEmail();

/**
 * メイン関数:ニュース取得からプロンプト生成まで実行
 */
function automateAiTrendAnalysis() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  
  // 1. 本日の日付オブジェクト
  const today = new Date();

  // 2. 通算日数(エポックタイムからの経過日数)を算出
  // getTime() はミリ秒を返すので、日で割って端数を切り捨てます
  const totalDays = Math.floor(today.getTime() / (1000 * 60 * 60 * 24));

  // 3. 通算日数をリスト数で割った余りをインデックスにする
  const index = totalDays % KEYWORDS_LIST.length;
  const currentKeyword = KEYWORDS_LIST[index];
  
  console.log(`通算日数: ${totalDays}日目 / 選択されたインデックス: ${index}`);
  console.log(`本日のキーワード: ${currentKeyword}`);

  // 3. 検索キーワードを動的に設定(従来の SEARCH_KEYWORD の代わり)
  const searchQuery = currentKeyword; 

  const newsItems = fetchGoogleNews(searchQuery);
  if (newsItems.length === 0) {
    console.warn(`キーワード「${searchQuery}」でのニュースは見つかりませんでした。`);
    return;
  }

  // 5. 記事タイトルを結合してGeminiに送るテキストを作成
  const newsContext = newsItems.map(item => `- ${item.title}`).join('\n');

  // 6. Gemini APIで分析
  const analysisResult = analyzeTrendsWithGemini(newsContext);
  
  // 7. スプレッドシートの最終行に書き込み
  const date = new Date();
  sheet.appendRow([
    date, 
    analysisResult.keywords.join(', '), 
    analysisResult.deepResearchPrompt
  ]);

  console.log('トレンド分析が完了しました。');

  // 8. Gmailで送信
  const subject = `【AIトレンド分析】本日の調査プロンプト (${Utilities.formatDate(date, "JST", "yyyy/MM/dd")})`;
  const body = `本日のキーワード: ${analysisResult.keywords.join(', ')}\n\n` +
               `以下のプロンプトをGemini (Deep Research等) にコピーして使用してください:\n\n` +
               `--------------------------------------------------\n` +
               `${analysisResult.deepResearchPrompt}\n` +
               `--------------------------------------------------`;
  
  GmailApp.sendEmail(RECIPIENT_EMAIL, subject, body);

  console.log('トレンド分析の完了およびメール送信に成功しました。');
}

/**
 * Google News RSSから最新1日分の記事を取得
 */
function fetchGoogleNews(keyword) {
  // キーワードに「 when:7d」を付け加えることで、過去1週間以内の記事に限定します
  const timeLimitQuery = `${keyword} when:7d`;
  const url = `https://news.google.com/rss/search?q=${encodeURIComponent(timeLimitQuery)}&hl=ja&gl=JP&ceid=JP:ja`;
  
  const response = UrlFetchApp.fetch(url);
  const xml = XmlService.parse(response.getContentText());
  const items = xml.getRootElement().getChild('channel').getChildren('item');
  
  // 取得件数を少し増やしたい場合は slice(0, 10) などに調整してください
  return items.slice(0, 10).map(item => ({
    title: item.getChildText('title'),
    link: item.getChildText('link'),
    pubDate: item.getChildText('pubDate') // 念のため公開日も取得可能
  }));
}

/**
 * Gemini APIを使用して分析を行う(安定版)
 */
function analyzeTrendsWithGemini(context) {
  const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
  const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL_NAME}:generateContent?key=${apiKey}`;
  const prompt = `
    以下の最新ニュースリストを分析し、必ず以下のJSON形式のみで回答してください。
    余計な解説文は一切含めないでください。

    1. 共通する重要な技術キーワードを3〜5個抽出してください(keywords)。
    2. それらのキーワードを元に、GeminiのDeep Research機能でさらに深掘り調査するための「具体的で高度な調査プロンプト」を1つ作成してください(deepResearchPrompt)。

    ニュースリスト:
    ${context}

    出力形式(JSON):
    {
      "keywords": ["ワード1", "ワード2"],
      "deepResearchPrompt": "ここにプロンプト"
    }
  `;

  const payload = {
    contents: [{ parts: [{ text: prompt }] }],
    // JSONモードを確実に有効化
    generationConfig: { 
      responseMimeType: "application/json" 
    }
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // エラー時に詳細を取得できるようにする
  };

  const res = fetchWithRetry(apiUrl, options);
  const jsonResponse = JSON.parse(res.getContentText());

  // デバッグ用:エラーが出た場合にログで中身を確認できるようにする
  if (jsonResponse.error) {
    console.error('APIエラー詳細:', jsonResponse.error.message);
    throw new Error('Gemini APIでエラーが発生しました: ' + jsonResponse.error.message);
  }

  // 安全にデータを抽出
  if (jsonResponse?.candidates?.[0]?.content?.parts?.[0]?.text) {
    return JSON.parse(jsonResponse.candidates[0].content.parts[0].text);
  } else {
    console.error('予期しないレスポンス形式:', jsonResponse);
    throw new Error('APIからの回答が空でした。');
  }

/**
 * 指数バックオフを用いたリトライ機能付きFetch
 */
function fetchWithRetry(url, options, maxRetries = 5) { // リトライ数5回
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = UrlFetchApp.fetch(url, options);
      const code = response.getResponseCode();
      
      // 成功(200)ならそのまま返す
      if (code === 200) return response;
      
      // 429(混雑)や500系エラーの場合はリトライ対象
      if (code === 429 || code >= 500) {
        console.warn(`一時的なエラー(${code})のためリトライします... (${i + 1}回目)`);
      } else {
        return response; // その他のエラーはそのまま返す
      }
    } catch (e) {
      lastError = e;
      console.warn(`リクエスト失敗: ${e.message} (${i + 1}回目)`);
    }
    
    if (i < maxRetries - 1) {
      // 指数バックオフ + 0〜1秒のランダムな待ち時間を追加(ジッター)
      const sleepTime = (Math.pow(2, i + 1) * 1000) + (Math.random() * 1000);
      Utilities.sleep(sleepTime);
    }
  }
  throw new Error(`最大リトライ回数後も失敗しました。: ${lastError}`);
}
}
GitHubで編集を提案

Discussion