RailsでRSSリーダーを作ったときに得たノウハウ第2弾、RSSフィードのパースのところを書いてみようと思います。

第1弾としてこんなん書きました。
個別ページのURLからRSSフィードURLを取得するfeedbag - ぴよログ
サイトURLを渡すとHTMLを解析してRSSフィードのURL候補を返してくれるというgemの紹介でした。今回は得られたRSSフィードのURLに情報を取りに行ってパースしてくれるgemを紹介してみようかと思います。
Feedzirra
これを使います。
いくつか候補がありました(全部忘れました)が、最も新しくてその上現在進行形で開発が進んでいる風だったこともあってこちらを使用していました。
初回のフィード取得
GitHubのREADMEにある通りですんなりいけます。折角なのでfeedbagを絡めてみます。
1require "feedbag"
2require "feedzirra"
3
4feed_urls = Feedbag.find "https://blog.piyo.tech/" # このブログ
5p feed_urls.first # => "https://blog.piyo.tech/feed"
6
7feed = Feedzirra::Feed.fetch_and_parse(feed_urls.first)
8
9p feed.title # => "ぴよログ"
10p feed.url # => "https://blog.piyo.tech/"
11p feed.feed_url # => "https://blog.piyo.tech/feed"
12p feed.etag # => "f6ce826cbbd07e222b6cee445fc981f730ad0693"
13p feed.last_modified # => 2014-01-15 11:47:34 UTC
14p feed.entries.count # => 7
こんな風に結構簡単にRSSフィードの解析が終わりました。このブログを対象としてみましたが、7記事分の情報が取れるようですね。それ以前の記事はフィードされていないということになります。
更新の確認
RSSリーダーというものは、RSSの仕組みのせいでリーダー側からRSS配信元を見に行かなくてはならない、いわゆるプル型のサービスです。そのため、更新されているかどうかもわからないのにRSSフィードを見に行かなくてはなりません。そして更新があれば新しい記事の分だけをサービスに保存する、というようなことを行うことになります。
この辺りの設計について話しだすと長くなるので、後日書くかもしれません。
話をFeedzirraに戻します。
先ほどの例でもわかるように、はてなブログのRSSフィードでは7記事分配信されるようです。ここではあるはてなブログを例に解説を進めます。
例えば初回取得時と2回目の取得時(つまり更新時)の間に対象のはてなブログに新たに2記事追加されたとします。
図でいうとこんな感じ。

先ほど使ったメソッド、fetch_and_parseを使うと現在のRSSフィードの内容をパースしてそこに載っている記事を全て取得してくることになるので、そのままデータベース等に保存しようとすると記事3〜記事7はダブってしまうことになります。
普通はそんなの困るのですでに取得済みの記事は除外して新しいものだけを新たに保存したいですよね。自前でやる場合はタイムスタンプ等で判別しなければいけません。
当然Feedzirraにはそのような方法が用意されています。
1require "feedbag"
2require "feedzirra"
3
4# 最初は一緒
5
6feed_urls = Feedbag.find "https://blog.piyo.tech/" # このブログ
7p feed_urls.first # => "https://blog.piyo.tech/feed"
8
9feed = Feedzirra::Feed.fetch_and_parse(feed_urls.first)
10p feed.entries.count # => 7
11
12# この瞬間に2記事分更新されたとする(そんなことあり得ないけど)
13
14updated_feed = Feedzirra::Feed.update(feed)
15updated_feed.updated? # => true
16updated_feed.new_entries.count # => 2
これで更新のときに必要な記事だけ取り出せますね、、と言いたいところですがそうはいきません。
何がだめ?
先ほどの更新の例では、最初にfetch_and_parseしたときに帰ってきたfeedというオブジェクトが生き残っていて、そのオブジェクトに対してupdateメソッドを呼ぶことで更新を確認しています。
普通に考えて初回取得時に存在していたオブジェクトが、それ以降も存在しているわけはありません。初回のRSSフィード取得が終わったしばらくあと(数時間後とか)にスケジュールされたジョブとして2回目のフィード取得が走るのが普通だからです。
つまり、feedに相当するオブジェクトを自分で生成した上でupdateを呼んであげる必要があるわけです。取得済みの記事だとかフィードの最終更新などの情報はデータベースなどに入れてあるはずなので、それらの情報からfeedを再作成します。こんな感じで。
1feed = Feedzirra::Parser::RSS.new
2feed.feed_url = "https://blog.piyo.tech/"
3feed.etag = "f6ce826cbbd07e222b6cee445fc981f730ad0693" # いらないかも
4feed.last_modified = DateTime.parse("2014-01-15 11:47:34 UTC")
5last_entry = Feedzirra::Parser::RSSEntry.new
6last_entry.url = "取得済みの記事の中で最も新しいのURLを入れておく"
7feed.entries = [last_entry]
8
9Feedzirra::Feed.update(feed)
そうするとupdateメソッドが使えるようになります。このような流れで更新された記事だけを取り出すことができるようになりました。
なお、Feedzirra::Parser::RSS以外にもFeedzirra::Parser::Atomなどのパーサーがあるのですが、違うフォーマットでもいい感じにやってくれるから大丈夫!という旨がこちらのコメントで確認できます。
rss - Ruby - Feedzirra and updates - Stack Overflow
ソース読めばわかるんでしょうがまだ読んでいないという。今度読みます。