わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

楽天APIで書籍検索

ISBNだけが知っている状態で,そこから書籍情報を機械的に取得したいのですが,ほんのちょびっとだけ調べていると,楽天APIの存在を知りました.

楽天には,ギフト購入や,旅行時の楽天トラベルで,よくお世話になっていて,会員登録をしています.デベロッパーIDの取得も簡単でした.APIに合うようURLを(Meadowで)書いて,ブラウザでアクセスし,うまく取得できるのを確認しました.
次はRubyで呼び出せるようにしましょう.RAAには…なさそう.でもスクリプトがありました.

ではこれを書籍検索に特化させましょう.

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

# rakuten-book-search.rb

# http://www.demenaoto.net/2009/04/23/ruby-on-railsror%E3%81%A7%E6%A5%BD%E5%A4%A9%E3%82%A6%E3%82%A7%E3%83%96%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B/
# http://webservice.rakuten.co.jp/api/booksbooksearch/

require "rexml/document"
require "open-uri"

class RakutenBookSearch
  attr_reader :search_result

  def initialize(developer_id = nil, affiliate_id = nil)
    if developer_id
      @developer_id_query = "developerId=#{developer_id}"
    else
      return nil, "Developer IDをセットして下さい"
    end

    @affiliate_id_query = "&affiliateId=#{affiliate_id}" if affiliate_id
    @request_url = "http://api.rakuten.co.jp/rws/2.0/rest"
  end

  def book_search(options = nil)
    params_must = %w(title author publisherName size isbn booksGenreId)
    params_option = %w(hits page availability outOfStockFlag carrier genreInformationFlag)
    
    # must options
    if !(Hash === options) ||
        (params_must.map {|item| item.to_sym} & options.keys).empty?
      return nil, "#{params_must.join(', ')}のいずれかをセットして下さい"
    end

    # options
    options_query = ""
    (params_must + params_option).each do |param|
      if options[param.to_sym]
        options_query += "&#{param}=#{options[param.to_sym]}"
      end
    end

    # sorting type
    if options[:sort]
      if %w(standard sales +releaseDate -releaseDate +itemPrice -itemPrice +reviewCount -reviewCount).include?(options[:sort])
        options_query += "&sort=#{options[:sort]}"
      else
        return nil, "ソート方式が不正"
      end
    end

    options_query_encoded = URI.encode(options_query)

    # URI.encodeでは'=+'が変換されないので、手動で置換する
    options_query_encoded = options_query_encoded.gsub(/=\+/,'=%2B')

    @search_request = "#{@request_url}?#{@developer_id_query}#{@affiliate_id_query}#{options_query_encoded}&operation=BooksBookSearch&version=2009-04-15".gsub("\n","").gsub(" ","")

    $stderr.puts @search_request if $DEBUG

    xml_result = open(@search_request)
    @search_result, return_text = xml_to_array(xml_result)
    return @search_result, return_text
  end
  alias :item_search :book_search

  def xml_to_array(xml_result)
    doc = REXML::Document.new xml_result
    doc.elements.each("Response/header:Header/Status") do |status|
      unless status.text == "Success"
        return_text = status.text
        unless doc.elements["Response/header:Header/StatusMsg"]
          return_text += " " + doc.elements["Response/header:Header/StatusMsg"].text
        end
        return nil, return_text
      end
    end

    result = Hash.new
    doc.elements.each("Response/Body/*") do |ele|
      %w(count page first last hits carrier pageCount).each do |param|
        result[param.to_sym] = ele.elements[param].text.to_i
      end

      result[:item] = Array.new
      ele.elements.each("Items/Item") do |item|
        result_item = Hash.new
        %w(title titleKana subTitle subTitleKana seriesName seriesNameKana contents contentsKana author authorKana publisherName size isbn itemCaption salesDate itemPrice discountRate itemUrl affiliateUrl smallImageUrl mediumImageUrl largeImageUrl availability postageFlag limitedFlag reviewCount reviewAverage booksGenreId).each do |elname|
          el = item.elements[elname]
          result_item[elname.to_sym] = el.text if el
        end
        result[:item] << result_item
      end
    end
    return result, ""
  end
end

if __FILE__ == $0
  search = RakutenBookSearch.new("ここに自分のDeveloper IDを書く")
  options = {:isbn => "4881665413"}
  result, error_message = search.item_search(options)
  if error_message.empty?
    p result
  else
    puts error_message
  end
end

大きな違いは,書籍検索APIに合わせた入出力のパラメータ変更です.それと,元のスクリプトは,パラメータごとのURI追加や値の取得を一つ一つ書いていましたが,%w(...)で文字列のリストを作ってeachで順に処理し,文字列からシンボルを取得するにはString#to_symを利用しました.必須パラメータが一つもないことを,「(params_must.map {|item| item.to_sym} & options.keys).empty?」と,Array#&を使って求めました.
@search_requestへの最初の代入について,「….gsub!(...).gsub!(...)」というのは,gsubの挙動を考えるとおかしく(実際,手元のエラーが出ました),「….gsub(...).gsub(...)」に置き換えました.
結果は以下のとおり.(翌朝,手作業で改行を入れました.)

$ ruby rakuten-book-search.rb
{:count=>1, :page=>1, :first=>1, :last=>1, :hits=>1, :carrier=>0, 
:pageCount=>1, :item=>[{:title=>"実践Ruby on Rails Webプログラミング入門", 
:titleKana=>"ジッセン ルビー オン レイルズ ウェブ プログラミング ニュウモン", 
:subTitle=>"無駄なく迅速な開発環境", 
:subTitleKana=>"ムダ ナク ジンソクナ カイハツ カンキョウ", 
:seriesName=>nil, :seriesNameKana=>nil, :contents=>nil, :contentsKana=>nil, 
:author=>"伊尾木将之", :authorKana=>"イオキ,マサユキ", 
:publisherName=>"ソーテック社", :size=>"単行本", :isbn=>"9784881665411", 
:itemCaption=>nil, :salesDate=>"2006-09-01", :itemPrice=>"2730", 
:discountRate=>"0", :itemUrl=>"http://item.rakuten.co.jp/book/4137945/", 
:affiliateUrl=>nil, 
:smallImageUrl=>"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/8816/88166541.jpg?_ex=64x64", 
:mediumImageUrl=>"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/8816/88166541.jpg?_ex=120x120", 
:largeImageUrl=>"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/8816/88166541.jpg?_ex=200x200", 
:availability=>"1", :postageFlag=>"1", :limitedFlag=>"0", :reviewCount=>"0", 
:reviewAverage=>"0.0"}]}

充実の内容です.
あっと,ISBN-10でリクエストを送っても,レスポンスのisbnの値は,13桁(ISBN-13)になっている点は,気をつけないといけませんね….