見出し画像

Googleアラート × GAS × Geminiで「業界動向情報」をほぼ自動収集するワークフロー

※本記事はアフィリエイトプログラムを利用しています。Amazonのアソシエイトとして、当アカウントは適格販売により収入を得ています。

毎月の技術報告や定例レポート。その中の項目のひとつに「業界動向」のコーナーがあります。情報収集と要約に意外と時間がかかっています。

Googleアラートで関連キーワードをウォッチしてはいるものの、いざレポートにまとめるとなると、その期間(例えば1ヶ月分)に集まった大量のニュースを読み返し-いや、読み返さない場合も・・・時には、気になったニュースを掲載するだけなど、再現性があまり高くない作業になってしまっています。

*Goolgeアラートを使い始めたときは、「ほんとに便利だなぁ」と感心したのですが。。。本記事の最後にGoogleアラートの簡単な利用方法を掲載しています。

「(専門情報の収集もある中)この作業、もっとスマートにできないか…」

そこで、本記事では、Googleアラート × GAS × Geminiで情報収集を効率化した内容を掲載します。*あまりスマートな感じではありあませんが、何かのヒントになれば幸いです。もっといい方法があれば是非紹介してください。


それでは、スタートです。
*執筆者は以下を参照してください。


本記事の目指す情報収集法

本記事では、Googleアラートでキーワード「gemini」 を例に、すでに情報を収集していることを前提にしています。以下は流れです。

・Gmail(Googleアラート)でニュースを集約
・Apps Script(GAS)でスプレッドシートに自動一覧化
・Googleスプレッドシートの AI 関数(=AI)1か月分の業界動向を要約

それでは、上記のような流れをつくる方法をまとめます。


全体の構成

今回つくる仕組みは、ざっくり次の4ステップです。

  1. Googleスプレッドシートを準備する

  2. GAS で「ラベル:アラート × 件名:gemini」のメールを30日分集約

  3. 別シートで「直近30日分」だけに整理

  4. AI 関数(=AI)にまとめて要約させる

まずは「キーワード:gemini」1種類で形を作っておき、慣れてきたら他のキーワードにも横展開できる構成にしています。

Step 1. スプレッドシートの準備

  1. Googleスプレッドシートを新規作成

    • ファイル名例:業界動向_Geminiニュース

  2. 最初のシート名を 「ニュース一覧」 に変更

  3. 中身は空のままでOK(ヘッダは後で GAS から自動で入れます)

Step 2. Apps Script(GAS)でメールを自動集約する

2-1. Apps Script を開く

  1. スプレッドシートのメニューから
    [拡張機能]→[Apps Script] をクリック

  2. 開いたエディタで、最初から入っているコードを全て削除します。

上記のコードは削除します。

2-2. コード全文(キーワード「gemini」)

以下のコードをそのまま貼り付けます。
*本記事のコードは、実際に使っているものから会社名や個人が特定される情報を一切含まない形に調整したものです。

【2026年7月追記】本記事のコードに、セキュリティ改善(数式インジェクション対策の強化・URL検証の追加)を反映しました。すでに旧コードをご利用の方は、お手数ですが差し替えをおすすめします。

変更点は2つです。以前は「=」で始まる本文抜粋だけ対策していましたが、件名を含む外部由来の文字列すべてを「ただの文字列」として書き込むようにしました。また、URLは「http / https で始まる正しいリンクか」を確認してから書き込むようにしています。

/**
 * 初期セットアップ用:ヘッダ行を作成
 * 最初に1回だけ実行すればOK
 */
function setupNewsSheet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName('ニュース一覧');
  if (!sheet) {
    sheet = ss.insertSheet('ニュース一覧');
  }

  const headers = [
    '日付',       // A
    '件名',       // B
    'キーワード', // C(gemini)
    '本文抜粋',   // D
    'URL',       // E
    'メールID'   // F(重複防止用)
  ];

  sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
}

/* ============================================================
 *  セキュリティ用の共通関数(今回の修正で追加)
 * ============================================================ */

// [修正1] 数式インジェクション対策の共通サニタイズ関数
// スプレッドシートは = + - @ タブ で始まるセルを数式として解釈することがある。
// 外部由来の文字列(記事タイトル・本文抜粋など)がこれらの文字で始まる場合、
// 先頭にシングルクォートを付けて「ただの文字列」として書き込む。
function sanitizeForSheet(value) {
  const str = String(value || '');
  if (/^[=+\-@\t]/.test(str)) {
    return "'" + str;
  }
  return str;
}

