Amazon API 経由で書籍を検索する

ひとまずのまとめ

事前準備部分

  1. Amazonアソシエイトプログラムにアカウントを作る
  2. Access Key ID と Secret Key を Amazon アソシエイトのサイトから取得する
  3. アカウント作成時に一緒に生成されるアソシエイトIDを取得する

コーディング部分(準備段階)

  1. パラメータを用意(TimestampとSignature以外)
  2. パラメータに Timestamp を付与
  3. リクエスト部分とパラメータに対して Signature を HMAC-SHA256 で準備(参考: Swift 4で HMAC-SHA256)
  4. Signature を base64 でエンコードした後に Signature を再度エンコード

コーディング部分(通信段階)

  1. 準備段階で用意した URL を使ってダウンロードを行う
  2. XMLで検索結果が返ってくるのでパースして結果を利用する

1. Amazonアソシエイトプログラムにアカウントを作る
これは Query を投げる際にアカウントが無いと AccessKey が affiliate.amazon.co.jp で認識できないと判定されて、Query できなくなっている。あと、Required 指定されている(2017-10-02現在) AssociateTag を指定するために必要。調査段階で先に aws.amazon.com にも同じメールアドレスを使ってアカウント作ってしまったので、そちらのアカウントも必須かどうかまでは未確認です。

API Version=2013-08-01 API Reference (公式)

後述のパラメータの意味や指定方法なども上記サイトからたどれます。が、サンプルは必ずしも正しいとは限らないので参考情報と思った方が良いかもしれない。

2. Access Key ID と Secret Key を Amazon アソシエイトのサイトから取得する

[ツール] → [Product Advertising API] → [認証キーの管理] からアクセスキーIDとシークレットキーを取得。アクセスキーIDは AWSAccessKeyID に指定する値となり(URLのQueryに指定する)、シークレットキーは HMAC-SHA256 を計算する際のキーになる(内部計算用の秘密鍵なので、URLに指定するものでは無い)。

3. アカウント作成時に一緒に生成されるアソシエイトIDを取得する

Amazon アソシエイトのサイトにログインすると、画面右上の方に表示されている。この値は AssociateTag に指定する値になる。

4. パラメータを用意(TimestampとSignature以外)

パラメータを追加する順番があるので気をつける。わからなくなったら以下公式のツールを使って、どのようにオーダーされるか確認していその通りに合わせると楽。パラメータを Array で格納してバイトオーダーでソートできれば、並び順は気にしなくても良いかもしれない。

Signed Request Helper (公式)

AWSAccessKeyId= Amazon アソシエイトで作ったアクセスキー
AssociateTag=  Amazon アソシエイトでアカウント作成時に生成されたアソシエイトID
IdType=ISBN Operation=ItemLookupの場合に指定。ItemLookupではない場合は除外

ISBNを指定する場合。

ItemId=4568504287 Operation=ItemLookupの場合に指定。ItemLookupではない場合は除外

実際のISBN値。

Operation=ItemSearch TitleやAuthorのパラメータを使う場合はItemSearch

CommonItem Search Parameters (公式)

ResponseGroup=ItemAttributes,Images 結果を受け取る際の項目内容。署名する前に “,” (カンマ)はエンコードする必要がある。

Response Groups List (公式)

SearchIndex=Books  書籍を検索する場合の指定。他のジャンルを指定したい場合はここを変更して、他の検索用パラメータを調整する
Service=AWSECommerceService サービス名。固定値
Timestamp=2017-01-01T00:00:00Z 署名前のタイムスタンプ。”:” (コロン)は署名する前にエンコードする必要がある。
Title= 書籍をタイトルで検索する場合。当然日本語はエンコードする必要がある。並び順は Timestamp より後ろ。

5. パラメータに Timestamp を付与

var args = Array<String>()

でパラメータをひとまず Array でまとめる場合のサンプル。

private var timestamp: DateFormatter {
    let formatter = DateFormatter()
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.timeZone = TimeZone(identifier: "GMT")
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
    return formatter
}
args.append(String(format: "Timestamp=%@", self.timestamp.string(from: Date()).urlAWSQueryEncoding()))

