質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.50%

  • PHP

    20374questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

PHPでAmazonの商品を検索する

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 311

setouchi

score 2

PHPでAmazonの商品を検索してみたいのですが、どうしても400エラーが帰ってきてしまいます。公式にもサンプルがないのでなぜ遮断されているかわかりません。もしわかる方がいらっしゃいましたら教えてください。問い合わせコード自体は公式のものを改造したものです。公式のページを末尾に追記。公式ではSignatureは自分で用意する感じ。Signatureの生成がおかしいのでしょうか。

 参考サイト

当初AmazonのAPIの使い方のコード自体はImplementing a Product Advertising API Request - Product Advertising APIを参考にし、SignatureはOAuth1.0の署名(Signature)を作成する方法を参考にしました。あと、誠に自分勝手で申し訳ないですが、勉強がてら今回は外部ライブラリを使わない方法でやってみたいとうのが目的でやっています。

 実際のコード

その後、m6uさんに助けていただき、下記のコードまで成長し、Signatureの問題までこぎつけました。Amazon側のステータスコードは400から403へ変わりました。Signatureの生成に何らかの欠陥があり403は解決されません。

define("AccessKey", "hoge");
define("AssociateTag", "hoge");
define("SecretKey", "hoge");

function ItemSearch($SearchIndex, $Keywords) {

  $base = "http://webservices.amazon.com/onca/xml";
  // $base = "http://ecs.amazonaws.jp/onca/xml";

  $params = [];
  foreach ([
    "Service" => 'AWSECommerceService',
    "AssociateTag" => AssociateTag,
    "AWSAccessKeyId" => AccessKey,
    "Operation" => "ItemSearch",
    "Version" => "2013-08-01",
    "SearchIndex" => $SearchIndex,
    "Keywords" => $Keywords,
    "ResponseGroup" => "ItemAttributes,Offers",
    "Timestamp" => gmdate('Y-m-d\TH:i:s\Z'),
    "SignatureVersion" => 2,
    "SignatureMethod" => "HmacSHA256"
  ] as $key => $value) {
    $params[urlEncodeRfc3986($key)] = urlEncodeRfc3986($value);
  }

  ksort($params);

  $parsed = parse_url($base);
  $sign = join("\n", ['GET', $parsed['host'], $parsed['path'], http_build_query($params, '', '&')]);

  $signature = base64_encode(hash_hmac('sha256', $sign, SecretKey , true));

  $queries = [];
  foreach ($params as $key => $value) {
    $queries[] = $key . "=" . $value;
  }

  $request = $base . "?" . join("&", $queries) . "&Signature=" . urlEncodeRfc3986($signature);

  var_dump($request);

  $response = file_get_contents($request);
  $parsed_xml = simplexml_load_string($response);

  print_r($parsed_xml);
}

function urlEncodeRfc3986($str) {
  return str_replace('%7E', '~', rawurlencode($str));
}

ItemSearch('シーツ', 'PetSupplies');
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+2

$signature_data = "GET&$baseEncode&$request_params";
GET の後にブランク入れる必要があるような?

rawurlencode()を2度もかけてるのも改善しないと。

日本の場合のエンドポイントは
「http://ecs.amazonaws.jp/onca/xml」と「https://aws.amazonaws.jp/onca/xml」
らしいです。
サンプルコードだとUSに問い合わせしちゃうように見える。

リクエスト文字列の途中に半角空白があると、
そこまでしか読み取られず、
リクエストに必要なパラメータが足りないことで
400 Bad requestになる可能性がある。

