見出し画像

[GAS]noteの記事データをRSSで拾ってNotionに自動格納する

がちゃがちゃ色々作ってみたシリーズです。

やりたいこと

  • noteの過去記事含めた記事情報データをnotionに自動格納したい

  • 記事情報データは以下を取りたい

    • 記事タイトル

    • 投稿日

    • 投稿時間

    • 格納サービス&価格

    • URL

    • 本文

やれたこと

  • noteの過去記事含めた記事情報データをnotionに自動格納したい △
    →最新含めた過去分25件だけ。以降データは格納可能に。

  • 記事情報データは以下を取りたい 〇

    • 記事タイトル

    • 投稿日

    • 投稿時間

    • 格納サービス&価格

    • URL

    • 本文
      →本文データ以外は問題なく収集可能だった。note proでないとRSSで本文データを取ることはnote側が提供していない

スプシとNotion画面

RSSデータをスプシに格納している。
25件以上あるのはupdateした結果で、取ってこれるのはMAX25件。
F列はNotion転記の重複排除フラグ。
スケジューラーで週1に設定しているが、気が向いた時に手動実行しそうな雰はある。

マーカー部分は関数、あとは転送

転送されたデータ類。
Notion側のプロパティ順は固定でなくてよい&プロパティを増やしてもGAS実行に影響がない点は便利。

スクリプト: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字

都内 アラフォーDINKS OL。 インターネットで遊んでいたら気が付いたら四十路が見えてきて驚愕し…

All of ぱぴ room

¥900 / 月

ぱぴちゃん家を買う

¥2,980 / 月

ありがとうございます。『あなたの課金は、私の課金』を標語に経済をまわしていきましょう。