// [修正2] URL検証の共通関数
// URLエンコードをデコードした後にも http / https スキームであることを確認する。
// javascript: などの危険なスキームがエンコードに紛れて入り込むのを防ぐ。
// 検証に通らないURLは空文字を返し、シートには書き込まない。
function safeDecodeUrl(encoded) {
  let decoded = '';
  try {
    decoded = decodeURIComponent(encoded);
  } catch (e) {
    decoded = encoded;
  }
  if (!/^https?:\/\//i.test(decoded)) return '';
  return decoded;
}

// [修正2] 本文から抽出したURLを検証・整形する
// Googleアラートのリンクは google.com/url?...&url=実際のURL... という
// リダイレクト形式のことが多いので、実際の記事URLを取り出したうえで検証する。
// リダイレクト形式でなければ、そのままスキーム検証だけ行う。
function extractSafeUrl(rawUrl) {
  if (!rawUrl) return '';
  const redirectMatch = rawUrl.match(/[?&]url=(https?[^&]+)/);
  if (redirectMatch) {
    return safeDecodeUrl(redirectMatch[1]);
  }
  return safeDecodeUrl(rawUrl);
}

/**
 * 過去30日分の「gemini」関連アラートメールを集約する
 * ラベル: アラート
 * キーワード: 件名に "gemini" を含むメール
 */
function collectGeminiAlertsLast30Days() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('ニュース一覧');

  if (!sheet) {
    throw new Error('「ニュース一覧」シートが見つかりません。先に setupNewsSheet() を実行してください。');
  }

  // 30日前を計算
  const today = new Date();
  const thirtyDaysAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);

  const labelName = 'アラート'; // Gmail のラベル名(必要に応じて変更)

  // 検索条件(キーワードは1つ:gemini)
  const keyword = 'gemini';

  // 既に書き込み済みのメールIDを取得して重複防止
  const idCol = 6; // F列
  const lastRow = sheet.getLastRow();
  const existingIds = new Set();

  if (lastRow > 1) {
    const ids = sheet.getRange(2, idCol, lastRow - 1, 1).getValues();
    ids.forEach(row => {
      if (row[0]) existingIds.add(String(row[0]));
    });
  }

  const rowsToAppend = [];

  // Gmail検索クエリ(過去30日・件名に "gemini"・ラベル「アラート」)
  const query = `label:${labelName} subject:"${keyword}" newer_than:30d`;
  const threads = GmailApp.search(query);

  threads.forEach(thread => {
    const messages = thread.getMessages();
    messages.forEach(msg => {
      const id = msg.getId();
      if (existingIds.has(id)) return; // 既に登録済みならスキップ

      const date = msg.getDate();
      // 念のため、30日より前のものは除外
      if (date < thirtyDaysAgo) return;

      // [修正1] 件名にもサニタイズを適用(旧版は未対策のままB列に書き込んでいた)
      const subject = sanitizeForSheet(msg.getSubject() || '');

      // 本文(プレーンテキスト優先、なければHTML→テキスト化)
      let body = '';
      try {
        body = msg.getPlainBody();
      } catch (e) {
        body = '';
      }
      if (!body) {
        try {
          const htmlBody = msg.getBody() || '';
          // HTMLタグを簡易的に除去してテキスト化
          body = htmlBody.replace(/<[^>]+>/g, ' ');
        } catch (e2) {
          body = '';
        }
      }
      body = String(body || '');

      // 本文から最初の200文字を抜粋(改行等をスペースにまとめる)
      let snippetText = body.replace(/\s+/g, ' ').trim().substring(0, 200);

      // 本文が空なら件名で代用
      if (!snippetText) {
        snippetText = (msg.getSubject() || '').substring(0, 200) || '(本文なし)';
      }

      // [修正1] 旧版は先頭が "=" の場合のみエスケープしていたが、
      // + - @ タブ始まりも数式扱いされうるため、共通関数で一括対策
      const snippet = sanitizeForSheet(snippetText);

      // 本文中の最初のURLを簡易抽出
      const urlMatch = body.match(/https?:\/\/[^\s">]+/);
      // [修正2] 抽出したURLは必ず extractSafeUrl() を通してから書き込む
      const url = urlMatch ? extractSafeUrl(urlMatch[0]) : '';

      rowsToAppend.push([
        date,
        subject,
        keyword,   // キーワード(今回は常に "gemini")
        snippet,
        url,
        id
      ]);

      existingIds.add(id);
    });
  });

  if (rowsToAppend.length > 0) {
    const startRow = sheet.getLastRow() + 1;
    sheet
      .getRange(startRow, 1, rowsToAppend.length, rowsToAppend[0].length)
      .setValues(rowsToAppend);
  }
}

2-3. 実行手順

  1. エディタ上部のプルダウンで setupNewsSheet を選んで ▶実行 をクリック(先に赤矢印部、保存してください)。

    • 初回は Google アカウント連携の権限許可が必要です

    • 実行後、「ニュース一覧」シートの1行目にヘッダが入ります

  2. 同様に collectGeminiAlertsLast30Days を選んで ▶実行 をクリック

    • Gmail の「アラート」ラベルの中から、件名に gemini を含む過去30日分のメールが検索されます

    • 「ニュース一覧」シートにニュースが1行ずつ追記されます

赤矢印で一旦保存しています。
「ニュース一覧」シートにニュースが1行ずつ追記されますことがわかります。

Step 3. 「ニュース30日」シートで期間を整理する

GASで既に「newer_than:30d」検索をしていますが、念のためスプレッドシート側でも「直近30日」に絞ったビューを作っておきます。

  1. 新しいシートを作成し、名前を 「ニュース30日」 に変更

  2. 「ニュース一覧」シートの A1:D1(ヘッダ)を
    「ニュース30日」シートの A1:D1 にコピー

  3. 「ニュース30日」シートの A2セル に、次の式を入れます:

=FILTER(ニュース一覧!A2:D, ニュース一覧!A2:A >= TODAY()-30)

Step 4. 要約用シートと AI 関数で「業界動向」を作る

4-1. 「要約」シートを作る

  1. さらに新しいシートを作成 → 名前を 「要約」 に変更

  2. 次のように入力します:

  • A2:gemini関連メール件数

  • B2:

=COUNTA(ニュース30日!A2:A)

4-2. プロンプト文を1セルで組み立てる(A5セル)

次に、AI 関数に渡す「指示文(プロンプト)」を A5で組み立てます。

A5セル:

="以下の表は、過去30日分の『アラート』ラベル付きニュースメールのうち、件名に「gemini」を含むものだけを集約した表です。" & CHAR(10) &
"列の意味は A列=日付、B列=件名、C列=キーワード(gemini)、D列=本文抜粋 です。" & CHAR(10) &
"gemini関連メール件数は " & B2 & " 件です。" & CHAR(10) &
"この表の内容をもとに、「gemini(生成AI、Gemini関連のサービス・機能・市場動向)」に関する最近1か月のトピックや傾向を要約してください。" & CHAR(10) &
"主なニュースの方向性(新機能リリース、料金やプランの変更、競合との比較、ビジネス活用事例など)と、当社の設計・開発業務にとっての示唆を、3〜5行程度の日本語の箇条書きで書いてください。" & CHAR(10) &
"ニュースが1件以上ある場合は、情報が少なくても必ず何らかの要約を書いてください。「geminiに関する情報はありません」とは書かないでください。" & CHAR(10) &
"本当にニュースが1件もない場合のみ、その旨を1行で明記してください。" & CHAR(10) &
"最終的な文章は、設計会議の議事録の「業界動向(gemini)」欄にそのまま貼れるよう、簡潔で読みやすい日本語にしてください。"

*セルの表示形式で「テキストの折り返し → 折り返す」にしておくと、中身が読みやすくなります。

4-3. AI 関数で要約を生成する(A8セル)

Googleスプレッドシートの AI 関数は、

  • =AI(プロンプト, データ範囲)

  • かつ「AI関数の中に他の関数を入れてはいけない(併用不可)」

という仕様があるので、AIセルはシンプルにこう書きます。

A8セル:

=AI(A5, ニュース30日!A1:D50)

・第1引数:A5(プロンプト文)
・第2引数:ニュース一覧(直近30日)を含む範囲
 ・行数は、ニュース件数+少しの余裕ぐらいにしておきます
 ・例:20件くらいなら A1:D30、もう少し多そうなら A1:D50 など
・他の関数(FILTER, TEXTJOIN など)は併用しない

この出力をそのまま 「業界動向(gemini)」として利用すればOKです。また、この文章をさらに生成AIで肉付けしてもいいかもしれません。

・GeminiはGoogleマップ・Chrome・Gmail・スプレッドシートなど、各サービスに急速に統合され、RAGやエージェント構築まで機能が拡張している。
・モデルもGemini 2.0/3や画像生成AI Nano Bananaなど、多機能・専門特化の方向へ進化している。
・AppleやGMとの提携により、Google外のエコシステムにも広がっており、市場展開が加速している。
・設計・開発では(*筆者の属するセクションとしての場合がアウトプットされています)、Gmail・Drive連携やスプレッドシート自動化、RAGによる独自データ検索をワークフローに組み込むことが重要で、同時にChatGPT・Claudeなど競合との比較評価も継続して行う必要がある。

2025年10~11月頃のニュースを簡単にまとめた内容。詳細は、スプレッドシート上でのリンク先からニュースを確認できます。

まとめ

本記事では「Googleスプレッドシート」という最も身近なツールが、情報収集と「業界動向」レポート作成にとても有用であることがわかりました。

Geminiとの連携でスプレッドシートの機能は格段に高まりつつあり、流石です。まずは手元のツールで「情報が一箇所に集まる仕組み」を作ることができて、だいぶ毎月のレポートが楽になりました。

皆様の日々の情報収集でヒントになれば幸いです。

おまけ:Googleアラートの利用法

以下にGoogleアラートの利用についてGoogle AIモードで聞きました。

Google アラートは、指定したキーワードに関連する最新の Google 検索結果(ウェブページ、ニュース記事など)を自動で検知し、メールまたはRSSで通知する無料サービスです。
利用方法
Google アカウントを持っていれば誰でも無料で利用できます。
Google アラートのページにアクセスします(「Google アラート」で検索)。アラートを設定したいキーワードを入力します。
「オプションを表示」をクリックして、通知頻度、ソース、言語などの詳細設定を行います。
「アラートを作成」をクリックして完了です。
ビジネスから個人的な興味まで、幅広い用途で活用できる便利なツールです。

Gmail上ではラベル「アラート」にて表示されています。

▼GAS関連書籍

▼生成AIに関する書籍


いいなと思ったら応援しよう!