タイトル: リクエストにスペースを含める方法
SEOタイトル: Amazon PA-API リクエストにスペースを含める方法完全ガイド
| この記事の要点 |
|
PA-API v5 とスペースの扱い
Amazon Product Advertising API は 2020 年以降 v5 に統一されました。v5 ではPOST + JSON ボディでリクエストを送るので、複数語キーワード(例: harry potter)はJSON 文字列の中にそのままスペースで書くのが基本です。
一方で v4 以前は GET + クエリ文字列だったため、スペースは URL エンコードが必要でした。古いコードと新コードが混在する解説が多いので、まずどちらの API かを確認してください。
v5 (現行) のリクエスト例
// SearchItems の JSON ボディ
POST /paapi5/searchitems HTTP/1.1
Host: webservices.amazon.co.jp
Content-Type: application/json; charset=utf-8
X-Amz-Date: 20260611T100000Z
Authorization: AWS4-HMAC-SHA256 Credential=AKIA...
{
"Keywords": "harry potter", // ← スペースそのまま OK
"SearchIndex": "All",
"ItemCount": 10,
"Resources": ["Images.Primary.Small", "ItemInfo.Title"],
"PartnerTag": "yourtag-22",
"PartnerType": "Associates",
"Marketplace": "www.amazon.co.jp"
}
JSON 文字列の中にあるスペースは何もする必要なし。ASCII の半角スペース (0x20) として送信されます。
署名計算(AWS V4)でのスペース
v5 の署名対象はボディ全体のハッシュ + Canonical Headersです。クエリ文字列を使わないので URL エンコードを意識する必要はほぼありません。ただし、Canonical Request 内に URI パス(/paapi5/searchitems)が含まれます。パスにスペースが入ることはまずありませんが、入れる場合は RFC 3986 準拠の rawurlencode でエンコードする必要があります。
<?php
// RFC 3986 準拠のエンコード
$encoded = rawurlencode('harry potter');
echo $encoded; // harry%20potter
// urlencode() との違い: urlencode は + を使う(application/x-www-form-urlencoded 用)
echo urlencode('harry potter'); // harry+potter
echo rawurlencode('harry potter'); // harry%20potter
// PA-API v4 のような URL 直書きでは rawurlencode のほうが安全
PHP での実装
cURL で直接呼ぶ
<?php
function paapiSearch(string $keywords): array {
$accessKey = getenv('PAAPI_ACCESS_KEY');
$secretKey = getenv('PAAPI_SECRET_KEY');
$partnerTag = 'yourtag-22';
$host = 'webservices.amazon.co.jp';
$region = 'us-west-2';
$service = 'ProductAdvertisingAPI';
$target = 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems';
$path = '/paapi5/searchitems';
$payload = json_encode([
'Keywords' => $keywords, // ★ スペースそのまま
'SearchIndex' => 'All',
'ItemCount' => 10,
'Resources' => ['Images.Primary.Small', 'ItemInfo.Title'],
'PartnerTag' => $partnerTag,
'PartnerType' => 'Associates',
'Marketplace' => 'www.amazon.co.jp',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$amzDate = gmdate('Ymd\THis\Z');
$dateStamp = gmdate('Ymd');
// Canonical Request
$canonicalHeaders =
"content-encoding:amz-1.0\n" .
"content-type:application/json; charset=utf-8\n" .
"host:{$host}\n" .
"x-amz-date:{$amzDate}\n" .
"x-amz-target:{$target}\n";
$signedHeaders = 'content-encoding;content-type;host;x-amz-date;x-amz-target';
$payloadHash = hash('sha256', $payload);
$canonicalRequest =
"POST\n{$path}\n\n{$canonicalHeaders}\n{$signedHeaders}\n{$payloadHash}";
// String to Sign
$credentialScope = "{$dateStamp}/{$region}/{$service}/aws4_request";
$stringToSign =
"AWS4-HMAC-SHA256\n{$amzDate}\n{$credentialScope}\n" .
hash('sha256', $canonicalRequest);
// Signature
$kDate = hash_hmac('sha256', $dateStamp, "AWS4{$secretKey}", true);
$kRegion = hash_hmac('sha256', $region, $kDate, true);
$kService = hash_hmac('sha256', $service, $kRegion, true);
$kSigning = hash_hmac('sha256', 'aws4_request', $kService, true);
$signature = hash_hmac('sha256', $stringToSign, $kSigning);
$authHeader =
"AWS4-HMAC-SHA256 Credential={$accessKey}/{$credentialScope}, " .
"SignedHeaders={$signedHeaders}, Signature={$signature}";
$ch = curl_init("https://{$host}{$path}");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Encoding: amz-1.0',
'Content-Type: application/json; charset=utf-8',
"Host: {$host}",
"X-Amz-Date: {$amzDate}",
"X-Amz-Target: {$target}",
"Authorization: {$authHeader}",
],
CURLOPT_RETURNTRANSFER => true,
]);
$res = curl_exec($ch);
return json_decode($res, true);
}
// 使用
$result = paapiSearch('harry potter'); // 半角スペース OK
$result = paapiSearch('鬼滅 の刃'); // 日本語 + スペース OK
print_r($result);
公式 PA-API SDK 利用(推奨)
// Amazon 公式 SDK (paapi5-php-sdk) をインストール
// composer require ...
use Amazon\ProductAdvertisingAPI\v1\ApiException;
use Amazon\ProductAdvertisingAPI\v1\api\DefaultApi;
use Amazon\ProductAdvertisingAPI\v1\Configuration;
use Amazon\ProductAdvertisingAPI\v1\com\amazon\paapi5\v1\SearchItemsRequest;
$config = new Configuration();
$config->setAccessKey(getenv('PAAPI_ACCESS_KEY'));
$config->setSecretKey(getenv('PAAPI_SECRET_KEY'));
$config->setHost('webservices.amazon.co.jp');
$config->setRegion('us-west-2');
$api = new DefaultApi(new \GuzzleHttp\Client(), $config);
$req = new SearchItemsRequest();
$req->setPartnerTag('yourtag-22');
$req->setPartnerType('Associates');
$req->setKeywords('harry potter'); // ★ スペースそのまま
$req->setSearchIndex('All');
$req->setItemCount(10);
try {
$res = $api->searchItems($req);
foreach ($res->getSearchResult()->getItems() as $item) {
echo $item->getItemInfo()->getTitle()->getDisplayValue() . "\n";
}
} catch (ApiException $e) {
echo $e->getMessage();
}
JS / Node.js
// fetch で直接呼ぶ場合
const body = JSON.stringify({
Keywords: 'harry potter', // スペースそのまま
SearchIndex: 'All',
ItemCount: 10,
PartnerTag: 'yourtag-22',
PartnerType: 'Associates',
Marketplace: 'www.amazon.co.jp'
});
// URL 構築時にスペースを含めるなら
const url = 'https://example.com/search?q=' + encodeURIComponent('harry potter');
// → ?q=harry%20potter
v4 (旧) でのスペース
古い PA-API v4 は GET メソッドでクエリ文字列に乗せる方式だったので、必ず URL エンコードが必要でした。
GET /onca/xml?Operation=ItemSearch&Keywords=harry%20potter&...
↑ 必須エンコード
PHP: rawurlencode('harry potter') → harry%20potter
JS: encodeURIComponent('harry potter') → harry%20potter
よくあるトラブル
| 症状 | 原因 | 対処 |
|---|---|---|
| SignatureDoesNotMatch | 署名対象とボディが不一致 | JSON ボディ完全一致を確認 |
| "+" がそのまま検索される | urlencode() の "+" 仕様 | rawurlencode() 使用 |
| 日本語が文字化け | JSON_UNESCAPED_UNICODE 未指定 | フラグ追加 + UTF-8 で送信 |
| InvalidParameterValue | Keywords が空 or 長すぎ | 1〜250 文字 |
| BadRequest | Content-Type ミス | application/json; charset=utf-8 |
Lambda からの呼出
サーバレス環境で PA-API を叩く際はSecrets Manager に Access/Secret Key を保管し、IAM ロールで取得するのが定石。Lambda の実行ロール自体には PA-API のアクセス権限不要(PA-API は IAM ではなく専用 Access Key 認証)。
FAQ
Q: + と %20 どちらが正しい?
A: HTTP 仕様としては URL のパスは %20、フォームの application/x-www-form-urlencoded は +。JSON ボディ内ならエンコード不要のスペース。
Q: Keywords に複数語入れると AND 検索?
A: PA-API は内部で AND 寄りに解釈します。完全フレーズなら "" で囲む(JSON 内では \" でエスケープ)。
Q: スペースを + で入れたら署名エラー
A: ボディ JSON 内の + はリテラルの「+」として送信されます。スペースの代わりにしないでください。