見出し画像

情報収集は自動化すると楽になる

情報が多すぎる。

でもキャッチアップしないと不安になる。

だから自動化してる。

今日は私の自動化の一部を公開してみる。

情報収集に辟易しているなら、GASのハードルを越える必要があるけど、とても便利なのでおすすめ。

キーワードやYouTubeチャンネルを登録しておけば、こんな感じで勝手に最新情報が溜まっていく。

それをNotebookLMに同期して、「今日更新のnote記事でAIについてまとめて」とか、自分のnote記事やプロフィールも入れてるから「私が見た方が良いもの教えて」って聞くだけ。

自分で検索すると、自分が知りたいものしか見つからない。
でもこれなら、自分では検索しなかったものも提案してくれる。

「このキーワードも追加した方がいいかも」って提案までしてくれる。
キーワード以外の情報(ノイズ)を見てしまって、集中力を阻害されることもなくなる。



✦ 情報取得~整理の仕組み

  1. GASでnote記事やYouTubeを自動取得 → スプレッドシートに蓄積

  2. NotebookLM のソースにスプレッドシートを追加
    ※私はここも自作の拡張機能作ってNotebookLMに自動同期してる

  3. NotebookLMでまとめた情報をキーワード毎に要約したりできる

❚ note

方法1:RSS版(公式・安定)
スキ数は取れないが、完全無料で上限なし。
仕様変更のリスクが低いので今回はこちら✅

方法2: API版(非公式・データ豊富)
スキ数も取れるが、仕様が変更される可能性あり
※ noteの非公式APIを利用する方法

❚ YouTube

YouTube Data APIで取得。無料枠があって、1日100回くらい検索できる。
個人利用なら十分。


長いので、今回はGASの設定とスプレッドシートで取得まで。


✦ 今回解説する全体の流れ

  • Googleドライブで新しいスプレッドシートを作成

  • メニューから「拡張機能」→「Apps Script」を選択

  • 最初から入っている function myFunction() { } を全部消して、コードを貼り付ける

  • フロッピーアイコン、または Ctrl + S で保存

  • YouTube取得する場合は、左メニュー「サービス」→「+」→「YouTube Data API v3」を追加

  • 初回実行時にGoogleアカウントの権限承認を求められるので「許可」

  • 取得したい情報を設定タブに入力

  • GASを実行して取得

  • 自動実行したい場合は、左メニュー「トリガー」→「+トリガーを追加」から設定


今回使わせてもらうギガビットさんのnoteとYouTubeはこちら。
毎日ゲーム作るチャレンジの、とあるゲームに、娘がハマった。

note

YouTube


✦ 準備

作業の流れはこんな感じ。

✅スプレッドシートを準備

まず、新しいスプレッドシートを立ち上げてタイトルを付ける。
※今回は例として「ギガビットさん:note & YouTube」にする。

スプレッドシートを用意

✅GASの設定

1.拡張機能メニューからGoogle Apps Scriptを選択。

2.コード.gsに初期で入っている下記のコードを削除。

function myFunction() {
  
}

3.後半に記載しているGASコードをまるごと貼り付けて保存。
※保存ボタンまたはCtrl+S
※保存していない場合は、左側のコード.gsにオレンジのマークがついている

必ず保存する

※YouTube取得する場合
左メニュー「サービス」→「+」→「YouTube Data API v3」を追加

「YouTube Data API v3」を追加

4.スプレッドシートに戻って更新する

上部メニューに「🔍SNSリサーチ・ハブ」が出てくる。

更新するとメニューが出てくる

✦ 実行

作業の流れはこんな感じ。
警告画面がどのタイミングで出てくるかチェック。

初回実行時

📢【注意】初回実行時に怖い警告画面が出ます
最初の動作時は一瞬びっくりすると思うので、心の準備をしておく。

✅初回実行

「1.設定シートを準備・修復ボタン」をクリック

「1.設定シートを準備・修復」ボタンをクリック

「このアプリはGoogleで確認されていません」って出る。
赤い警告マークで怖いけど、大丈夫。

これは、自分で作ったプログラム。
どんな挙動をするかは自分がわかってる。
GASを使う上で必ず通る道。慣れよう。


突破方法:

1.画面左下の「詳細」をクリック

「詳細」をクリック


2.「無題のプロジェクト(安全ではないページ)に移動」をクリック
次の画面で「許可」を押せば完了

安全ではないページに移動をクリック

