Ruby標準のRSSライブラリでRSS1.0、RSS2.0、AtomのフィードをDBに保存する

railsruby標準のrssgemを使用して、RSSフィードの取得・登録処理を実装した。

環境

やったこと

gemの追加

rssはbundled gemなので、railsで使う場合はGemfileに以下を追加してbundle installする。

# Gemfile

gem 'rss'

RSSのパース、DBへの登録

rssgemを使ってRSSをパースする場合はRSS::Parser.parseメソッドを使用する。 .itemsとすることで、オブジェクトの配列が返ってくる

# 戻り値としてオブジェクトの配列が返ってくる
RSS::Parser.parse('https://example.com/rss').items

パース後のオブジェクトはフィードの形式毎にクラスが異なるだけでなく、タイトルやリンクなどを取得する際に指定するプロパティも異なるため、フィードの形式毎に別の登録処理を実行する。
※フィードの形式毎にクラスを定義しても良い

# app/models/feed.rb

class Feed < ApplicationRecord
  def self.fetch_and_save(feed_url)
    # RSSをパース
    rss_items = RSS::Parser.parse(feed_url).items

    rss_items.each do |item|
      # プロパティの種類が異なるため、フィードの形式毎に登録処理を分ける
      case item.class.name
      # RSS1.0の登録処理
      when 'RSS::RDF::Item' then Feed.save_rdf_feed(item)
      # RSS2.0の登録処理
      when 'RSS::Rss::Channel::Item' then Feed.save_rss_feed(item)
      # Atomの登録処理
      when 'RSS::Atom::Feed::Entry' then Feed.save_atom_feed(item)
      end
    end
  end
  .
  .
  .

RSS1.0の登録処理

RSS1.0、およびrssgemで対応しているRSSのモジュール(2023年9月2日現在)では、記事ごとに画像を設定できないようなので、RSS1.0では画像のURLにnilを設定する。

# app/models/feed.rb

def self.save_rdf_feed(rdf_item)
  Feed.create(
    title: rdf_item.title,
    url: rdf_item.link,
    summary: rdf_item.description,
    image_url: nil,
    published_at: rdf_item.dc_date
  )
end

RSS2.0の登録処理

enclosureに関してはオプションで、当該xmlに存在しない場合がある。そのためrss_item.enclosure&.urlとして、存在しないときは画像のURLにnilを設定して登録する。

# app/models/feed.rb

def self.save_rss_feed(user, rss_item)
  Feed.create(
    title: rss_item.title,
    url: rss_item.link,
    summary: rss_item.description,
    image_url: rss_item.enclosure&.url,
    published_at: rss_item.pubDate
  )
end

Atomの登録処理

AtomではRSS2.0のenclosureのように画像の場合も明確にタグの種類が異なるわけではなく、複数存在するlinkの一つとして画像が設定されている。そのため、linktypeプロパティにimageが設定されているものを探して画像のURLを取得する。こちらもRSS2.0と同様、存在しない場合があるため&.hrefとしている。

# app/models/feed.rb

def self.save_atom_feed(user, atom_item)
  Feed.create(
    title: atom_item.title.content,
    url: atom_item.link.href,
    summary: atom_item.content.content,
    image_url: atom_item.links.find { |link| !link.type.nil? && link.type.include?('image') }&.href,
    published_at: atom_item.published.content
  )
end