情報収集は自動化すると楽になる
情報が多すぎる。
でもキャッチアップしないと不安になる。
だから自動化してる。
今日は私の自動化の一部を公開してみる。

情報収集に辟易しているなら、GASのハードルを越える必要があるけど、とても便利なのでおすすめ。
キーワードやYouTubeチャンネルを登録しておけば、こんな感じで勝手に最新情報が溜まっていく。

それをNotebookLMに同期して、「今日更新のnote記事でAIについてまとめて」とか、自分のnote記事やプロフィールも入れてるから「私が見た方が良いもの教えて」って聞くだけ。
自分で検索すると、自分が知りたいものしか見つからない。
でもこれなら、自分では検索しなかったものも提案してくれる。
「このキーワードも追加した方がいいかも」って提案までしてくれる。
キーワード以外の情報(ノイズ)を見てしまって、集中力を阻害されることもなくなる。

✦ 情報取得~整理の仕組み
GASでnote記事やYouTubeを自動取得 → スプレッドシートに蓄積
NotebookLM のソースにスプレッドシートを追加
※私はここも自作の拡張機能作ってNotebookLMに自動同期してる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」を追加

4.スプレッドシートに戻って更新する
上部メニューに「🔍SNSリサーチ・ハブ」が出てくる。


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

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

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

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

これは、自分で作ったプログラム。
どんな挙動をするかは自分がわかってる。
GASを使う上で必ず通る道。慣れよう。
突破方法:
1.画面左下の「詳細」をクリック

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

「安全ではない」って書いてあるけど、自分で入れたコードだから問題ない。
デベロッパーのメールアドレスが自分のものになっていることを確認すれば、自分が作成・管理しているスクリプト。
不安な人はこの前に、AIにGASコード全部読ませて安全?って聞いてみるのがオススメ。
ここを乗り越えると、設定タブが新しく作成されるので、次は取得したいものを設定していく。

✅何を取得するか設定
作業の流れはこんな感じ。

設定を保存するタブ名
設定
見出し
媒体:note / YouTube
種別:タグ/キーワード、ユーザーID、チャンネルID
ID または キーワード:検索したいワードやID
メモ:自由

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

例:ギガビットさんの場合
https://note.com/gigabit_million → gigabit_million
■YouTubeチャンネルIDの取得方法
YouTubeでチャンネルページを開く
説明のところの「さらに表示」をクリック
下にある「チャンネルを共有」をクリック
「チャンネルIDをコピー」をクリック


これでチャンネルIDがコピーできる。
例:ギガビットさんの場合
UC3Fppua1Iytc4OjMj_bS_Dw
✅メニューからGASを実行して情報を取得する

「2. note:最新記事を取得」をクリック
または
「3. YouTube:一括リサーチ」をクリック
すると、自動でタブが作られてデータが入ってくる。
noteは最新の30件くらいしか取得できないので、早いとこ取得を始めた方がいい。

✅取得された結果
トリガーを設定しておけば、毎日自動で取得される。
noteはタイトル、URL、要約、サムネなどを取得。

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

列の順番は、私の場合noteはタイトル、YouTubeはサムネで選ぶのでこういう順番にしてる。
AIに聞いたら順番とか取得するものを変えてくれるので、お好きなように。
✦ トリガーの設定方法
左側のバーの「時計アイコン」をクリック
右下の「トリガーを追加ボタン」をクリック
トリガーの設定をする
実行する関数:たくさん出てくる中から2つ
noteのトリガー:実行する関数:FETCH_ALL_NOTES
YouTubeのトリガー:実行する関数:FETCH_YOUTUBE_ALL
イベントのソース:いつ実行するかを設定


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

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

次回は、このスプレッドシートを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

