Madogiwa Blog

主に技術系の学習メモに使っていきます。

ピュアRubyでAtomも対応したRSS Parserを作ってみたMEMO

みなさん、こんにちは(・∀・) rubyの標準RSSライブラリが思ったよりも高機能でびっくりしたので、gemを使わずにAtomRssのParserを作ってみたので、そのへんのやり方をメモしておきますm( )m

Ruby標準のRSSライブラリ

標準ライブラリを使用する場合は、下記のような形でhttpリクエストを送信したParseされたRSSフィードを取得することができます👀

require 'rss'
rss_source = Net::HTTP.get(URI.parse(endpoint))
rss = RSS::Parser.parse(rss_source)
=> #<RSS::Rss:0x00007fe3dfcc0bd0...

RSS::Parser.parseの返り値は、引数で渡されたRSSの形式によって 、Rssの場合はRSS::RssAtomの場合は、RSS::Atom::Feedのオブジェクトが返却されます。(デフォルトでAtomまで対応してる🙌)

厳密には、下記とのこと

  • RSS 1.0をパースした場合は RSS::RDF オブジェクト
  • RSS 0.9x/2.0をパースした場合は RSS::Rss オブジェクト
  • Atom をパースした場合は RSS::Atom::Feed オブジェクト

下記に詳細が乗っています👀
https://docs.ruby-lang.org/ja/latest/library/rss.html

共通のプロパティを持つオブジェクトを返却するRSSParserを作ってみる

標準ライブラリでAtomでもRssでも共通のオブジェクトを返却するRSSParserを作ってみようと思います👩‍🔧

イメージは下記のような感じ👀

# AtomでもRssでもParsedItemのオブジェクトが返却される
RssCliant.new(endpoint).parsed_items
=> [#<ParsedItem:0x00007fe3e2e3ab48
  @description="みなさん、...",
  @eye_catching_image="https://cdn.blog.st-hatena.com/images/theme/og-image-1500.png",
  @link="https://madogiwa0124.hatenablog.com/entry/2019/03/09/194825",
  @published_at=2019-03-09 19:48:25 +0900,
  @title="vue-cliで作ったアプリをGithub Pagesでサクッとリリースする">,
 #<ParsedItem:0x00007fe3e1c0ce08
  @description=
   "自分が作っている...",
  @eye_catching_image="https://cdn-ak.f.st-hatena.com/images/fotolife/m/madogiwa0124/20190303/20190303213625.gif",
  @link="https://madogiwa0124.hatenablog.com/entry/2019/03/03/214203",
  @published_at=2019-03-03 21:42:03 +0900,
  @title="railsとVueを使って無限スクロール機能を実装するMEMO🌀">,

まずはCliant部分を作ってみる

まずは下記のようなRssCliantをというClassを作ってみました👀 endpointを受け取って標準ライブラリを使ってParse、その後AtomRssかによって使用するParserを切り替え共通のオブジェクトを返却します。

class RssClient
  def initialize(endpoint)
    @endpoint = endpoint
    @rss_source = Net::HTTP.get(URI.parse(endpoint))
  end

  attr_reader :endpoint, :rss_source

  # 不正な形式だった場合に、バリデーションなしでParseする
  def parsed_rss!
    RSS::Parser.parse(rss_source)
  rescue RSS::InvalidRSSError
    RSS::Parser.parse(rss_source, false)
  end

  def parsed_items
    parsed_xml = parsed_rss!
    # オブジェクトのClass名でAtom or Rss用のParserを使って共通のオブジェクトにParse
    case parsed_xml.class.name
    when 'RSS::Atom::Feed' then Parser::Atom.call(parsed_xml)
    when 'RSS::Rss' then Parser::Rss.call(parsed_xml)
    else []
    end
  end
end

Atom or RssのParser部分

Rss.parseでParseされたXMLを引数をもとに、build_parsed_itemPasedItemという共通のオブジェクトを生成し、それらのリストを返却するようにしています🙌 他の形式に対応する場合はParser::Hogeが増えていくようなイメージですね👀
※またAtomの場合は、記事のアイキャッチ画像の取得方法が、ちょっと不明だったので一旦nilを設定するようにしてます💦

Rss

class Parser::Rss
  def self.call(parsed_xml)
    new.call(parsed_xml)
  end

  def call(parsed_xml)
    @parsed_xml = parsed_xml
    items
  end

  attr_reader :parsed_xml

  def items
    @items = parsed_xml.items.map { |item| build_parsed_item(item) }
  end

  def build_parsed_item(item)
    Parser::ParsedItem.new(
      title: item.title,
      description: item.description,
      published_at: item.pubDate,
      link: item.link,
      eye_catching_image: item.enclosure&.url
    )
  end
end

Atom

class Parser::Atom
  def self.call(parsed_xml)
    new.call(parsed_xml)
  end

  def call(parsed_xml)
    @parsed_xml = parsed_xml
    items
  end

  attr_reader :parsed_xml

  def items
    @items = parsed_xml.entries.map { |entry| build_parsed_item(entry) }
  end

  def build_parsed_item(item)
    Parser::ParsedItem.new(
      title: item.title.content,
      description: item.content.content,
      published_at: item.published.content,
      link: item.link.href,
      eye_catching_image: nil # AtomにアイキャッチのURLなさそうなので一旦NULLを設定
    )
  end
end

完成形

これでイメージどおりの完成形ができました🙌

# AtomでもRssでもParsedItemのオブジェクトが返却される
RssCliant.new(endpoint).parsed_items
=> [#<ParsedItem:0x00007fe3e2e3ab48
  @description="みなさん、...",
  @eye_catching_image="https://cdn.blog.st-hatena.com/images/theme/og-image-1500.png",
  @link="https://madogiwa0124.hatenablog.com/entry/2019/03/09/194825",
  @published_at=2019-03-09 19:48:25 +0900,
  @title="vue-cliで作ったアプリをGithub Pagesでサクッとリリースする">,
 #<ParsedItem:0x00007fe3e1c0ce08
  @description=
   "自分が作っている...",
  @eye_catching_image="https://cdn-ak.f.st-hatena.com/images/fotolife/m/madogiwa0124/20190303/20190303213625.gif",
  @link="https://madogiwa0124.hatenablog.com/entry/2019/03/03/214203",
  @published_at=2019-03-03 21:42:03 +0900,
  @title="railsとVueを使って無限スクロール機能を実装するMEMO🌀">,

おわりに

今回はRubyの標準ライブラリだけを使ってRssParserを実装してみました。RssParserはFeedjira等のGemが有名ですが、そんなに凝ったことしないのであればRubyの標準ライブラリが使いやすく高機能なので充分なのでは?という気持ちになりました🙌

他にも標準ライブラリには便利そうな機能がありそうだったので、ちょっと見てみると良さそうですね👀