FileMakerで楽天APIを叩く【楽天商品検索API編】

#目的と概要
FileMaker Pro Advancedを使用し、楽天APIにJANコードや商品名等のキーワードでリクエストを送信して得たXMLをレコードに保存する実例サンプル。具体的使用例としては、バーコードリーダー等で得たJANコードを基に商品名を得るなどができる（はず）。


##目的の背景
FileMakerでは、Ver.12より「URL から挿入」というスクリプトステップが実装され、指定したURLのHTMLソースや、各種APIが返すXMLなどのテキストをフィールドに格納することが可能となった。これにより、FileMakerシステムとWeb情報との連携が容易になり、情報更新の自動化などの利便性が飛躍的に向上した。

もともとはこの機能を元に、Amazonの商品情報API（Product Advertising API）をFileMakerから検索するシステムを実装したかったが、Amazonはリクエスト送信時に付与するSignatureをSHA-256でエンコードする必要があり、FileMaker単体ではエンコードに使用する関数が実装されていないため不可能であった。SHA-256エンコード用のプラグインを使用するか、Javascriptを内包しWebビューアーから取得するなどすれば実現可能だとは思われるが、今後検討することとして、とりあえず目的を手っ取り早く実現するために、認証が簡単だった楽天APIを利用することに切り替えた。
##見どころ
- 目的のXMLタグの内容を抽出する再帰カスタム関数の作成
- 楽天商品名によく出てくる「【送料無料】」などの不要な文字列を除去する再帰カスタム関数の作成

#作成した環境
- FileMaker Pro Advanced Ver14（カスタム関数を使用するため、Adv版が必須。）
- Windows 7（MacOSXだとAppleScriptを利用してSHA-256エンコードした値をFileMakerで取得できるらしい。）

#事前に準備が必要なこと
## 楽天APIに対してアプリを登録し、アプリIDを取得する
楽天APIでは、楽天APIを利用するアプリケーションに対して「アプリID」という識別IDを発行し、APIに対してリクエストを送信する際には、この値をパラメーターに含めなければならないので、サンプルファイルを利用する際には個別にアプリIDを登録する必要がある。
新規アプリ登録：https://webservice.rakuten.co.jp/app/create

