その他
    ホーム技術発信DoRubyRubyでどう書く?:重複したRSSをまとめる

    Rubyでどう書く?:重複したRSSをまとめる

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    KBMJがZDnetで連載している記事を紹介します。コメントなどありましたらそちらでお願いします。

    Rubyでどう書く?:重複したRSSをまとめる 高瀬裕一(KBMJ)

    「ネットの話題を追いかけるために、はてブのホッテントリやlivedoor クリップの人気ページをRSSで取得しています。でも、重複していることも多いんです。助けてください」(32才 男性・家事手伝い)――よろしい、ならばRubyで書こう。 

    第4回目のテーマは「重複したRSSをまとめる」と題しまして、Rubyで複数のRSSから重複したエントリを除去する問題を出します。

    問題

     最近、はてなブックマークやlivedoor クリップなど、ユーザーが外部のサイトを集めて、その結果を出力するサービスが増えてきました。

     ただ、一部のサイトを見ているだけで話題を追うことができるので便利なのですが、同じURLが複数のRSSにあり「もうこれは見たのに……」という事も多いですよね。

     そこで今回は、複数のRSSを一つのRSSとして出力するプログラムを問題にします。

    仕様

    • RSSのURLは、コマンドラインから引数として1個以上与えられる。
    • 結合した結果のRSSのタイトルおよび説明は、各引数のタイトルをつなげたものとする。
    • 結合した結果のRSSのURLは第一引数のURLとする。
    • フィードのリンク先が同一のものを同じフィードとして扱う。
    • 出力順は第一引数のものを順に出力し、その後第二引数、第三引数のものを順に出力する(同じフィードとして扱うものは除外する)。
    • 重複したものは前の引数の内容を出力する。
    • 出力先は標準出力とする。
    • 出力するRSSのバージョンは2.0

    回答例

     1 require 'rss'
      2
      3 rss_feeds = rss_urls = []
      4 title=""
      5
      6 ARGV.each do | rss_url |
      7   open(rss_url) do | http |
      8     response = http.read
      9     rss_results = RSS::Parser.parse(response, false)
     10    
     11     if rss_results && rss_results.channel && rss_results.channel.title
     12       title += " と " if title.size > 0
     13       title += rss_results.channel.title
     14     end
     15    
     16     rss_results.items.each do | item |
     17       unless rss_urls.include? item.link
     18         rss_urls << item.link
     19         rss_feeds << item
     20       end
     21     end
     22   end
     23 end
     24
     25 rss = RSS::Maker.make("2.0") do | writer |
     26   writer.channel.title = title || ""
     27   writer.channel.link = rss_urls[0] || ""
     28   writer.channel.description = title || ""
     29   rss_feeds.each do | feed |
     30     feed.setup_maker(writer)
     31   end
     32 end
     33
     34 puts rss.to_s

     試しに、livedoor クリップ 人気ページと、はてなブックマーク 人気エントリーを取得してみましょう。

    ruby rss.rb http://clip.livedoor.com/rss/hot http://b.hatena.ne.jp/hotentry?mode=rss

     本記事を執筆時点では、実行結果は以下のようになります。

    <?xml version="1.0" encoding="UTF-8"?>
    <rss version="2.0"
      xmlns:content="http://purl.org/rss/1.0/modules/content/"
      xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
      <channel>
        <title>livedoor クリップ - 人気ページ と はてなブックマーク 最近の人気エントリー</title>
        <link>http://clip.livedoor.com/rss/hot</link>
        <description>livedoor クリップ - 人気ページ と はてなブックマーク 最近の人気エントリー</description>
    08-06-23 22:52 $ ruby "test.rb" "http://clip.livedoor.com/rss/hot" "http://b.hatena.ne.jp/hotentry?mode=rss" | head -20
    <?xml version="1.0" encoding="UTF-8"?>
    <rss version="2.0"
      xmlns:content="http://purl.org/rss/1.0/modules/content/"
      xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
      <channel>
        <title>livedoor クリップ - 人気ページ と はてなブックマーク 最近の人気エントリー</title>
        <link>http://clip.livedoor.com/rss/hot</link>
        <description>livedoor クリップ - 人気ページ と はてなブックマーク 最近の人気エントリー</description>
        <item>
          <title>Engineer25 すべてを楽しむ若きスーパーエンジニア達 第4回 cho45氏- ウェブキャリア</title>
          <link>http://www.web-career.com/contents/engineer25/4.html</link>
          <description></description>
          <dc:subject>interview</dc:subject>
          <dc:subject>ruby</dc:subject>
          <dc:subject>javascript</dc:subject>
          <dc:subject>program</dc:subject>
        </item>
        <item>
       ...中略...
        </item>
      </channel>
    </rss>

    解説

     本サンプルプログラムは、大きく3段階の処理になっています。

     第1段階(6行目から9行目)では、渡された引数を元に各ページにアクセスし、RSSを解析しています。

     第2段階(11行目から20行目)では、rss_feeds内にRSSを分解したフィードの内容を登録しています。

     今回は第一引数の出力内容を優先するので、順番を管理しやすいArrayに登録しました。そして、重複チェック用に、rss_urlsというArrayを作って、登録時にそこにurlを入れ、urlが既に登録されていないかチェックをしています。

     第3段階目(25行目から34行目)では、全RSSの取得を完了した後で、makeでRSSを再生成して出力しています。

    最後に――

     今回のお題は主に自分が必要と感じるものを作ってみました。

     パーサが標準のライブラリにあるので特に手間無く作れましたが、もうちょっと実用度を上げようとすると、サーバが落ちている場合や、引数が間違えている場合などの例外処理を組み込むのも良いと思います。

     こんな風に作ってみたよという方が居られましたら、コメントやbuilderブログなどで教えていただきたいと思います。