見出し画像

【Portfolio】RSSで取得した記事データを表示する

こんにちは、今年もお花見をしていないりょーたです。
まだ夜は寒いですね🥶

今回は、自分のポートフォリオに、RSS経由で取得した

  • note

  • Qiita

  • Zenn

の記事リンクの一覧を表示するようにしたので、そちらについてまとめたいと思います。

↓実際のページはこちら


やりたかったこと

私は普段、先ほどあげた3つのメディアで記事(ブログ)をあげているのですが、折角なのでこの情報をポートフォリオ内で集約して表示したいと考えました。

CMSを用いて、記事を作成するたびに、コンテンツを追加し更新する方法も思いついたのですが、絶対に面倒臭くなってやらなくなる未来が見えたので、データの収集は自動化させたいと思いました。

そこで、各メディアで用意されているRSS配信の機能を利用して取得したデータを保存しておき、それをバッジ処理で定期的に更新する方法を採用しました。


記事一覧を取得するRSSのURL

note

URL:https://note.com/[note ID]/rss

記事に関して取得できる情報

<item>
	<title>【Musubite】トークテーマの改善を行いました</title>
	<media:thumbnail>https://assets.st-note.com/production/uploads/images/100720121/rectangle_large_type_2_af7c1551abe17a838745afa2d503fa19.png?width=800</media:thumbnail>
	<description>
		<![CDATA[<p name="30fb6e07-b521-4963-b1b7-0fd345fcf3fe" id="30fb6e07-b521-4963-b1b7-0fd345fcf3fe">こんにちは、りょーたです。<br>株式会社ニュートラルでデザインエンジニアをしています。</p><p name="564c04d0-7dd7-420f-94b1-499ee2c03c40" id="564c04d0-7dd7-420f-94b1-499ee2c03c40">今回は、弊社のプロダクト<a href="https://musubite-job.com/" target="_blank" rel="nofollow noopener">Musubite</a>のUI改善と、それに伴うUX向上についてのお話第3弾です。</p><br/><a href='https://note.com/ryotanny/n/n66201cd31716'>続きをみる</a>]]>
	</description>
	<note:creatorImage>https://assets.st-note.com/production/uploads/images/92752951/profile_55ed0ed26f85a74ef192eb8d8300cdb7.jpg?fit=bounds&amp;format=jpeg&amp;quality=85&amp;width=330</note:creatorImage>
	<note:creatorName>りょーた | デザインエンジニア</note:creatorName>
	<pubDate>Mon, 20 Mar 2023 14:05:31 +0900</pubDate>
	<link>https://note.com/ryotanny/n/n66201cd31716</link>
	<guid>https://note.com/ryotanny/n/n66201cd31716</guid>
</item>
  • 記事タイトル

  • サムネイル

  • 概要文(記事の冒頭)

  • 投稿者の情報(アイコン、名前)

  • 記事の公開日時

  • 記事のURL


Zenn

URL:https://zenn.dev/[アカウントID]/feed

記事に関して取得できる情報

<item>
	<title>
		<![CDATA[OpenAPIの分割された定義ファイルからコードを生成する]]>
	</title>
	<description>
		<![CDATA[デザインエンジニアのりょーたです。
弊社では、フォロワー採用サービスのMusubiteというプロダクトを開発しています。
フロントエンドをNext.js(SSR)、バックエンドをRuby on Railsで開発しており、データのやり取りはREST APIを用いています。REST APIの仕様は、OpenAPIを導入して管理をしており、フロントエンドでは、そのスキーマを用いてソースコードの生成を行なっています。
今回の記事では、aspida用の型定義を作成できるようになるまでのプロセスをまとめようと思います。
前提として、OpenAPIの定義ファイルは、以下のようにymlファイルを役割ごと...]]>
	</description>
	<link>https://zenn.dev/ryota0222/articles/b811120b7d2701</link>
	<guid isPermaLink="true">https://zenn.dev/ryota0222/articles/b811120b7d2701</guid>
	<pubDate>Thu, 09 Mar 2023 12:21:29 GMT</pubDate>
	<enclosure url="https://res.cloudinary.com/zenn/image/upload/s--D39HOr-Q--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:OpenAPI%25E3%2581%25AE%25E5%2588%2586%25E5%2589%25B2%25E3%2581%2595%25E3%2582%258C%25E3%2581%259F%25E5%25AE%259A%25E7%25BE%25A9%25E3%2583%2595%25E3%2582%25A1%25E3%2582%25A4%25E3%2583%25AB%25E3%2581%258B%25E3%2582%2589%25E3%2582%25B3%25E3%2583%25BC%25E3%2583%2589%25E3%2582%2592%25E7%2594%259F%25E6%2588%2590%25E3%2581%2599%25E3%2582%258B%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:%25E3%2582%258A%25E3%2582%2587%25E3%2583%25BC%25E3%2581%259F%2520%257C%2520%25E3%2583%2587%25E3%2582%25B6%25E3%2582%25A4%25E3%2583%25B3%25E3%2582%25A8%25E3%2583%25B3%25E3%2582%25B8%25E3%2583%258B%25E3%2582%25A2%2Cx_203%2Cy_98/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2ExMWJmOTJlZTcuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_72/og-base.png" length="0" type="image/png"/>
	<dc:creator>りょーた | デザインエンジニア</dc:creator>