「安全ではない」って書いてあるけど、自分で入れたコードだから問題ない。
デベロッパーのメールアドレスが自分のものになっていることを確認すれば、自分が作成・管理しているスクリプト。

不安な人はこの前に、AIにGASコード全部読ませて安全?って聞いてみるのがオススメ。

ここを乗り越えると、設定タブが新しく作成されるので、次は取得したいものを設定していく。


✅何を取得するか設定

作業の流れはこんな感じ。

何を取得するか設定~取得された結果
  • 設定を保存するタブ名

    • 設定

  • 見出し

    • 媒体:note / YouTube

    • 種別:タグ/キーワード、ユーザーID、チャンネルID

    • ID または キーワード:検索したいワードやID

    • メモ:自由

設定例

■noteのユーザー名の取得方法

noteのプロフィールページを開いて、URLの note.com/ の後ろがユーザー名。

URLの note.com/ の後ろがユーザー名

例:ギガビットさんの場合
https://note.com/gigabit_million → gigabit_million

ギガビット@ゲームつくるひと

■YouTubeチャンネルIDの取得方法

YouTubeでチャンネルページを開く

  1. 説明のところの「さらに表示」をクリック

  2. 下にある「チャンネルを共有」をクリック

  3. 「チャンネルIDをコピー」をクリック

説明のところの「さらに表示」をクリック
「チャンネルを共有」をクリック→「チャンネルIDをコピー」をクリック

これでチャンネルIDがコピーできる。

例:ギガビットさんの場合
UC3Fppua1Iytc4OjMj_bS_Dw

ギガビット@ゲームつくるひと

✅メニューからGASを実行して情報を取得する

2と3が取得ボタン

「2. note:最新記事を取得」をクリック
または
「3. YouTube:一括リサーチ」をクリック

すると、自動でタブが作られてデータが入ってくる。

noteは最新の30件くらいしか取得できないので、早いとこ取得を始めた方がいい。

✅取得された結果

トリガーを設定しておけば、毎日自動で取得される。

noteはタイトル、URL、要約、サムネなどを取得。

noteリストの画面

YouTubeは再生数、高評価、評価率、コメント数などを取得できる。

YouTubeリストの画面

列の順番は、私の場合noteはタイトル、YouTubeはサムネで選ぶのでこういう順番にしてる。

AIに聞いたら順番とか取得するものを変えてくれるので、お好きなように。


✦ トリガーの設定方法

  • 左側のバーの「時計アイコン」をクリック

  • 右下の「トリガーを追加ボタン」をクリック

  • トリガーの設定をする

    • 実行する関数:たくさん出てくる中から2つ

      • noteのトリガー:実行する関数:FETCH_ALL_NOTES

      • YouTubeのトリガー:実行する関数:FETCH_YOUTUBE_ALL

    • イベントのソース:いつ実行するかを設定

トリガーの設定方法
実行する関数

■ noteのトリガー

実行する関数:FETCH_ALL_NOTES

noteのトリガー

■ YouTubeのトリガー

実行する関数:FETCH_YOUTUBE_ALL

YouTubdeのトリガー

次回は、このスプレッドシートをNotebookLMにソースとして読み込ませて、何ができるかを書く予定。

…だったんだけど、解説系のnote書くのが思った以上にしんどいから、書かないかも(これも5日かかった)
しかも、動画撮ったけどアップできず、GIFにしたけどアップできなくて、さらに調整すること数時間。
(こういう時に、自分のYouTubeチャンネルがあると強いなー…)


✦ GASのコード

一応貼っておくけど、自分なりに修正して使ってください。

/**
 * メニュー作成
 */
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu('🔍SNSリサーチ・ハブ')
    .addItem('1. 設定シートを準備・修復', 'INITIALIZE_SETTINGS')
    .addSeparator()
    .addItem('2. note:最新記事を取得', 'FETCH_ALL_NOTES')
    .addItem('3. YouTube:一括リサーチ', 'FETCH_YOUTUBE_ALL')
    .addToUi();
}

/**
 * 媒体の選択に合わせて、隣のプルダウンを動的に書き換える
 */
function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== "設定") return;
  const range = e.range;
  
  if (range.getColumn() === 1 && range.getRow() >= 2) {
    const medium = range.getValue();
    const typeCell = range.offset(0, 1);
    let options = [];
    
    if (medium === "note") {
      options = ["ユーザーID", "タグ/キーワード"];
    } else if (medium === "YouTube") {
      options = ["チャンネルID"]; // YouTubeはこれのみ
    }
    
    if (options.length > 0) {
      const rule = SpreadsheetApp.newDataValidation().requireValueInList(options).build();
      typeCell.setDataValidation(rule);
      typeCell.setValue(options[0]);
    } else {
      typeCell.clearDataValidation();
      typeCell.setValue("");
    }
  }
}