.urlAWSQueryEncoding() で、タイムスタンプの中の “-” (ハイフン)はそのままで “:” (コロン)はエンコードする。

extension String {
    func urlAWSQueryEncoding() -> String {
        var allowedCharacters = CharacterSet.alphanumerics
        allowedCharacters.insert(charactersIn: "-")
        if let ret = self.addingPercentEncoding(withAllowedCharacters: allowedCharacters ) {
            return ret
        }
        return ""
    }
}

6. リクエスト部分とパラメータに対して Signature を HMAC-SHA256 で準備

参考: Swift 4で HMAC-SHA256

HMAC 計算部分だけ抜粋。このタイミングで base64encode もしてしまう。

extension String {
    func hmac(algorithm: CryptoAlgorithm, key: String) -> String {
        var result: [CUnsignedChar]
        if let ckey = key.cString(using: String.Encoding.utf8), let cdata = self.cString(using: String.Encoding.utf8) {
            result = Array(repeating: 0, count: Int(algorithm.digestLength))
            CCHmac(algorithm.HMACAlgorithm, ckey, ckey.count-1, cdata, cdata.count-1, &result)
        } else {
            fatalError("Nil returned when processing input strings as UTF8")
        }

        return Data(bytes: result, count: result.count).base64EncodedString()
    }
}

エンコード対象はHTTPサーバに対するクエリ部分とパラメータをまとめたものを指定する。このタイミングで手順2で取得したシークレットキーを使って HMAC-SHA256 の値を求める。

var params: String = ""
for arg in args {
    params += (params.count == 0 ? "": "&") + arg
}
let signTarget = String(format: "GET\nwebservices.amazon.co.jp\n/onca/xml\n%@", params)
let signature = signTarget.hmac(algorithm: .SHA256, key: self.secretkey)

コード的には上記で視覚的には以下が署名対象の文字列のイメージ。イメージです。

GET
webservices.amazon.co.jp
/onca/xml
AWSAccessKeyId=01234567890&AssociateTag=0123456789(---以下略---)

このタイミングでは https とかは気にしない。

7. Signature を base64 でエンコードした後に Signature を再度エンコード

base64エンコードした時点で “=” (イコール)などがくっついてくるので、それらをエンコードする。パラメータを全部揃えてからエンコードだと、本来必要な “=” とかもエンコードされてしまうので、Signature 部分に個別に行う。

以下は準備したパラメータ群(params)を “&” で連結したものの後ろに Signature をくっつける時にエンコードしてしまう。

ret = String(format: "?%@&Signature=%@", params, signature.urlNumericEncoding())

ここまでが URL のパラメータ部分。後のために “?” も先頭につけてしまう。

8. 準備段階で用意した URL を使ってダウンロードを行う

実際に投げる場合は https でクエリする。アプリ側から http でクエリするとセキュリティ的にアウトエラーで、実行時にエラーとなります。

https://webservices.amazon.co.jp/onca/xml%@

%@の部分は Signature もひっくるめたクエリになります。手順7の ret の値。

https でクエリをかければ、返ってくる内容(例えばイメージファイルのURLなど)のURLも全て https になって返ってきます。http でブラウザで投げると返ってくる内容も http で記述されて返されます。

URL を作る段階で、署名した時に使ったドメイン(webservices.amazon.co.jp)と URL 内で指定するドメインは同じである必要があります。異なると問い合わせ時に Signature の検算エラー扱いになります。

実際のURLは以下。投げてもエラーになります。

https://webservices.amazon.co.jp/onca/xml?AWSAccessKeyId=AKIAJ3SKECER6QTBW3YA&AssociateTag=foobarbuz-999&Operation=ItemSearch&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2017-10-02T06%3A45%3A25Z&Title=%E3%83%80%E3%83%B3%E3%82%B8%E3%83%A7%E3%83%B3%E9%A3%AF&Signature=48x1Jg6uerLQpOj7F2rASmADQIkDYPQI8K4xxF50%2Bgg%3D

上記 Access key は削除済み。Associate tag はダミーです。参考まで。

9. XMLで検索結果が返ってくるのでパースして結果を利用する

これから実装。

to be continued…

フォローする