</item>
  • 記事タイトル

  • 概要文(記事の冒頭)

  • 記事のURL

  • 記事の公開日時

  • OGP画像

  • 投稿者の情報(名前)


Qiita

URL:https://qiita.com/[アカウントID]/feed

記事に関して取得できる情報

<entry>
	<id>tag:qiita.com,2005:PublicArticle/1491384</id>
	<published>2021-10-01T12:07:11+09:00</published>
	<updated>2021-12-13T15:39:47+09:00</updated>
	<link rel="alternate" type="text/html" href="https://qiita.com/ryotanny/items/a0dad6c47ca07780da75"/>
	<url>https://qiita.com/ryotanny/items/a0dad6c47ca07780da75</url>
	<title>[React Native]アニメーションでちらつきが発生した時の対処法</title>
	<content type="html">はじめに
今回、react-native-modalというライブラリを使ってモーダルを実装した際に、モーダルを閉じるアニメーションで背景にチラつきが発生する(下の動画参照)ことがあったため、その対応…</content>
	<author>
		<name>ryotanny</name>
	</author>
</entry>
  • 記事ID

  • 記事の公開日時

  • 記事の更新日時

  • 記事のURL

  • 記事タイトル

  • 概要文(記事の冒頭)

  • 投稿者の情報(名前)


取得したデータをJSON形式で保存するスクリプトの作成

RSS経由でデータを取得できることがわかったので、JSON形式に変換し、扱いやすい形式で保存します。
以下、変更後のJSONデータの例です。

{
   "zenn":[
      {
         "title":"OpenAPIの分割された定義ファイルからコードを生成する",
         "content":"デザインエンジニアのりょーたです。\n弊社では、フォロワー採用サービスのMusubiteというプロダクトを開発しています。\nフロントエンドをNext.js(SSR)、バックエンドをRuby on Railsで開発しており、データのやり取りはREST APIを用いています。REST APIの仕様は、OpenAPIを導入して管理をしており、フロントエンドでは、そのスキーマを用いてソースコードの生成を行なっています。\n今回の記事では、aspida用の型定義を作成できるようになるまでのプロセスをまとめようと思います。\n前提として、OpenAPIの定義ファイルは、以下のようにymlファイルを役割ごと...",
         "url":"https://zenn.dev/ryota0222/articles/b811120b7d2701",
         "date":"2023-03-09T12:21:29.000Z",
         "thumbnail":"https://res.cloudinary.com/zenn/image/upload/s--D39HOr-Q--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:OpenAPI%25E3%2581%25AE%25E5%2588%2586%25E5%2589%25B2%25E3%2581%2595%25E3%2582%258C%25E3%2581%259F%25E5%25AE%259A%25E7%25BE%25A9%25E3%2583%2595%25E3%2582%25A1%25E3%2582%25A4%25E3%2583%25AB%25E3%2581%258B%25E3%2582%2589%25E3%2582%25B3%25E3%2583%25BC%25E3%2583%2589%25E3%2582%2592%25E7%2594%259F%25E6%2588%2590%25E3%2581%2599%25E3%2582%258B%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:%25E3%2582%258A%25E3%2582%2587%25E3%2583%25BC%25E3%2581%259F%2520%257C%2520%25E3%2583%2587%25E3%2582%25B6%25E3%2582%25A4%25E3%2583%25B3%25E3%2582%25A8%25E3%2583%25B3%25E3%2582%25B8%25E3%2583%258B%25E3%2582%25A2%2Cx_203%2Cy_98/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2ExMWJmOTJlZTcuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_72/og-base.png",
         "favicon":"https://zenn.dev/images/logo-transparent.png",
         "site":"zenn"
      },
      ...
   ],
   "qiita":[
      {
         "title":"ContentfulにAPI経由でメディアをアップロードして公開する",
         "content":"この記事は「つながる勉強会 Advent Calendar 2022」の 3日目の記事です。\nhttps://adventar.org/calendars/7752\n\n1日目の記事はこちら「フルリモー…",
         "url":"https://qiita.com/ryotanny/items/156a64980feb0cb4768b",
         "date":"2022-12-03T01:37:56.000Z",
         "favicon":"https://cdn.qiita.com/assets/favicons/public/production-c620d3e403342b1022967ba5e3db1aaa.ico",
         "site":"qiita"
      },
      ...
   ],
   "note":[
      {
         "title":"【Musubite】トークテーマの改善を行いました",
         "content":"<p name=\"30fb6e07-b521-4963-b1b7-0fd345fcf3fe\" id=\"30fb6e07-b521-4963-b1b7-0fd345fcf3fe\">こんにちは、りょーたです。<br>株式会社ニュートラルでデザインエンジニアをしています。</p><p name=\"564c04d0-7dd7-420f-94b1-499ee2c03c40\" id=\"564c04d0-7dd7-420f-94b1-499ee2c03c40\">今回は、弊社のプロダクト<a href=\"https://musubite-job.com/\" target=\"_blank\" rel=\"nofollow noopener\">Musubite</a>のUI改善と、それに伴うUX向上についてのお話第3弾です。</p><br/><a href='https://note.com/ryotanny/n/n66201cd31716'>続きをみる</a>",
         "url":"https://note.com/ryotanny/n/n66201cd31716",
         "date":"2023-03-20T05:05:31.000Z",
         "thumbnail":"https://assets.st-note.com/production/uploads/images/100720121/rectangle_large_type_2_af7c1551abe17a838745afa2d503fa19.png?width=800",
         "favicon":"https://assets.st-note.com/poc-image/manual/note-common-images/production/svg/production.ico",
         "site":"note"
      },
      ...
   ]
}