/**
 * 安全にアラートを表示する補助関数
 */
function safeAlert(message) {
  try { SpreadsheetApp.getUi().alert(message); } catch (e) { console.log(message); }
}

/**
 * 1. 設定シートの整備
 */
function INITIALIZE_SETTINGS() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName("設定") || ss.insertSheet("設定");
  
  sheet.getRange(1,1,1,4).setValues([["媒体", "種別", "ID または キーワード", "メモ"]])
        .setFontWeight("bold").setBackground("#d9ead3").setHorizontalAlignment("center");
  
  sheet.getRange("A2:A100").setDataValidation(SpreadsheetApp.newDataValidation().requireValueInList(["note", "YouTube"]).build());
  
  sheet.setColumnWidth(1, 100);
  sheet.setColumnWidth(2, 120);
  sheet.setColumnWidth(3, 250);
  sheet.setColumnWidth(4, 200);
  
  safeAlert("設定シートを準備しました。YouTubeの種別は「チャンネルID」のみとなります。");
}

/**
 * 2. note取得
 */
function FETCH_ALL_NOTES() {
  const data = getSettingsData("note");
  if (data.length === 0) return;

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName("noteリスト") || ss.insertSheet("noteリスト");
  setupNoteHeader(sheet);

  const existingUrls = sheet.getDataRange().getValues().map(row => row[3]);
  const newEntries = [];

  data.forEach(item => {
    let rssUrl = (item.type === "ユーザーID") 
      ? `https://note.com/${item.id}/rss` 
      : `https://note.com/hashtag/${encodeURIComponent(item.id)}/rss`;

    try {
      const response = UrlFetchApp.fetch(rssUrl, {muteHttpExceptions: true});
      if (response.getResponseCode() !== 200) return;
      
      const xml = response.getContentText();
      const document = XmlService.parse(xml);
      const items = document.getRootElement().getChild('channel').getChildren('item');
      const media = XmlService.getNamespace('media', 'http://search.yahoo.com/mrss/');

      items.forEach(article => {
        const link = article.getChildText('link');
        if (existingUrls.indexOf(link) === -1) {
          const title = article.getChildText('title');
          const pubDate = new Date(article.getChildText('pubDate'));
          let desc = (article.getChildText('description') || "").replace(/<[^>]+>/g, " ").replace(/(続きをみる|続きを見る)/g, "").replace(/[…\.]/g, "").replace(/\s+/g, " ").trim();
          const thumb = article.getChild('thumbnail', media);
          const img = thumb ? `=IMAGE("${thumb.getText()}")` : "画像なし";

          newEntries.push(["", item.id, title, link, pubDate, desc, img]);
          existingUrls.push(link);
        }
      });
    } catch (e) { console.error(`noteエラー: ${e.message}`); }
  });

  if (newEntries.length > 0) {
    newEntries.sort((a, b) => a[4] - b[4]);
    sheet.getRange(sheet.getLastRow() + 1, 1, newEntries.length, 7).setValues(newEntries);
    formatNoteSheet(sheet);
    safeAlert("noteの新着記事を追加しました。");
  }
}

/**
 * 3. YouTube取得(チャンネルID限定版)
 */
