[GAS]noteの記事データをRSSで拾ってNotionに自動格納する
がちゃがちゃ色々作ってみたシリーズです。
やりたいこと
noteの過去記事含めた記事情報データをnotionに自動格納したい
記事情報データは以下を取りたい
記事タイトル
投稿日
投稿時間
格納サービス&価格
URL
本文
やれたこと
noteの過去記事含めた記事情報データをnotionに自動格納したい △
→最新含めた過去分25件だけ。以降データは格納可能に。記事情報データは以下を取りたい 〇
記事タイトル
投稿日
投稿時間
格納サービス&価格
URL
本文
→本文データ以外は問題なく収集可能だった。note proでないとRSSで本文データを取ることはnote側が提供していない
スプシとNotion画面
スクリプト:RSS情報をスプシに集約
function fetchNoteArticles() {
//********************************
// 必要な設定情報
//********************************
const feedUrl = 'https://note.com/papapico/rss'; // noteのRSSフィードURL
const sheetName = 'note_articles'; // スプレッドシートのシート名
//********************************
// スプレッドシートの取得
//********************************
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
if (!sheet) {
throw new Error("シート名 '" + sheetName + "' が見つかりません。");
}
//********************************
// 既存データのURLを取得
//********************************
const existingUrls = new Set();
const lastRow = sheet.getLastRow();
if (lastRow > 1) { // ヘッダー行がある場合
const urlRange = sheet.getRange(2, 2, lastRow - 1, 1).getValues(); // URL列を取得 (B列)
urlRange.forEach(row => existingUrls.add(row[0])); // URLをセットに追加
}
//********************************
// RSSフィードを取得
//********************************
const response = UrlFetchApp.fetch(feedUrl);
const xml = response.getContentText();
const document = XmlService.parse(xml);
const entries = document.getRootElement()
.getChild('channel')
.getChildren('item');
let newArticles = [];
//********************************
// 記事データの解析
//********************************
entries.forEach(entry => {
const title = entry.getChildText('title'); // 記事タイトル
const link = entry.getChildText('link'); // 記事URL
const pubDate = entry.getChildText('pubDate'); // 公開日 (UTC+9形式)
// 日付と時間を日本時間形式で取得
const { formattedDate, formattedTime } = formatPubDateWithColons(pubDate);
// 記事ページを取得
const articleResponse = UrlFetchApp.fetch(link);
const articleHtml = articleResponse.getContentText();
// 価格情報を解析
const priceMatch = articleHtml.match(/<span[^>]*>¥([\d,]+)/);
const price = priceMatch ? priceMatch[1].replace(/,/g, '') : '0'; // 価格がない場合は0埋め
// 曜日を取得(短縮形式)
const weekday = getShortWeekdayFromDate(formattedDate);
// 既存データと比較して新しい記事のみリストに追加
if (!existingUrls.has(link)) {
newArticles.push([title, link, formattedDate, formattedTime, price, "", weekday]);
}
});
//********************************
// データを古い順にソート
//********************************
newArticles.sort((a, b) => {
const dateComparison = a[2].localeCompare(b[2]); // 公開日を比較
if (dateComparison === 0) {
return a[3].localeCompare(b[3]); // 同じ日なら公開時間を比較
}
return dateComparison;
});
//********************************
// スプレッドシートにデータを書き込む
//********************************
newArticles.forEach(article => sheet.appendRow(article));
// マガジン列に関数を設定
const newLastRow = sheet.getLastRow();
for (let i = lastRow + 1; i <= newLastRow; i++) {
const formula = `=IF(E${i}=480,"ぱぴちゃんとお話",IF(E${i}=900,"all of ぱぴ room",IF(E${i}=2980,"ぱぴちゃん家を買う","0")))`;
sheet.getRange(i, 8).setFormula(formula); // マガジン列はH列
}
Logger.log(`${newArticles.length} 件の新しい記事が追加されました。`);
}
//********************************
// 日付を日本時間形式に変換する関数(hh:mm:ss対応)
//********************************
function formatPubDateWithColons(pubDate) {
const date = new Date(pubDate);
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const day = ('0' + date.getDate()).slice(-2);
const hours = ('0' + date.getHours()).slice(-2);
const minutes = ('0' + date.getMinutes()).slice(-2);
const seconds = ('0' + date.getSeconds()).slice(-2);
return {
formattedDate: `${year}${month}${day}`, // yyyymmdd形式
formattedTime: `${hours}:${minutes}:${seconds}` // hh:mm:ss形式
};
}
//********************************
// 日付(yyyymmdd)から短縮曜日を取得する関数
//********************************
function getShortWeekdayFromDate(dateStr) {
const year = parseInt(dateStr.slice(0, 4), 10);
const month = parseInt(dateStr.slice(4, 6), 10) - 1; // 月は0ベース
const day = parseInt(dateStr.slice(6, 8), 10);
const date = new Date(year, month, day);
const shortWeekdays = ["日", "月", "火", "水", "木", "金", "土"];
return shortWeekdays[date.getDay()];
}
スクリプト:スプシからNotionに転記
function addDataToNotionFromSpreadsheet() {
const props = PropertiesService.getScriptProperties();
const dbId = props.getProperty('NOTION_DB_ID'); // NotionデータベースID
const token = props.getProperty('NOTION_TOKEN'); // Notion APIトークン
const apiUrl = 'https://api.notion.com/v1/pages';
const sheetName = 'note_articles'; // スプレッドシート名
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
if (!sheet) {
Logger.log(`シート名 "${sheetName}" が見つかりません。スプレッドシートを確認してください。`);
return;
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
Logger.log('データがありません。スクリプトを終了します。');
return;
}
for (let i = 2; i <= lastRow; i++) {
const isTransferred = sheet.getRange(i, 6).getValue(); // 転記済列(F列)を確認
if (!isTransferred) {
const title = String(sheet.getRange(i, 1).getValue()).trim(); // タイトル (A列)
const url = String(sheet.getRange(i, 2).getValue()).trim(); // URL (B列)
const postDate = String(sheet.getRange(i, 3).getValue()).trim(); // 投稿日 (C列)
// 投稿時間を文字列として取得(hh:mm:ss形式)
const postTime = formatTime(sheet.getRange(i, 4).getValue()); // 投稿時間 (D列)
const price = sheet.getRange(i, 5).getValue(); // 価格 (E列)
const weekday = String(sheet.getRange(i, 7).getValue()).trim(); // 曜日 (G列)
const magazine = getMagazineFromPrice(price); // 価格に基づいてマガジンを判定
// Notionのページ作成データ
const pageObj = {
parent: {
database_id: dbId,
},
properties: {
"タイトル": {
"title": [{
"text": {
"content": title
}
}]
},
"URL": {
"url": url
},
"投稿日": {
"rich_text": [{
"text": {
"content": postDate // 投稿日を文字列として転記
}
}]
},
"投稿時間": {
"rich_text": [{
"text": {
"content": postTime // 投稿時間を hh:mm:ss 形式で転記
}
}]
},
"価格": {
"number": Number(price) // 数値型
},
"曜日": {
"rich_text": [{
"text": {
"content": weekday // 曜日を文字列として転記
}
}]
},
"コンテンツ種別": {
"select": {
"name": magazine // セレクト型で価格に対応したマガジン名を転記
}
}
}
};
Logger.log(`送信データ: ${JSON.stringify(pageObj)}`); // デバッグ用ログ
const options = {
method: "POST",
headers: {
"Content-type": "application/json",
"Authorization": "Bearer " + token,
"Notion-Version": '2022-06-28',
},
payload: JSON.stringify(pageObj),
};
try {
const response = UrlFetchApp.fetch(apiUrl, options);
if (response.getResponseCode() === 200) {
Logger.log(`行 ${i} のデータをNotionに転記しました: ${title}`);
sheet.getRange(i, 6).setValue("done"); // F列に "done" を記録
} else {
Logger.log(`行 ${i} のデータ転記に失敗しました: ${response.getContentText()}`);
}
} catch (e) {
Logger.log(`エラーが発生しました: ${e.message}`);
}
}
}
}
//********************************
// 投稿時間を hh:mm:ss 形式に変換する関数
//********************************
function formatTime(value) {
if (value instanceof Date) {
const hours = ('0' + value.getHours()).slice(-2);
const minutes = ('0' + value.getMinutes()).slice(-2);
const seconds = ('0' + value.getSeconds()).slice(-2);
return `${hours}:${minutes}:${seconds}`;
}
return String(value).trim(); // 既に文字列の場合はそのまま返す
}
//********************************
// 価格に基づいてマガジン名を判定する関数
//********************************
function getMagazineFromPrice(price) {
if (price === 480) {
return "ぱぴちゃんとお話";
} else if (price === 900) {
return "all of ぱぴ room";
} else if (price === 2980) {
return "ぱぴちゃん家を買う";
} else {
return "不明"; // 未知の価格の場合
}
}
コピペでもいけるけど…
これは個人的に「自動化したほうがコピペ管理するより楽…」という部類でした。Notionは表をコピペしてDBに変換するという超お手軽機能もあるのですが「コピペで都度管理」って私が最も苦手&嫌いな作業だったりするので、週次くらいでupdate情報が自動でNotionに格納されるのは楽だしイイネ!と思います。
まぁすべては「今からはいいんだけど、過去分のデータを入れたい」部分をクリアできてないわけですが…。
API、公開されている時とダメな時があり、今はダメな時っぽいんだよね…。公開されているときにがっと取ってくる方が楽では…?みたいな気持ちになる。
やりたいことは上記なんだけど、1万の価値はないというか、だったら自分でやる…となる(やらない)
ここから先は
0字
ありがとうございます。『あなたの課金は、私の課金』を標語に経済をまわしていきましょう。