メインコンテンツへスキップ
見出し画像
Photo bylight_mimosa502

[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万の価値はないというか、だったら自分でやる…となる(やらない)

     
     
     
    noteではお買い物したものの記録や、好きなものについて、率直にしたためます。インターネットで大っぴらにはできない生活やお金、持ち物のリアルタイムな素直な気持ちと動きを書く場所です。