この投稿は、
DMM WEBCAMP mentor Advent Calendar 2022
の投稿10日目のエントリーです。
9日目は @takakou さんで
初学者のためのUnity+clusterによるメタバース空間の構築。
です。メタバース・・私もちょっとやってみた事があるので、楽しく読ませていただきました。
はじめに
最近DMMでメンターをやらせていただいております。 @tomoaki-kimura です。
今回は、 irb
を使って Api
を理解するという内容をやってみたいと思います。
環境と前提
- ruby 3.1.x
1.楽天Api
準備
では、早速準備をしましょう。
まず、 RakutenDevelopers のサイトを見てみます。
楽天Apiは、使用の申請が簡単で、ドキュメントも親切なので、最初に扱うものとしてオススメです。
また、テストフォーム等も用意されており、誰でも気軽に実験が行えます。
今回は、下図の赤で囲ってある、
「楽天ブックス書籍検索API」を使ってみたいと思います。
リンク を押してみましょう。
このような画面が出ればOKです。
ざっくり見て、「???」な人はそのまま本文を進めて手を動かしていきましょう。
Apiに必要なもの
Apiをリクエストする為には、 リクエストURL
が必要になりますが、分解すると、
- a. ベースとなるURL
- b. パラメーター
- c. Apiキー
と、この3つが必要になります。
リクエストURLとは
リクエストURLとは、
この赤枠の中のurlです。が、このURLですが、見てみると
https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?[parameter]=[value]…
このように書かれており、ちょっと意味が分からない方もいらっしゃるかと思います。
ちょうどその下に実例が書かれており、
https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?
applicationId=[アプリID]
&title=%E5%A4%AA%E9%99%BD
&booksGenreId=001004008
&sort=%2BitemPrice
このようなURLを作ってアクセスすると、Apiから結果を得られるという事になります。
このURLの方も、なかなか理解が難しいですね。
一旦このURLを、先述の a, b, c の項目で分解してみましょう。すると、
- a. ベースとなるURL
https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404
ベースとなるURLは、
- b. パラメーター
?applicationId=[アプリID]&title=%E5%A4%AA%E9%99%BD&booksGenreId=001004008&sort=%2BitemPrice
- c. Apiキー
[アプリID]
このように分けられます。(cに関しては個別に取得したID差し替えますが、後ほど扱いたいと思います)
ざっくり簡単な構造として、
[ベースURL]?[項目1]=[値1]&[項目2]=[値2]&[項目3]=[値3]
このように、 ?
の後ろがパラメーターで、 &
で区切る事で何個でも指定する項目(絞り込む項目)を増やす事が出来るわけです。
では、各部をもう少し見てみましょう。
a. ベースとなるURL
https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404
このベースとなるURLは、リクエストURLからパラメーターを除いたもの、 ?
の文字より前の部分となっています。
普通にブラウザ等でアクセス出来る訳ではなく、パラメーターを組み合わせる事により、特定の条件のデーターを探す事が可能です。
b. パラメーター
ここでは、 applicationId=[アプリID]
以外のパラメーターを見ていきます。
パラメーター単体を見ると
title=%E5%A4%AA%E9%99%BD
このようになっていますが、 %
の文字が多用されていて文字と認識する事が出来ません。
わかりやすいようにするためには、 UTF-8
で URL
エンコードされている部分をデコードしてみると良いでしょう。
デコードは、 irb
または コンソール
で試す事が出来ます。
3.1.x で試します。
$ ruby -v
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21]
$ irb
irb(main):001:0>
irb
を起動し、下記のようにパラメーターを1つずつ取り出して試してみましょう。
irb(main):001:0> require 'uri'
=> true
irb(main):002:0> URI.decode_www_form_component("title=%E5%A4%AA%E9%99%BD")
=> "title=太陽"
irb(main):003:0> URI.decode_www_form_component("booksGenreId=001004008")
=> "booksGenreId=001004008"
irb(main):004:0> URI.decode_www_form_component("sort=%2BitemPrice")
=> "sort=+itemPrice"
(※ rails c
を使う場合は、 require 'uri'
はすでに読み込まれており、呼び出す必要はありません。)
"title=太陽"
"booksGenreId=001004008"
"sort=+itemPrice"
このようにデコードされました。数字や、半角英数はそのままですが、日本語部分や、 +
等のweb上で不都合のある文字がエンコードされていたようです。
本来ならば、リクエストURL丸ごとエンコード&デコード可能なのですが、ドキュメントに記載されている、
リクエストURL全体をエンコードするのではなく、[value]部分を個別にエンコードしてください。
という注釈に従っています。
c. Apiキー
applicationId=[アプリID]
このパラメーター部分の [アプリID]
の部分です。(実際の値を入れる際は[]は取ります)
こちらは、使いたいアプリ毎に申請する必要があります。
アプリID発行から、申請します。リンクを押すと、
ログインを求められますので、ログインまたはサインアップして下さい。
ログインすると、
下の画面のようになりますので、
-
アプリ名:
api_test
(適当でOK) -
アプリURL
http://localhost:3000
(適当でOK)
を指定します。
画像のマネをしていただいても大丈夫ですが、適当にネーミングしても大丈夫です。
ただし、 アプリURL
は https://
または http://
が必須のようです。
作成出来たら、アプリ一覧画面にリダイレクトしますので、
一番上の アプリID/デベロッパーID
の数字をメモしておきましょう。
これで、パラメーターが全て整いますので、Apiのリクエストが可能となります。
2.Apiのしくみ
では、テストフォームで試してみます。
テストフォームで試す
一旦 Api詳細画面 に戻って、
テストフォーム のリンクを押しましょう。
すると、
このようなフォームが出てきます。
このフォームはすでに必要なデーターが入力されており、先程の
"title=太陽"
に当たるパラメーターも存在しています。
ちなみに、 アプリIDの部分は、先程のアプリIDの申請を行っていて、且つログインした状態では、申請済みのIDが自動入力されますが、それ以外の場合だと、「仮ID」のようなものが発行され、このページ内だけで有効なIDが自動入力されます。
その場合、このようなIDとなり、数字ではありませんが、ID自体は存在するため、このページの作業においては実はログインも必要ありません。
まずは、このままリクエストを試してみましょう。
フォーム下側に移動し、 GET
のボタンを押すと、
このように、レスポンスが赤い文字で返ってきます。
実際には JSON
というデーター形式でレスポンスされますので、各プログラミング言語側で、扱いやすい形式に変換します。
Ruby
では Arrey
と Hash
を組み合わせた形でデーター変換されます。
ブラウザで試す
次にブラウザで、Apiのレスポンスを確認してみましょう。
このURL
https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format=json&title=%E5%A4%AA%E9%99%BD&booksGenreId=001004008&applicationId=xxxxxxxxxxxxxxx
をコピーしてブラウザに直接貼りましょう。(xxxの部分にそれぞれのアプリIDが入ります。)
すると・・・
このように、画面いっぱいに情報が表示されます。
ただ今回は、仮IDでは駄目で、ログインした時のアプリIDでなくてはエラーになります。IDが合わない時は、
このような結果になります。
このように、 GET
でリクエストするApiの場合は、ブラウザで可視化出来ますが、もし投稿機能やSNS認証などで、 POST
のやりとりが必要となる場合は、ブラウザで可視化が出来ないので、 PostMan などを使って開発します。(今回は GET
のみ扱いますのでこれはやりません)
階層構造
Apiのレスポンスには、Hashに変換可能な階層構造が用いられていて、
{
"GenreInformation": [],
"Items": [
{
"Item": {
"affiliateUrl": "",
"author": "多和田 葉子",
//(省略)
}
},
{
"Item": {
"affiliateUrl": "",
"author": "知念 実希人",
//(省略)
}
},
]
//(省略)
}
このようになっています。
ただ、現状データーが多く、どのようなデーターがあるか分かりにくいので、これから irb
を使って検証してみましょう。
3. irbで、データーを触ってみる
JSONからの変換
まずは、 irb
の起動です。
irb(main):001:0> require 'open-uri'
=> true
URLからレスポンスを取得するために、ライブラリ open-uri
を require
で読み込みます。
もし戻り値が false
になってもすでにライブラリは読まれているという事になるので、そのまま進めて大丈夫です。
ちなみに、 open-uri
はネット上にあるファイルにアクセス、取得するためのライブラリで、ファイルの取得等も行えます。
他にApiで使えるライブラリとしては、 net/http
もあり、こちらはより多機能になっています。
どちらも Ruby
標準の機能ですが、初期段階で読み込まれていないので、 require
が必要となります。
(Railsでは読み込まれています。)
irb(main):002:0> url = "https://app.rakuten.co.jp/services/api/BooksBook/Search/
20170404?format=json&title=%E5%A4%AA%E9%99%BD&booksGenreId=001004008&application
Id=xxxxxxxxxxxxxxxxx"
=> "https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format...
まずは、先程のURLを変数 url
に代入しておきます。(xxxxは各アプリIDに置き換えて下さい)
次に、URL.open
で引数の url
から情報を取得します。
irb(main):003:0> URI.open(url)
=> #<File:/var/folders/k7/wp537bh155j3db7cjqhw63300000gn/T/open-uri20221210-648-qdvzvv>
File
として取得しているようです。これを read
メソッドで中身を可読化します。
irb(main):004:0> URI.open(url).read
=> "{\"GenreInformation\":[],\"Items\":[{\"Item\":{\"affiliateUrl\":\"\",\"author\":\"多和田 葉子\",\"authorKana\":\"タワダ ヨウコ\",\"availability\":\"1\",\"booksGenreId\":\"001004008004\",\"chirayomiUrl\":\"\",\"contents\":\"\",\"discountPrice\":0,\"discountRate\":0,\"isbn\":\"9784065291856\",\"itemCaption\":\"ヨーロッパで移民として生きるため、自家製の言語“パンスカ”をつくり出したHirukoは、
(省略)
sNameKana\":\"シンチョウ ブンコ\",\"size\":\"文庫\",\"smallImageUrl\":\"https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1011/10110428.jpg?_ex=64x64\",\"subTitle\":\"\",\"subTitleKana\":\"\",\"title\":\"沈まぬ太陽(3(御巣鷹山篇))\",\"titleKana\":\"シズマヌ タイヨウ\"}}],\"carrier\":0,\"count\":101,\"first\":1,\"hits\":30,\"last\":30,\"page\":1,\"pageCount\":4}"
このようなレスポンスが返ってきます。この時点では、 " "
に囲まれているので文字情報(JSON)となっていますので、これを JSON
ライブラリで Ruby
で扱える情報に変換します。
irb(main):005:0> require 'JSON'
=> true
これで JSON
から Hash
や Array
に変換が可能です。
その前に、一旦先程の JSON
を変数に入れておきましょう。
irb(main):006:0> result_json = URI.open(url).read
=> "{\"GenreInformation\":[],\"Items\":[{\"Item\":{\"affiliateUrl\":\"\",\"...
最近のRubyは変数に代入した時の戻り値は少し表示されたら、後は ...
で省略されるので、見やすいですね。
代入した後の確認で、変数だけを打っておいても良いでしょう。
irb(main):007:0> result_json
=> "{\"GenreInformation\":[],\"Items\
(以下略)
では、次に変換です。
irb(main):008:0> JSON.parse(result_json)
=>
{"GenreInformation"=>[],
"Items"=>
[{"Item"=>
{"affiliateUrl"=>"",
"author"=>"多和田 葉子",
"authorKana"=>"タワダ ヨウコ",
"availability"=>"1",
"booksGenreId"=>"001004008004",
"chirayomiUrl"=>"",
"contents"=>"",
"discountPrice"=>0,
"discountRate"=>0,
(以下略)
このように返ってきました。見てみると、
{
"discountPrice"=>0,
"discountRate"=>0
}
Hash
でデーターが階層化されています。これで、データーが扱えそうですが、今回はもうひと工夫します。
Hash
のキーが String
だと扱いにくいので、 Symbol
型に変更しておきます。
symbolize_names
を使って、以下のように書き換えます。
irb(main):009:0> JSON.parse(URI.open(url).read, { symbolize_names: true })
=>
{:GenreInformation=>[],033244509624681327"
:Items=>
[{:Item=>
{:affiliateUrl=>"",
:author=>"多和田 葉子",
:authorKana=>"タワダ ヨウコ",
:availability=>"1",
:booksGenreId=>"001004008004",
:chirayomiUrl=>"",
:contents=>"",
:discountPrice=>0,
:discountRate=>0,
(以下略)
今度は、キーが Symbol
になっていますね。
{
:discountPrice=>0,
:discountRate=>0
}
レスポンスはこれでOKですので、一旦ここまでを変数に入れておきましょう。
irb(main):010:0> result = JSON.parse(URI.open(url).read, { symbolize_names: true })
=>
{GenreInformation:=>[],
...
ではこの変数 result
を使ってデーターを操作していきます。
Hashの場合
ドキュメントを読むのは前提ではあるのですが、簡単に当たりを付けていく事も出来ます。
大体 Apiのデーターはいきなり配列の形で出てくる事はなく、 Hash
の構造になっていますので、一旦 keys
メソッドを使って キーのみを表示させてみます。
irb(main):011:0> result.keys
=> [:GenreInformation, :Items, :carrier, :count, :first, :hits, :last, :page, :pageCount]
これだけの項目が1階層目に存在するようです。
- GenreInformation
- Items
- carrier
- count
- first
- hits
- last
- page
- pageCount
ネーミングを考えても Items
が怪しいので、
result[:Items]
で階層を掘ってみましょう。すると、
irb(main):014:0> result[:Items]
=>
[{:Item=>
{:affiliateUrl=>"",
:author=>"多和田 葉子",
:authorKana=>"タワダ ヨウコ",
:availability=>"1",
:booksGenreId=>"001004008004",
:chirayomiUrl=>"",
:contents=>"",
:discountPrice=>0,
:discountRate=>0,
(以下略)
このような結果が出てきます。注目すべきは、配列の戻り値が取れているという事です。
ということは、ここが書籍データーの集合体という事になります。
配列の場合
では、配列の場合の処理ですが、本来は each
等でループさせて情報を処理するのですが、今は構造を調べている段階ですので、1個だけ取り出したい所です。
となると、
result[:Items].first
で1つの情報のみ取得出来ます。
irb(main):017:0> result[:Items].first
=>
{:Item=>
{:affiliateUrl=>"",
:author=>"多和田 葉子",
:authorKana=>"タワダ ヨウコ",
:availability=>"1",
:booksGenreId=>"001004008004",
:chirayomiUrl=>"",
:contents=>"",
:discountPrice=>0,
:discountRate=>0,
:isbn=>"9784065291856",
:itemCaption=>
"ヨーロッパで移民として生きるため、自家製の言語“パンスカ”をつくり出したHirukoは、消えてしまった故郷の島国を探して、仲間たちと共に船の旅に出る。一行を乗せた船はコペンハーゲンからバルト海を東へ進むが、沿岸の港町では次々と謎めいた人物が乗り込んできてー。言葉で結びついた仲間たちの時空を超えた出会いと冒険を描く多和田葉子の新たな代表作。",
:itemPrice=>2090,
:itemUrl=>"https://books.rakuten.co.jp/rb/17283466/",
:largeImageUrl=>"https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1856/9784065291856_1_3.jpg?_ex=200x200",
:limitedFlag=>0,
:listPrice=>0,
:mediumImageUrl=>"https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1856/9784065291856_1_3.jpg?_ex=120x120",
:postageFlag=>2,
:publisherName=>"講談社",
:reviewAverage=>"4.2",
:reviewCount=>5,
:salesDate=>"2022年10月20日頃",
:seriesName=>"",
:seriesNameKana=>"",
:size=>"単行本",
:smallImageUrl=>"https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1856/9784065291856_1_3.jpg?_ex=64x64",
:subTitle=>"",
:subTitleKana=>"",
:title=>"太陽諸島",
:titleKana=>"タイヨウショトウ"}}
では、先程の要領で、キーを調べてみます。
irb(main):024:0> result[:Items].first.keys
=> [:Item]
キーが一つしかありません。
では、このキーを使ってもう1階層掘ってみます。
irb(main):025:0> result[:Items].first[:Item]
=>
{:affiliateUrl=>"",
:author=>"多和田 葉子",
:authorKana=>"タワダ ヨウコ",
:availability=>"1",
:booksGenreId=>"001004008004",
:chirayomiUrl=>"",
:contents=>"",
:discountPrice=>0,
:discountRate=>0,
:isbn=>"9784065291856",
:itemCaption=>
"ヨーロッパで移民として生きるため、自家製の言語“パンスカ”をつくり出したHirukoは、消えてしまった故郷の島国を探して、仲間たちと共に船の旅に出る。一行を乗せた船はコペンハーゲンからバルト海を東へ進むが、沿岸の港町では次々と謎めいた人物が乗り込んできてー。言葉で結びついた仲間たちの時空を超えた出会いと冒険を描く多和田葉子の新たな代表作。",
:itemPrice=>2090,
:itemUrl=>"https://books.rakuten.co.jp/rb/17283466/",
:largeImageUrl=>"https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1856/9784065291856_1_3.jpg?_ex=200x200",
:limitedFlag=>0,
:listPrice=>0,
:mediumImageUrl=>"https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1856/9784065291856_1_3.jpg?_ex=120x120",
:postageFlag=>2,
:publisherName=>"講談社",
:reviewAverage=>"4.2",
:reviewCount=>5,
:salesDate=>"2022年10月20日頃",
:seriesName=>"",
:seriesNameKana=>"",
:size=>"単行本",
:smallImageUrl=>"https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1856/9784065291856_1_3.jpg?_ex=64x64",
:subTitle=>"",
:subTitleKana=>"",
:title=>"太陽諸島",
:titleKana=>"タイヨウショトウ"}
どうやら、1箇所無駄?なキーがあるようです。実はこれは、楽天側でもバージョンアップをしており、
出力フォーマットのバージョンを切り替える事で、もう少しシンプルな呼び出しが可能になっているようです。
パラメーターに formatVersion=2
を付け足すだけのようですね。
今回はデフォルトの情報そのままでしたので、ここは触らずにいきます。
では、キーを取り出しましょう。
irb(main):026:0> result[:Items].first[:Item].keys
=>
[:affiliateUrl,
:author,
:authorKana,
:availability,
:booksGenreId,
:chirayomiUrl,
:contents,
:discountPrice,
:discountRate,
:isbn,
:itemCaption,
:itemPrice,
:itemUrl,
:largeImageUrl,
:limitedFlag,
:listPrice,
:mediumImageUrl,
:postageFlag,
:publisherName,
:reviewAverage,
:reviewCount,
:salesDate,
:seriesName,
:seriesNameKana,
:size,
:smallImageUrl,
:subTitle,
:subTitleKana,
:title,
:titleKana]
ようやく仕留められました。
適当にデーターを取ってみましょう。
irb(main):027:0> result[:Items].first[:Item][:isbn]
=> "9784065291856"
irb(main):028:0> result[:Items].first[:Item][:title]
=> "太陽諸島"
irb(main):029:0> result[:Items].first[:Item][:author]
=> "多和田 葉子"
irb(main):030:0> result[:Items].first[:Item][:itemPrice]
=> 2090
irb(main):031:0> result[:Items].first[:Item][:largeImageUrl]
=> "https://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/1856/9784065291856_1_3.jpg?_ex=200x200"
いい感じですね。
また、結果が何件あるか調べるには、
irb(main):018:0> result[:Items].size
=> 30
こんな感じで良いですね。
必要な情報をピックアップする
では、早速必要な情報を見ていきましょう。
一旦、楽天のドキュメントを見てみましょう。
このようになっています。
ここから、先程のキーを照らし合わせて欲しいものを考えてみると、理解が深まるのではないでしょうか?
今回絶対に必要なものは、 isbn
です。この数値を 一意のキー
として、Rails側のデーターの存在を確認するようにします。
そして、次回に持越しとなりますが、
- isbn
- 書籍タイトル
- 著者名
- 定価
- 商品URL
- 商品画像
あたりの情報で、Rails側のDBに格納出来るしくみを作りたいと思います。
後編アップしました。
【Rails】gemを使わずに 楽天Apiを動かしてみよう:後編【ハンズオン】
後編では、アプリに組み込んで動作させてみます。
結論
Apiを扱うには、コンソールである程度Apiの構造を掘り下げておくとラク。
そして、ドキュメントも読もう。
irbは神。
おわりに
内容が長くなってしまったので、2分割で書かせていただきます。
次回は、今回扱ったデーターをRails内でどのように表示し、DBに取り込むか・・・という所をやりたいと思います。
明日11日目は、@takumi3488 さんで 【CSS】テキストの下線(アンダーライン)を太くしたくないですか? です。
ここまで読んでいただきありがとうございます。