function FETCH_YOUTUBE_ALL() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const data = getSettingsData("YouTube");
  if (data.length === 0) return;

  let sheet = ss.getSheetByName("YouTubeリサーチ") || ss.insertSheet("YouTubeリサーチ");
  setupYoutubeHeader(sheet);

  const existingUrls = sheet.getDataRange().getValues().map(row => row[4]);
  const newVideos = [];

  data.forEach(item => {
    try {
      // チャンネルID指定のみを実行
      if (item.type !== "チャンネルID") return;

      let res = YouTube.Search.list("snippet", { 
        channelId: item.id, 
        maxResults: 50, 
        order: "date", 
        type: "video" 
      });

      if (!res.items || res.items.length === 0) return;
      const videoIds = res.items.map(v => v.id.videoId).join(",");
      const details = YouTube.Videos.list("snippet,statistics,contentDetails", { id: videoIds });

      details.items.forEach(v => {
        const url = `https://www.youtube.com/watch?v=${v.id}`;
        if (existingUrls.indexOf(url) === -1) {
          const stats = v.statistics;
          const vCount = parseInt(stats.viewCount || 0);
          const lCount = parseInt(stats.likeCount || 0);
          const lRatio = vCount ? (vCount / (lCount || 1)).toFixed(1) : 0;
          const duration = formatDuration(v.contentDetails.duration);

          newVideos.push([
            item.id, v.snippet.channelTitle, `=IMAGE("${v.snippet.thumbnails.medium.url}")`, 
            v.snippet.title, url, duration, vCount, lCount, lRatio, parseInt(stats.commentCount || 0),
            v.snippet.publishedAt.split("T")[0], (v.snippet.tags ? v.snippet.tags.join(", ") : "なし"), v.snippet.description.substring(0, 300)
          ]);
          existingUrls.push(url);
        }
      });
    } catch (e) { console.error(`YTエラー: ${e.message}`); }
  });

  if (newVideos.length > 0) {
    newVideos.sort((a, b) => new Date(a[10]) - new Date(b[10]));
    sheet.getRange(sheet.getLastRow() + 1, 1, newVideos.length, 13).setValues(newVideos);
    formatYoutubeSheet(sheet);
    safeAlert(`${newVideos.length} 件の動画を追加しました!`);
  }
}

// --- 補助関数:以下、共通ロジック ---
function getSettingsData(platformName) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const config = ss.getSheetByName("設定");
  if (!config) return [];
  const lastRow = config.getLastRow();
  if (lastRow < 2) return [];
  const rows = config.getRange(2, 1, lastRow - 1, 3).getValues();
  return rows.filter(row => row[0].toString().trim().toLowerCase() === platformName.toLowerCase() && row[2] !== "")
             .map(row => ({ type: row[1].toString().trim(), id: row[2].toString().trim() }));
}
function setupNoteHeader(s) {
  if (s.getLastRow() === 0) {
    s.getRange(1,1,1,7).setValues([["No.","取得元","Title","URL","Date Created","Summary","Thumbnail"]]).setFontWeight("bold").setBackground("#eeeeee").setHorizontalAlignment("center");
    s.setFrozenRows(1);
  }
}
function formatNoteSheet(s) {
  const lr = s.getLastRow();
  if (lr < 2) return;
  const nr = []; for(let i=1; i<lr; i++){ nr.push([i]); }
  s.getRange(2, 1, nr.length, 1).setValues(nr);
  s.getRange(2, 5, lr - 1, 1).setNumberFormat('yyyy/MM/dd HH:mm');
  s.getRange(2, 3, lr - 1, 1).setWrap(true);
  s.getRange(2, 6, lr - 1, 1).setWrap(true);
  s.getRange(2, 1, lr - 1, 7).setVerticalAlignment("middle");
  s.setRowHeights(2, lr - 1, 80);
  s.autoResizeColumns(1, 5);
  s.setColumnWidth(3, 300);
  s.setColumnWidth(6, 400);
  SpreadsheetApp.flush();
}
function setupYoutubeHeader(s) {
  if (s.getLastRow() === 0) {
    const headers = [["検索/ch","チャンネル","サムネ","タイトル","URL","動画尺","再生数","高評価","評価率","コメント","公開日","タグ","概要"]];
    s.getRange(1,1,1,13).setValues(headers).setFontWeight("bold").setBackground("#fce8e6").setHorizontalAlignment("center");
    s.setFrozenRows(1);
  }
}
function formatYoutubeSheet(s) {
  const lr = s.getLastRow();
  if (lr < 2) return; 
  const range = s.getRange(2, 1, lr - 1, 13);
  range.setWrap(true);
  range.setVerticalAlignment("middle");
  for (let i = 2; i <= lr; i++) { s.setRowHeight(i, 90); }
  s.setColumnWidth(3, 160);
  s.setColumnWidth(4, 250);
  s.setColumnWidth(12, 200); 
  s.setColumnWidth(13, 300); 
  SpreadsheetApp.flush();
}
function formatDuration(duration) {
  const match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/);
  const h = (match[1] || "0").replace("H", "");
  const m = (match[2] || "0").replace("M", "");
  const s = (match[3] || "0").replace("S", "");
  return `${h.padStart(2, "0")}:${m.padStart(2, "0")}:${s.padStart(2, "0")}`;
}

自己紹介も読んでいただけると嬉しいです。

私が、去年から作っているサービスMintor(ミンター)もチェックしてくれると、さらに嬉しいです!
https://mintor.dev


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