以下、TypeScriptで書いたスクリプトのサンプルです。

import { writeFileSync } from 'fs';

import Parser from 'rss-parser';

interface CustomItem {
  'media:thumbnail': string;
  image: string;
}

interface AssetItem {
  title?: string;
  content?: string;
  url?: string;
  date?: string;
  thumbnail?: string;
}

const parser = new Parser<unknown, CustomItem>({
  customFields: {
    item: [['media:thumbnail', 'image']],
  },
});

// マッピング情報
const rssFeed = {
  zenn: {
    label: 'Zenn',
    url: 'https://zenn.dev/ryota0222/feed',
    favicon: 'https://zenn.dev/images/logo-transparent.png',
  },
  qiita: {
    label: 'Qiita',
    url: 'https://qiita.com/ryotanny/feed',
    favicon: 'https://cdn.qiita.com/assets/favicons/public/production-c620d3e403342b1022967ba5e3db1aaa.ico',
  },
  note: {
    label: 'Note',
    url: 'https://note.com/ryotanny/rss',
    favicon: 'https://assets.st-note.com/poc-image/manual/note-common-images/production/svg/production.ico',
  },
};

try {
  console.log('🌟 Fetch RSS');
  const jsonFeed: Record<string, AssetItem[]> = {};
  for (const [site, info] of Object.entries(rssFeed)) {
    // RSSのデータ取得
    const feed = await parser.parseURL(info.url);
    const items = feed.items.map((i) => {
      return {
        title: i.title,
        content: i.content,
        url: i.link,
        date: i.isoDate,
        thumbnail: i.enclosure?.url ?? i.image,
        favicon: info.favicon,
        site,
      };
    });
    // jsonFeedに格納
    jsonFeed[site] = items;
    console.log(`✅ Fetched ${info.label}`);
  }

  // static/rss.jsonに出力
  writeFileSync('./src/data/rss.json', JSON.stringify(jsonFeed));
} catch (err) {
  console.error(err);
}

console.log('✨ Finished!');

rssの変換には「rss-parser」というライブラリを利用しました。

https://www.npmjs.com/package/rss-parser

あとは、GitHub Actionsでクーロンジョブをスケジュールすれば記事情報を定期的に更新することができます。


今後の改善点

QiitaのRSSではOGP画像が取得できませんでした。

上からnote, Qiita, Zenn

どうにか画像の情報を取れないかとみてみると、記事ページのメタタグで以下のURLを参照していました。

https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Fpublic%2Farticle-ogp-background-9f5428127621718a910c8b63951390ad.png?ixlib=rb-4.0.0&w=1200&mark64=aHR…&mark-x=142&mark-y=112&blend64=aHR…&blend-x=142&blend-y=491&blend-mode=normal&s=03…

なるほど、Imgixを用いて画像を動的に生成しているようです。
細かくまだ見れていないのですが、今回のケースだとタイトルさえ変更できればいいので、希望はありそう🙂
できない場合は最悪スクレイピングすれば取得できるので、そのうち対応するかもです


最後に

slideshareにもRSS配信機能があったので、なかなか便利に使えそうですね!
いつになるかわかりませんが、次は、サービスワーカーを用いた通知の機能を実装してみたいと思います!safariも対応したことですし🎉


興味を持ってくれる方がいましたら、気軽にお話ししましょう!

この記事が気に入ったらサポートをしてみませんか?