Ruby ではてなブックマーク API を叩く

久しぶりに真面目に Ruby.以前にはてなブックマーク APIRuby ラッパを書いていたのですが,はてなブックマーク API 側もちょこちょこと変更があったようでうまく動かなくなっていたので修正しました.以前に書いたときは,よく分からずあれもこれも書いた結果,ファイル構成やら何やら分かりづらくなっていたので,いくつかの機能に絞って 1 ファイルに纏めました.依存している (gem)ライブラリは jsonnokogiri です.

hatena/bookmark.rb - crown - clown's github

module Crown
module Hatena
module Bookmark
    # --------------------------------------------------------------- #
    #
    #  structures
    #
    #  get() で返される構造体
    #
    # --------------------------------------------------------------- #
    User     = Struct.new(:tags, :date, :comment)
    Related  = Struct.new(:eid, :uri, :title, :count)
    Response = Struct.new(:eid, :uri, :image, :title, :count, :users, :related)
    
    # --------------------------------------------------------------- #
    #  functions
    # --------------------------------------------------------------- #
    def count(uri, session= Net::HTTP.new("api.b.st-hatena.com", 80)) # -> int
    def get(uri, (uri, session = Net::HTTP.new("b.hatena.ne.jp", 80)) # -> Response or nil
    def each(query, session = Net::HTTP.new("b.hatena.ne.jp", 80)) # { |block| }
end # Bookmark
end # Hatena
end # Crown

count(), get() はそれぞれ,指定した URL のはてなブックマーク件数,はてなブックマーク情報を取得します.each() は少し変わり種で,例えば,http://b.hatena.ne.jp/hotentry などに列挙されてある(ブックマークされている)記事の URL に対応するブックマーク情報を順に取得し,その情報をブロックに渡します.この際,例えば,http://b.hatena.ne.jp/entrylist?url=d.hatena.ne.jp/tt_clown/ のような URL だと html の head に next 属性で次のページの URL が示されてあることがありますが,each() はその next 属性を利用してできるだけ多くのブックマーク情報を取得しようと試みます.

each() は,2010 年人気エントリー2009 年人気エントリー2008 年人気エントリー と毎年,その年のブクマされた記事 TOP10 を挙げていますが,このデータを取得する際に使用しています.

#!/usr/bin/ruby -Ku
# annual.rb
require 'crown/hatena/bookmark'
PREFIX = "entrylist?sort=count&url="

# --------------------------------------------------------------------------- #
#
#  人気のエントリーから引数に指定した年の記事の URL を抽出する.
#
#  Parameters
#    ARGV[0]: 対象とするサイトの URL
#    ARGV[1]: 対象とする年
#    ARGV[2]: 取得件数(オプション)
#
# --------------------------------------------------------------------------- #
if (ARGV.length < 2)
    puts("usage annual.rb URL year [num_of_url]")
    return
end

year = ARGV[1].to_i
limit = (ARGV.length > 2) ? ARGV[2].to_i : 10
i = 0
Crown::Hatena::Bookmark.each(PREFIX + ARGV[0]) { |entry|
    # その記事がいつのものかは,最初にブックマークした人のブックマーク日時で判断する.
    v = entry.users.to_a.sort { |x, y|
        x[1].date <=> y[1].date
    }
    
    if (v != nil && !v.empty? && v[0][1].date.year == year)
        printf("+ %s (%d users)\n", entry.uri, entry.count)
        i += 1
        break if (i >= limit)
    end
}
実行例
$ ruby annual.rb d.hatena.ne.jp/tt_clown/ 2010
+ http://d.hatena.ne.jp/tt_clown/20100202/1265096776 (1123 users)
+ http://d.hatena.ne.jp/tt_clown/20101213/1292232085 (111 users)
+ http://d.hatena.ne.jp/tt_clown/20100205/1265350752 (90 users)
+ http://d.hatena.ne.jp/tt_clown/20100318/1268894932 (83 users)
+ http://d.hatena.ne.jp/tt_clown/20100702/1278048637 (67 users)
+ http://d.hatena.ne.jp/tt_clown/20100904/1283587136 (64 users)
+ http://d.hatena.ne.jp/tt_clown/20100806/1281088936 (57 users)
+ http://d.hatena.ne.jp/tt_clown/20100216/1266340239 (51 users)
+ http://d.hatena.ne.jp/tt_clown/20100116/1263622099 (51 users)
+ http://d.hatena.ne.jp/tt_clown/20100818/1282110140 (49 users)

その他

昔作ったコードが動かなくなっていた原因をここに書いておきます.

html のエスケープの仕方が変わっていた

以前は,はてなブックマーク API に URL を渡す際にエスケープしなければならない文字がちょっと特殊で自力で処理しないといけない部分があったのですが,いつの間にか CGI.escape() 相当のエスケープになっていました.逆に,以前のエスケープ処理だとエラーになってしまうようです.

Ruby の XMLRPC だとエラーになる

はてなブックマーク件数取得 API には XMLRPC 形式のものもあるのですが,はてなブックマーク件数取得APIのXML-RPCをRubyで使う - maru source に書かれたるように Ruby の XMLRPC を使用するとエラーが発生しました.Ruby の XMLRPC は text/xml しか許していないのですが,はてなブックマーク API は application/xml で結果を返してくるせいのようです.

これは今のところどうしようもない(?)ので,はてなブックマーク件数の取得は単純に GET コマンドでブックマーク件数をテキストで取得する方法を使用し,「特定のサイト」の合計ブックマーク数の取得,ASIN からブックマーク数を取得,の機能は実装を見送っています.

Ruby はまだまだどう書くのが(Ruby として)良いのかよく分かってないので変な書き方の部分もあるかもしれませんが,まぁボチボチ直していこうかと思います.