RailsでRSSリーダーを作ったときに得たノウハウ第2弾、RSSフィードのパースのところを書いてみようと思います。
第1弾としてこんなん書きました。
個別ページのURLからRSSフィードURLを取得するfeedbag - ぴよログ
サイトURLを渡すとHTMLを解析してRSSフィードのURL候補を返してくれるというgemの紹介でした。今回は得られたRSSフィードのURLに情報を取りに行ってパースしてくれるgemを紹介してみようかと思います。
Feedzirra
これを使います。
いくつか候補がありました(全部忘れました)が、最も新しくてその上現在進行形で開発が進んでいる風だったこともあってこちらを使用していました。
初回のフィード取得
GitHubのREADMEにある通りですんなりいけます。折角なのでfeedbagを絡めてみます。
require "feedbag"
require "feedzirra"
feed_urls = Feedbag.find "https://blog.piyo.tech/" # このブログ
p feed_urls.first # => "https://blog.piyo.tech/feed"
feed = Feedzirra::Feed.fetch_and_parse(feed_urls.first)
p feed.title # => "ぴよログ"
p feed.url # => "https://blog.piyo.tech/"
p feed.feed_url # => "https://blog.piyo.tech/feed"
p feed.etag # => "f6ce826cbbd07e222b6cee445fc981f730ad0693"
p feed.last_modified # => 2014-01-15 11:47:34 UTC
p feed.entries.count # => 7
こんな風に結構簡単にRSSフィードの解析が終わりました。このブログを対象としてみましたが、7記事分の情報が取れるようですね。それ以前の記事はフィードされていないということになります。
更新の確認
RSSリーダーというものは、RSSの仕組みのせいでリーダー側からRSS配信元を見に行かなくてはならない、いわゆるプル型のサービスです。そのため、更新されているかどうかもわからないのにRSSフィードを見に行かなくてはなりません。そして更新があれば新しい記事の分だけをサービスに保存する、というようなことを行うことになります。
この辺りの設計について話しだすと長くなるので、後日書くかもしれません。
話をFeedzirraに戻します。
先ほどの例でもわかるように、はてなブログのRSSフィードでは7記事分配信されるようです。ここではあるはてなブログを例に解説を進めます。
例えば初回取得時と2回目の取得時(つまり更新時)の間に対象のはてなブログに新たに2記事追加されたとします。
図でいうとこんな感じ。
先ほど使ったメソッド、fetch_and_parse
を使うと現在のRSSフィードの内容をパースしてそこに載っている記事を全て取得してくることになるので、そのままデータベース等に保存しようとすると記事3〜記事7はダブってしまうことになります。
普通はそんなの困るのですでに取得済みの記事は除外して新しいものだけを新たに保存したいですよね。自前でやる場合はタイムスタンプ等で判別しなければいけません。
当然Feedzirraにはそのような方法が用意されています。
require "feedbag"
require "feedzirra"
# 最初は一緒
feed_urls = Feedbag.find "https://blog.piyo.tech/" # このブログ
p feed_urls.first # => "https://blog.piyo.tech/feed"
feed = Feedzirra::Feed.fetch_and_parse(feed_urls.first)
p feed.entries.count # => 7
# この瞬間に2記事分更新されたとする(そんなことあり得ないけど)
updated_feed = Feedzirra::Feed.update(feed)
updated_feed.updated? # => true
updated_feed.new_entries.count # => 2
これで更新のときに必要な記事だけ取り出せますね、、と言いたいところですがそうはいきません。
何がだめ?
先ほどの更新の例では、最初にfetch_and_parse
したときに帰ってきたfeed
というオブジェクトが生き残っていて、そのオブジェクトに対してupdate
メソッドを呼ぶことで更新を確認しています。
普通に考えて初回取得時に存在していたオブジェクトが、それ以降も存在しているわけはありません。初回のRSSフィード取得が終わったしばらくあと(数時間後とか)にスケジュールされたジョブとして2回目のフィード取得が走るのが普通だからです。
つまり、feed
に相当するオブジェクトを自分で生成した上でupdate
を呼んであげる必要があるわけです。取得済みの記事だとかフィードの最終更新などの情報はデータベースなどに入れてあるはずなので、それらの情報からfeed
を再作成します。こんな感じで。
feed = Feedzirra::Parser::RSS.new
feed.feed_url = "https://blog.piyo.tech/"
feed.etag = "f6ce826cbbd07e222b6cee445fc981f730ad0693" # いらないかも
feed.last_modified = DateTime.parse("2014-01-15 11:47:34 UTC")
last_entry = Feedzirra::Parser::RSSEntry.new
last_entry.url = "取得済みの記事の中で最も新しいのURLを入れておく"
feed.entries = [last_entry]
Feedzirra::Feed.update(feed)
そうするとupdate
メソッドが使えるようになります。このような流れで更新された記事だけを取り出すことができるようになりました。
なお、Feedzirra::Parser::RSS
以外にもFeedzirra::Parser::Atom
などのパーサーがあるのですが、違うフォーマットでもいい感じにやってくれるから大丈夫!という旨がこちらのコメントで確認できます。
rss - Ruby - Feedzirra and updates - Stack Overflow
ソース読めばわかるんでしょうがまだ読んでいないという。今度読みます。