Signature送信のために、Timestampも必要になるけど、
その対応が漏れているのかも。
参考:REST リクエストのサンプル

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/27 12:27 編集

    回答ありがとうございます!下記の様にすればよいのでしょうか?
    ```
    $signature_data = "GET $baseEncode $request_params";
    ```
    これでも残念ながらエラーとなってしまいます。

    キャンセル

  • 2018/06/27 12:31

    実装したことがないので、
    https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html
    を読みながら思いついたことを書き出してみた次第。
    送信直前のリクエスト文字列をvar_dump()してみれば、
    おかしいところがわかるかもよ。

    キャンセル

  • 2018/06/27 12:31

    こういうことでしょうか?これでもエラーとなってしまいます。
    ```
    $signature_data = "GET " . $base . " " . http_build_query($params, '', '&');
    ```

    キャンセル

  • 2018/06/27 12:32

    ありがとうございました!

    キャンセル

  • 2018/06/27 13:13

    GET のリクエスト文字列の、キーに当たるところはrawurlencode()する必要がなくて、
    値のみrawurlencode()しての&で連結っていうシンプルな流れで十分じゃないかなぁ。

    キャンセル

  • 2018/06/27 13:16

    おおー参考になります!!
    > リクエスト文字列の途中に半角空白があると、そこまでしか読み取られず、リクエストに必要なパラメータが足りないことで400 Bad requestになる可能性がある。

    キャンセル

  • 2018/06/27 13:19 編集

    Signatureは[こちら](https://docs.aws.amazon.com/AWSECommerceService/latest/GSG/ImplementinganA2SRequest.html#php-requests)によると必要みたいです。

    キャンセル

  • 2018/06/27 13:38 編集

    最終的に下記のようなリクエストURLが生成されるんですが、なんとなくSignatureが間違っているなら403となっても良い気がするんですが400。URLが壊れているようには見受けられなく。リクエストのエンドポイントを http://ecs.amazonaws.jp/onca/xml に変更しても変わらず。
    ```
    http://webservices.amazon.com/onca/xml?AWSAccessKeyId=[アクセスキーURLエンコード値]&
    AssociateTag=[アソシエイトタグのURLエンコード値]&
    Keywords=PetSupplies&
    Operation=ItemSearch&
    ResponseGroup=ItemAttributes%2COffers&
    SearchIndex=[キーワードのURLエンコード値]&
    Service=AWSECommerceService&
    Version=2013-08-01&
    Signature=[SignatureのURLエンコード値]
    ```
    Signatureを抜いてcurlでリクエストを投げるとSignatureは必須だと正常に返してくれる。
    ```
    <?xml version="1.0"?>
    <ItemSearchErrorResponse xmlns="http://ecs.amazonaws.com/doc/2013-08-01/"><Error><Code>MissingParameter</Code><Message>The request must contain the parameter Signature.</Message></Error><RequestID>hoge</RequestID></ItemSearchErrorResponse>
    ```

    キャンセル

  • 2018/06/27 14:00

    Timestampかな、と。

    キャンセル

  • 2018/06/27 14:13

    おおお!本当にありがとうございます!
    403までこぎつけました!!Signatureが駄目だと言っています。あとはSignatureがなぜ駄目なのかを調査すればいけそうです。
    ```
    <?xml version="1.0"?>
    <ItemSearchErrorResponse xmlns="http://ecs.amazonaws.com/doc/2013-08-01/"><Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message></Error><RequestID>hogehoge</RequestID></ItemSearchErrorResponse>
    ```

    キャンセル

  • 2018/06/27 14:33

    hash_hmac() するときのアルゴリズム指定が'sha256'、かな

    キャンセル

  • 2018/06/27 16:27 編集

    あれからいろいろ試しましたが、403は変わらず。

    * sha256による生成
    * RFC3986によるURLエンコード
    * SignatureVersionの指定
    * SignatureMethodの指定
    * SecretKeyの使用方法の誤りを修正

    上記の変更を適用したコードを質問のコードに適用(質問を修正)しました。署名文字列はスペース区切りから改行区切りといろいろ試しましたが、どちらも変わらず。

    キャンセル

  • 2018/06/27 17:15 編集

    signatureにhash_hmac()したあとにさらにrawurlencode()加えるのとかOK? 得られたsignature文字列に+とか=とか入っている場合にはそこもURLエンコードしないといけない。

    キャンセル

  • 2018/06/27 17:56

    signatureはhash_hmac -> base64_encode -> rawurlencode -> %7Eを~に変換(RFC3986)という処理を加えております。

    キャンセル

  • 2018/06/27 18:06

    ここまでくると、リクエストのサンプル(https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html)と比較して何が足りないかを考えるしかなくなってきますね。

    キャンセル

  • 2018/06/27 18:44

    解決しました。1日がかりでしたが、m6uさんが適切なヒントをくださったおかげです。最大限の感謝をお送り致します。とても楽しかったです。ありがとうございました!!

    キャンセル

+2

さすがにそんなところから自分で書いてたら面倒すぎる。
大昔は一から書いてたけどすぐにclass化。
今ならこの辺。
https://github.com/Exeu/apai-io

これでも面倒なのでさらにLaravel用には自分で作ってるけど。
https://github.com/kawax/laravel-amazon-product-api

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

check解決した方法

0

m6uさん協力の元解決しました。下記のコードが完成形です。

$accessKey = "access";
$secretKey = "secret";
$associateTag =  "tag";
$endpoint = "webservices.amazon.co.jp";
$uri = "/onca/xml";

foreach ([
  "Service" => "AWSECommerceService",
  "Operation" => "ItemSearch",
  "AWSAccessKeyId" => $accessKey,
  "AssociateTag" => $associateTag,
  "SearchIndex" => "PetSupplies",
  "ResponseGroup" => "Images,ItemAttributes,Offers",
  "Keywords" => "シーツ",
  "Timestamp" => gmdate('Y-m-d\TH:i:s\Z')
] as $key => $value) {
  $params[rawurlencode($key)] = rawurlencode($value);
};

ksort($params);

$queries = [];
foreach ($params as $key => $value) {
  $queries[] = "$key=$value";
}

$queryString = join("&", $queries);
$signature = base64_encode(hash_hmac("sha256", join("\n", ["GET", $endpoint, $uri, $queryString]), $secretKey, true));

echo file_get_contents("https://$endpoint$uri?$queryString&Signature=" . rawurlencode($signature));

署名のコードでhttp_build_queryで二重にURLエンコードされていた点(完全に見落としです)。ここが解決された後エラーは503エラーとなり、そこからAmazonへのリクエス時の不要なクエリを削除していくことで無事200が返りました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.50%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • PHP

    20374questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。