はてなブックマーク件数取得APIのXML-RPCをRubyで使う

はてなブックマーク件数取得API

はてなでは、はてなブックマークされている数を取得するためのAPIが公開されています。
はてなブックマーク件数取得APIとは - はてなキーワード
よく利用されるであろうAPIは以下の二つ

  • 特定のページの被ブックマーク数を取得する(GETリクエスト、XML-RPC)
  • 特定のサイトの被ブックマーク数を取得する(XML-RPC)

「特定のページ」のほうはGETリクエストでのJSONPに対応してるので、scriptタグとjavascriptから簡単に使えます。が、「特定のサイト」のほうはXML-RCPだけ。これではjavascriptからは使えません。(javascriptのXML-RPCライブラリはあるのですが、XMLHttpRequestを使うので同じドメインのみ)
うーん、困った。それじゃあ自分のサーバ上にGETリクエスト(JSONP)で「特定のサイト」を取得するAPIを作ろう。

XML-RPCって何?

Google先生に聞けば色々詳しく教えてもらえる。僕がGoogle先生から教えてもらったことを要約するとこんな感じ。

  • RPCってのはリモート・プロシージャ・コールの略。
  • ほかのコンピュータのAPIを呼び出す仕組み。
  • その呼び出し方や応答のやりとりをHTTPとXMLを使って行う

詳しくは以下参照。
XML-RPC - Wikipedia
404 - エラー: 404

RubyでXML-RPCを使うには?

RubyでXML-RPCを使うのはすごく簡単。標準添付されているxmlrpcライブラリを使用すればおk。

require "xmlrpc/client"

client = XMLRPC::Client.new2("http://example.com/api/sample.rb")
result = client.call("sample.sumAndDifference", 5, 3)
p result

Content-Typeがtext/xml以外はエラー

じゃあこれで被ブックマーク数のAPIを使えると思ってコード書いて実行するとエラー

require "xmlrpc/client"

url = "http://www.ruby-lang.org/ja/"

client = XMLRPC::Client.new2("http://b.hatena.ne.jp/xmlrpc")
result = client.call("bookmark.getTotalCount" , url)
puts "getTotalCount:#{result}"
/usr/local/lib/ruby/1.8/xmlrpc/client.rb:557:in `do_rpc': Wrong content-type (received 'application/xml' but expected 'text/xml') (RuntimeError)
        from /usr/local/lib/ruby/1.8/xmlrpc/client.rb:420:in `call2'
        from /usr/local/lib/ruby/1.8/xmlrpc/client.rb:410:in `call'
        from ./hoge.rb:8

どうやらRubyのxmlrpc/clientでは受け取ったデータのcontent-typeはtext/xmlのみしか許さない様子。うーん、また困った。
仕方ない、ソース読んで何か解決策探すか。
(Ruby1.9では起きないのかなー?)

かなり場当たり的だけど一応解決

ソース読むとxmlrpc/client.rbでエラー(例外箇所)を発見。

     ct = parse_content_type(resp["Content-Type"]).first
      if ct != "text/xml"
        if ct == "text/html"
          raise "Wrong content-type (received '#{ct}' but expected 'text/xml'): \n#{data}"
        else
          raise "Wrong content-type (received '#{ct}' but expected 'text/xml')"
        end
      end

これはparse_content_typeをちょっといじれば何とかなるかも。parse_content_typeはxmlrpc/utils.rbにあります。

module XMLRPC
...
  module ParseContentType
    def parse_content_type(str)
      a, *b = str.split(";")
      return a.strip.downcase, *b
    end
  end
end

parse_content_typeの戻り値を無理やり変更して以下のように対応。(ライブラリを直接修正するのはちょっと心配なのと、レンタルサーバ上だからそもそもライブラリを編集できません)

require "xmlrpc/client"

module XMLRPC::ParseContentType
  def parse_content_type(str)
    a, *b = str.split(";")
    a = "text/xml" if a == "application/xml"
    return a.strip.downcase, *b
  end
end

url = "http://www.ruby-lang.org/ja/"

client = XMLRPC::Client.new2("http://b.hatena.ne.jp/xmlrpc")
result = client.call('bookmark.getTotalCount', url)
puts "getTotalCount:#{result}"

というわけでかなり場当たり的な方法で解決。良い子は真似しちゃダメな気がする><