簡単なフォームに記入すれば、即時にアプリIDが発行される。このIDをコピーし、サンプルファイルに入力すればAPIが利用できる。
![スクリーンショット 楽天API](https://qiita-image-store.s3.amazonaws.com/0/44526/be51dad4-18cb-eecf-f2e4-b214b3bc1beb.png)


#サンプルファイルの解説
##サンプルファイルダウンロードURL
https://www.dropbox.com/s/vafbkdb68zwbz96/rakuten-api.zip?dl=0

![スクリーンショット](https://qiita-image-store.s3.amazonaws.com/0/44526/fe514ba3-a7e2-9e3d-4b77-52dc1ad463db.png)
※レイアウト名がAmazonになっているのは、前述のとおり最初はAmazonAPIを利用しようとして断念したため。

##テーブル構造
![スクリーンショット（テーブル構造）](https://qiita-image-store.s3.amazonaws.com/0/44526/47944020-9246-2ba7-29e5-eae72be447d2.png)
テーブルの構造は上の画像の通り。
###グローバルフィールド（API・アプリ固有の値）
楽天APIから発行されたアプリIDを格納する[APP_ID]、APIのURL（パラメーター以前の部分）を格納する[URL]フィールドは、APIの仕様やAPIアプリIDが変更されないかぎり、常に固有のテキストを使用するのでグローバルフィールドとしている。

今回は実装サンプルとして構造が把握しやすいようにレコード中のグローバルフィールドとして設定しているが、この状態だとユーザーが意図せずともフィールド内容が更新され、APIに正常なURLが渡らなくなるおそれがあるので、実際の運用時には別途グローバルフィールドを格納するマスタテーブルを作成して参照するか、スクリプトによりグローバル変数として格納するなどの工夫が必要だろう。

[APP_ID]は、サンプルスクリーンショットにある通り、`applicationId=nnnnnnnnnnnnnnnnnnn`のように先頭にパラメーターを付与するようにしている。サンプルファイルではID部分を消去しているので、テストする場合はアプリIDを新規発行して追加してもらいたい。
###リクエストURLを生成するためのフィールド
[PRAM]はAPIの仕様に基づいたパラメーターを設定するためのフィールドであり、１要素１行の改行区切りとしている。パラメーター設定により、出力件数やソート順のほか、「在庫があるもののみ抽出」「商品画像が登録されているもののみを抽出」など、要求に応じて出力データを指定することが出来る。
パラメーター仕様についてはhttps://webservice.rakuten.co.jp/api/ichibaitemsearch/ に記載されている。実際の運用時にパラメーターを変更する必要がある場合は、エラーを防止するためパラメーターごとに値一覧や入力規則により入力値を制限したほうが利便性が向上するだろう。

［RequestURL］は、APIに送信するURLで、次の計算式を指定している

```
//［RequestURL］フィールド計算式
URL&APP_ID&"&format=xml&keyword="& keyword & "&" & Substitute ( RAKUTEN_API::PRAM ; "¶" ; "&" )
```

楽天APIではレスポンス形式をJSONとXMLから選択できるが、FileMakerでテキスト関数を用いてデータを抽出する場合はXMLのほうが都合が良かったので、固定値として`format=xml`を指定している。最後のSubstitute関数は、[PRAM]フィールドの値に含まれている改行を"&"に置換してURLの一文に追加している。

今回はサンプルとして目視できるように個別に計算式で生成しているが、実際の運用時には後段で説明する「URLから挿入」スクリプトステップ中でURLを計算式で指定できるので、特に目視確認が必要でない場合は作成不要である。

###レスポンスを取得し、加工するフィールド
[CALLBACK]フィールドは、「検索実行」ボタンに設定した「URL から挿入」スクリプトステップで、APIが返したXMLを格納するようにしている。以降のフィールドは、このフィールドに格納されたXMLを加工して値を抽出している。

今回のサンプルファイルでは、「商品名」「商品URL」「商品画像URL」を取得するようにしており、それぞれ計算フィールドとして設定した[itemName][itemUrl][imageUrl]フィールドが値を抽出するようにしてある。

今回はこれらのフィールドを計算フィールドとして設定しているが、計算フィールドは結果を保存せず、レコード呼び出しにより逐次計算を実行し値を返すため、レコード数によっては速度低下を招くので、実際の運用ではテキストフィールドなど、適切な形式で値を保存するフィールドに設定したほうが良い。

「商品名」については、APIが返した商品名と、楽天の商品名に多い「【送料無料】【明日楽対応】」などの隅付き括弧の文字列を除去し、（なるべく）商品名のみを抽出するように加工した[itemName_clean]フィールドを別途に設定してある。

なお、今回のサンプルファイルでは、これらの値は**XMLの先頭に出現するもののみを対象にして抽出し、２件目以降は対象外にしている**。そのため、hitsパラメーターを２以上に設定してレスポンスを得たり、値が複数返る要素を指定し、すべての（あるいはｎ個目の値など任意の）値を抽出する場合は、別途対応するように構成を考慮する必要がある。

商品画像はWebビューアーを用いて[imageUrl]の値を参照している。なお、APIではsmall（64*64px）とmiddle（128*128px）の画像URLのいずれかが取得できる仕様になっているが、実際は画像サイズを指定している引数を除去すればオリジナルサイズの画像が取得できるので、Webビューアーにはオリジナルサイズの画像URLを渡している。

##検索実行ボタン
![スクリーンショット 2015-09-11 15.31.52.png](https://qiita-image-store.s3.amazonaws.com/0/44526/36d641de-692f-65fd-af5b-4077fc2cd822.png)
「検索実行」ボタンには単一のスクリプトステップ「URL から挿入」を指定してる。「URLを指定」には［RequestURL］フィールドを指定しているが、前述のとおり特に理由がなければ、この部分で計算式を指定したほうがリソースに無駄がなくなる。なお、キーワードに日本語等の2byte文字が入力される可能性がある場合は、この計算式を指定する部分でURLエンコードを自動化するオプションを設定する必要がある。

##XMLから値を抽出するカスタム関数
先ほどの[itemName][itemUrl][imageUrl]の各計算フィールドには、XMLから値を抽出するためのカスタム関数が設定されている。
例として、商品名である[itemName]フィールドは次のように設定している。

```
//［itemName］フィールド計算式
getXmlElm ( CALLBACK ; "itemName";1;1 )
```

カスタム関数`getXmlElm(text;tag)`は、次の計算式が設定されている。

```
/*
 getXmlElm(text;tag;n;m)関数
 XMLに含まれているタグを抽出して返す。
 抽出するタグが複数ある場合、値を改行で区切って返す。
 text:抽出するXMLタグが含まれているテキスト（XML文）
 tag:抽出するXMLタグ　<itemName> を抽出する場合は　"itemName"のように設定する。
 n:抽出を開始するXMLタグの出現順番
 m:抽出を終了するXMLタグの出現順番（０指定でn以降全て）
 ex)１個目から4個目のタグを抽出：n=1;m=4
*/

Let([
	%text     = text;
	%tag      = tag;
	%starttag = "<" & %tag & ">";
	%endtag   = "</" & %tag & ">";
	%start    = Position(%text;%starttag;0;n) + Length(%starttag);
	%end      = Position(%text;%endtag;0;n) - %start
        %m        = if(m=0;PatternCount(%text;%endtag);m)
];
Case(
	n < %m ; Middle(%text;%start;%end)&"¶"&getXmlElm(%text;tag;n+1;m);
	n = %m ; Middle(%text;%start;%end)
	)
)
```
関数の仕組みとしては、textから与えられたtagを基に`<tag>`、`</tag>`のように開始タグと終了タグをそれぞれ生成し、Position関数でその出現位置の文字数である%startと%endを得て、Middle関数でその文字数範囲を抽出している。引数n、mは抽出範囲を示すもので、nを抽出した後に再帰によってn+1を抽出するように自己を呼び出している。再帰の終了は明示していないが n>m が成立した時点である。

今回は複数抽出する必要がないので、各要素を取り出す計算式にはいずれもn=1,m=1を与えているが、計算式で範囲を指定するなどして、必要に応じて複数個のXMLタグの要素を得ることが出来る。複数個の場合の返り値は改行区切りであるため、ValueCount関数などの既存のFileMaker関数にも適用できる。

##楽天商品名から【】で囲まれた文字列を消去するカスタム関数

前述のとおり、楽天に登録されている商品名には【送料無料】など商品名と直接関係のないサービス名などが記入されていることが多く、データーベースに登録する際には不要となるケースが多いだろう。このため、指定文字に囲まれた文字列を一律に除去するカスタム関数を作成する。サンプルファイルでは[itemName_clean]フィールドの計算式にTrimBrackets関数を指定して、【】を除去した商品名を得ている。

```
/*
TrimBrackets(text;leftchar;rightchar)
textに含まれている leftchar と rightchar に囲まれた文字列を一律に削除する。
  text ＝ 除去する文字列が含まれたテキスト
  leftchar = 除去を開始する指定文字。通常"【"などの開始カッコを指定
  rightchar = 除去を終了する指定文字。通常"】"などの終了カッコを指定
*/
Let([
	%text = text;
	%left = leftchar;
	%right = rightchar;
	%c_left = Position(%text;%left;1;1);
	%c_right = Position(%text;%right;1;1)+1
];
Case(
	%c_right >0;
	TrimBrackets(Replace(%text;%c_left;%c_right-%c_left;"");%left;%right);
%text
))
```

この関数も再帰を使用し、先頭から指定文字`leftchar`、`rightchar`に囲まれた文字列を先頭から検索して除去している。この関数の場合は再帰の終了を指定文字の出現数=0で指定している。


#参考情報
- 動作検証のために「生きている」JANコードのリストが必要だったので、ホーザン株式会社が公開してる同社のJANコード一覧表を使用した。サンプルファイルでは、このリストから抽出したJANコードをレコードに登録している。ホーザン株式会社のJANコード一覧表：http://www.hozan.co.jp/support/data/JAN_code.html
- 楽天APIにはテストフォームが用意されているので、パラメーター指定などでエラーが発生する場合には、テストフォームを利用して目的のレスポンスが返るURLを取得して比較することができる。
https://webservice.rakuten.co.jp/explorer/api/IchibaItem/Search/
- keywordパラメーターは、半角128文字以内の制限がかけられているので、実際の運用時にはリクエスト送信前にキーワードの文字列長を計算する必要がある。
- スクリプトでLoopを組むなどして、短時間にAPIに多数のリクエストを送るとエラーが返るので、Loopの中に「スクリプト一時停止／続行」スクリプトステップを組み込み、適切な間隔でリクエストを送るようにすること。


#まとめと感想

Amazon APIと比較すると楽天APIは認証周りが非常に簡単なので、手っ取り早くデータベースにAPIを組み込むなら楽天APIに分がある。

タイトルには【楽天商品検索API編】と打ったが、他のAPIも基本的な仕組みは一緒なようなので次回があるかどうかは不明。楽天ブックスとゴルフ場検索の楽天GORAのAPIは実装すれば面白そうなのでやりたい。

今回のサンプルを作成するまでは、再帰カスタム関数の仕組み（停止条件と返り値の指定）が今ひとつ理解していなかったので、サンプルファイルで動作可能な再帰カスタム関数を作成することができたので良い学習になった。