Java で Amazon Product Advertising API 5.0 (PA-API v5) をコールする

## 概要

- Java で Amazon Product Advertising API 5.0 (PA-API v5) をコールする
- AWS が提供している公式の SDK を使わず API を直接コールする
- AWS 署名バージョン 4 (AWS Signature Version 4) の処理を Java 標準ライブラリのみで実装する
- 今回の実行環境: macOS Catalina + Java 15 (AdoptOpenJDK 15) + Jackson Databind 2.11.1 + Gradle 6.6.1

## サンプルコード

### ファイル一覧

```
├── build.gradle
└── src
    └── main
        └── java
            ├── AwsSignature4.java
            ├── JsonUtil.java
            ├── MyApp.java
            └── PaApi5Wrapper.java
```

### build.gradle

Gradle の設定ファイル。
JSON 操作ライブラリは Java の標準ライブラリにないため Jackson を使用する。

```groovy
plugins {
  id 'java'
  id 'application'
}

repositories {
  mavenCentral()
}

dependencies {
  // Jackson を使う
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1'
}

application {
  mainClassName = 'MyApp'
}

sourceCompatibility = JavaVersion.VERSION_15
```

### src/main/java/MyApp.java         

```java
import java.util.HashMap;
import java.util.Map;

/**
 * PA-API v5 をコールするサンプルクラス。
 */
public class MyApp {

  public static void main(String[] args) throws Exception {
    // PA-API v5 をコールする
    searchItems();
    getItems();
  }

  private static final String ACCESS_KEY = "<YOUR-ACCESS-KEY-HERE>"; // 取得したアクセスキー
  private static final String SECRET_KEY = "<YOUR-SECRET-KEY-HERE>"; // 取得したシークレットキー
  private static final String TRACKING_ID = "<YOUR-PARTNER-TAG-HERE>"; // トラッキングID (例: XXXXX-22)

  // キーワードから商品を検索
  public static void searchItems() throws Exception {

    String keywords = "シェイクスピア";

    // リクエスト情報
    Map<String, Object> req = new HashMap<>() {
      {
        put("ItemCount", 3); // 検索結果の数
        put("PartnerTag", TRACKING_ID); // ストアID or トラッキングID
        put("PartnerType", "Associates"); // パートナータイプ
        put("Keywords", keywords); // 検索キーワード
        put("SearchIndex", "All"); // 検索カテゴリー (All, AmazonVideo, Books, Hobbies, Music などを指定可能)
        put("Resources", new String[]{ // レスポンスに含む値のタイプ
          "ItemInfo.Title",
          "ItemInfo.ByLineInfo",
          "ItemInfo.ProductInfo",
          "Images.Primary.Large",
          "Images.Primary.Medium",
          "Images.Primary.Small"
        });
      }
    };

    // リクエスト情報を JSON 文字列にする
    String reqJson = new JsonUtil().objectToJson(req);
    System.out.println("===== キーワードから商品を検索: リクエスト =====");
    System.out.println(reqJson);

    // PA-API v5 をコールして結果を JSON 文字列で受け取る
    PaApi5Wrapper api = new PaApi5Wrapper(ACCESS_KEY, SECRET_KEY);
    String resJson = api.searchItems(reqJson);
    System.out.println("===== キーワードから商品を検索: レスポンス =====");
    System.out.println(new JsonUtil().prettyPrint(resJson));
  }

  // ASIN から商品情報を取得
  public static void getItems() throws Exception {

    String[] asinList = new String[]{"4391641585", "B010EB1HR4", "B0125SPF90", "B07V52KSGT"};

    // リクエスト情報
    Map<String, Object> req = new HashMap<>() {
      {
        put("PartnerTag", TRACKING_ID); // ストアID or トラッキングID
        put("PartnerType", "Associates"); // パートナータイプ
        put("ItemIds", asinList); // ASINのリスト
        put("Resources", new String[]{ // レスポンスに含む値のタイプ
          "ItemInfo.Title",
          "ItemInfo.ByLineInfo"
        });
      }
    };

    // リクエスト情報を JSON 文字列にする
    String reqJson = new JsonUtil().objectToJson(req);
    System.out.println("===== ASIN から商品情報を取得: リクエスト =====");
    System.out.println(reqJson);

    // PA-API v5 をコールして結果を JSON 文字列で受け取る
    PaApi5Wrapper api = new PaApi5Wrapper(ACCESS_KEY, SECRET_KEY);
    String resJson = api.getItems(reqJson);
    System.out.println("===== ASIN から商品情報を取得: レスポンス =====");
    System.out.println(new JsonUtil().prettyPrint(resJson));
  }
}
```

### src/main/java/PaApi5Wrapper.java

```java
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Map;

/**
 * PA-API v5 ラッパークラス。
 */
public class PaApi5Wrapper {

  private static final String HOST = "webservices.amazon.co.jp"; // Amazon.co.jp の Web API ホスト
  private static final String REGION = "us-west-2"; // Amazon.co.jp では us-west-2 を指定

  private final String accessKey;
  private final String secretKey;

  /**
   * コンストラクタ。
   * @param accessKey アクセスキー
   * @param secretKey シークレットキー
   */
  public PaApi5Wrapper(String accessKey, String secretKey) {
    this.accessKey = accessKey;
    this.secretKey = secretKey;
  }

  /**
   * キーワードから商品を検索する。
   * @param reqJson リクエスト情報 JSON
   * @return レスポンス情報 JSON
   * @throws InterruptedException
   * @throws IOException
   * @throws NoSuchAlgorithmException
   * @throws InvalidKeyException
   * @throws URISyntaxException
   */
  public String searchItems(String reqJson) throws InterruptedException, IOException, NoSuchAlgorithmException, InvalidKeyException, URISyntaxException {
    String path = "/paapi5/searchitems";
    String target = "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems";
    return callApi(reqJson, path, target);
  }

  /**
   * ASIN から商品情報を取得する。
   * @param reqJson リクエスト情報 JSON
   * @return レスポンス情報 JSON
   * @throws InterruptedException
   * @throws IOException
   * @throws NoSuchAlgorithmException
   * @throws InvalidKeyException
   * @throws URISyntaxException
   */
  public String getItems(String reqJson) throws InterruptedException, IOException, NoSuchAlgorithmException, InvalidKeyException, URISyntaxException {
    String path = "/paapi5/getitems";
    String target = "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.GetItems";
    return callApi(reqJson, path, target);
  }

  /**
   * PA-API v5 をコールする。
   * @param reqJson リクエスト情報 JSON
   * @param path API エントリポイントのパス
   * @param target リクエストの送信先サービスおよびデータのオペレーション
   * @return レスポンス情報 JSON
   * @throws URISyntaxException
   * @throws InvalidKeyException
   * @throws NoSuchAlgorithmException
   * @throws IOException
   * @throws InterruptedException
   */
  public String callApi(String reqJson, String path, String target) throws URISyntaxException, InvalidKeyException, NoSuchAlgorithmException, IOException, InterruptedException {

    // Java 11 から正式導入された HTTP Client API を使う

    // HTTP リクエスト情報を構築
    HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
      .uri(new URI("https://" + HOST + path)) // API コール用の URL
      .POST(HttpRequest.BodyPublishers.ofString(reqJson)) // API コールのパラメータをセット
      .timeout(Duration.ofSeconds(10));

    // 署名情報を付加したヘッダ情報を取得
    AwsSignature4 awsv4Auth = new AwsSignature4(accessKey, secretKey, path, REGION, HOST, target);
    Map<String, String> signedHeaders = awsv4Auth.getHeaders(reqJson);

    // リクエスト情報にヘッダをセット
    signedHeaders.remove("host"); // Host ヘッダは付加しない (jdk.httpclient.allowRestrictedHeaders)
    for (Map.Entry<String, String> entrySet : signedHeaders.entrySet()) {
      reqBuilder.header(entrySet.getKey(), entrySet.getValue());
    }

    // HTTP リクエスト情報を生成
    HttpRequest req = reqBuilder.build();

    // API をコールして結果を取得
    HttpClient client = HttpClient.newBuilder()
      .version(HttpClient.Version.HTTP_1_1)
      .connectTimeout(Duration.ofSeconds(10))
      .build();
    HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());

    // ステータスコードで成功・失敗を判断
    if (res.statusCode() == 200) {
      return res.body();
    } else {
      throw new RuntimeException(res.body());
    }
  }
}
```

### src/main/java/AwsSignature4.java

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;

/**
 * AWS 署名バージョン 4 (AWS Signature Version 4)
 */
public class AwsSignature4 {

  private static final String SERVICE = "ProductAdvertisingAPI"; // PA-API
  private static final String HMAC_ALGORITHM = "AWS4-HMAC-SHA256";
  private static final String AWS_4_REQUEST = "aws4_request";

  private final String awsAccessKey;
  private final String awsSecretKey;
  private final String path;
  private final String region;
  private final String host;
  private final String target;

  /**
   * コンストラクタ。
   * @param awsAccessKey アクセスキー
   * @param awsSecretKey シークレットキー
   * @param path API エントリポイントのパス
   * @param region リージョン
   * @param host API エントリポイントのパス
   * @param target リクエストの送信先サービスおよびデータのオペレーション
   */
  public AwsSignature4(
    String awsAccessKey, String awsSecretKey,
    String path, String region,
    String host, String target) {
    this.awsAccessKey = awsAccessKey;
    this.awsSecretKey = awsSecretKey;
    this.path = path;
    this.region = region;
    this.host = host;
    this.target = target;
  }

  /**
   * 認証用のヘッダ情報を返す。
   * @param payload リクエスト情報 JSON
   * @return 認証用のヘッダ情報
   * @throws NoSuchAlgorithmException
   * @throws InvalidKeyException
   */
  public Map<String, String> getHeaders(String payload) throws NoSuchAlgorithmException, InvalidKeyException {
    // ベースになるヘッダ
    TreeMap<String, String> headers = new TreeMap<>();
    headers.put("host", host);
    headers.put("content-type", "application/json; charset=utf-8");
    headers.put("content-encoding", "amz-1.0");
    headers.put("x-amz-target", target);
    // 署名を作成するときに使用されるタイムスタンプ
    final Date date = new Date();
    headers.put("x-amz-date", getXAmzDateString(date));
    // 署名付きヘッダー (signed headers)
    String signedHeaders = createSignedHeaders(headers);
    // 正規リクエスト (canonical request)
    String canonicalRequest = createCanonicalRequest(path, headers, signedHeaders, payload);
    // 署名文字列 (string to sign)
    String stringToSign = createStringToSign(date, region, canonicalRequest);
    // 署名 (signature)
    String signature = calculateSignature(awsSecretKey, date, region, stringToSign);
    // Authorization ヘッダー値
    String authorization = buildAuthorizationString(awsAccessKey, region, signature, signedHeaders, date);
    headers.put("Authorization", authorization);
    return headers;
  }

  // 署名付きヘッダー (signed headers)
  private static String createSignedHeaders(TreeMap<String, String> headers) {
    StringBuilder signedHeaderBuilder = new StringBuilder();
    for (String key : headers.keySet()) {
      signedHeaderBuilder.append(key).append(";");
    }
    return signedHeaderBuilder.substring(0, signedHeaderBuilder.length() - 1);
  }

  // 正規リクエスト (canonical request)
  private static String createCanonicalRequest(String path, TreeMap<String, String> headers, String signedHeaders, String payload) throws NoSuchAlgorithmException {
    StringBuilder canonicalRequest = new StringBuilder();
    canonicalRequest.append("POST").append("\n");
    canonicalRequest.append(path).append("\n").append("\n");
    for (String key : headers.keySet()) {
      canonicalRequest.append(key).append(":").append(headers.get(key)).append("\n");
    }
    canonicalRequest.append("\n");
    canonicalRequest.append(signedHeaders).append("\n");
    canonicalRequest.append(sha256(payload));
    return canonicalRequest.toString();
  }

  // 署名文字列 (string to sign)
  private static String createStringToSign(Date current, String region, String canonicalRequest) throws NoSuchAlgorithmException {
    return HMAC_ALGORITHM + "\n"
      + getXAmzDateString(current) + "\n"
      + getYMDString(current) + "/" + region + "/" + SERVICE + "/" + AWS_4_REQUEST + "\n"
      + sha256(canonicalRequest);
  }

  // 署名 (signature)
  private static String calculateSignature(String awsSecretKey, Date current, String region, String stringToSign) throws InvalidKeyException, NoSuchAlgorithmException {
    final String currentDate = getYMDString(current);
    byte[] signatureKey = getSigningKey(awsSecretKey, currentDate, region);
    byte[] signature = hmacSha256(signatureKey, stringToSign);
    return bytesToHex(signature);
  }

  // 署名キー (signing key)
  private static byte[] getSigningKey(String key, String date, String region) throws InvalidKeyException, NoSuchAlgorithmException {
    // 各ハッシュ関数の結果が次のハッシュ関数の入力になる
    byte[] kSecret = ("AWS4" + key).getBytes(StandardCharsets.UTF_8);
    byte[] kDate = hmacSha256(kSecret, date);
    byte[] kRegion = hmacSha256(kDate, region);
    byte[] kService = hmacSha256(kRegion, SERVICE);
    byte[] kSigning = hmacSha256(kService, AWS_4_REQUEST);
    return kSigning;
  }

  // Authorization ヘッダー値
  private static String buildAuthorizationString(String awsAccessKey, String region, String signature, String signedHeaders, Date current) {
    return HMAC_ALGORITHM + " "
      + "Credential=" + awsAccessKey + "/" + getYMDString(current) + "/" + region + "/" + SERVICE + "/" + AWS_4_REQUEST + ","
      + "SignedHeaders=" + signedHeaders + ","
      + "Signature=" + signature;
  }

  // ハッシュ関数 SHA-256
  private static String sha256(String data) throws NoSuchAlgorithmException {
    MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    messageDigest.update(data.getBytes(StandardCharsets.UTF_8));
    byte[] digest = messageDigest.digest();
    return String.format("%064x", new java.math.BigInteger(1, digest));
  }

  // HMAC-SHA256 関数
  private static byte[] hmacSha256(byte[] key, String data) throws NoSuchAlgorithmException, InvalidKeyException {
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(key, "HmacSHA256"));
    return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
  }

  // バイナリ値を 16 進数表現に変換
  private static String bytesToHex(byte[] data) {
    final char[] hexCode = "0123456789ABCDEF".toCharArray();
    StringBuilder r = new StringBuilder(data.length * 2);
    for (byte b : data) {
      r.append(hexCode[(b >> 4) & 0xF]);
      r.append(hexCode[(b & 0xF)]);
    }
    return r.toString().toLowerCase();
  }

  // x-amz-date ヘッダ用日時文字列 (UTC で YYYYMMDD'T'HHMMSS'Z' の ISO 8601 形式)
  private static String getXAmzDateString(Date date) {
    DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); // ISO 8601
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    return dateFormat.format(date);
  }

  // 日付文字列 yyyyMMdd 形式
  private static String getYMDString(Date date) {
    DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    return dateFormat.format(date);
  }
}
```

### src/main/java/JsonUtil.java

```java
// 外部ライブラリの Jackson を使う
import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;

/**
 * JSON 操作クラス。
 */
public class JsonUtil {

  /**
   * オブジェクトから JSON 文字列を生成する。
   * @param obj オブジェクト
   * @return JSON 文字列
   * @throws IOException
   */
  public String objectToJson(Map<String, Object> obj) throws IOException {
    StringWriter out = new StringWriter();
    ObjectMapper mapper = new ObjectMapper();
    // ASCII 文字以外は Unicode escape する
    mapper.configure(JsonWriteFeature.ESCAPE_NON_ASCII.mappedFeature(), true);
    mapper.writerWithDefaultPrettyPrinter().writeValue(out, obj);
    return out.toString();
  }

  /**
   * JSON を読みやすい形にする。
   * @param json JSON 文字列
   * @return 読みやすい形になった JSON 文字列
   * @throws IOException
   */
  public String prettyPrint(String json) throws IOException {
    StringWriter out = new StringWriter();
    ObjectMapper mapper = new ObjectMapper();
    mapper.writerWithDefaultPrettyPrinter().writeValue(out, mapper.readValue(json, Map.class));
    return out.toString();
  }
}
```

## サンプルコードの実行結果

実行環境: macOS Catalina + Java 15 (AdoptOpenJDK 15) + Gradle 6.6.1

```
$ gradle run
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :run
===== キーワードから商品を検索: リクエスト =====
{
  "PartnerType" : "Associates",
  "PartnerTag" : "XXXXX-22",
  "Keywords" : "\u30B7\u30A7\u30A4\u30AF\u30B9\u30D4\u30A2",
  "SearchIndex" : "All",
  "ItemCount" : 3,
  "Resources" : [ "ItemInfo.Title", "ItemInfo.ByLineInfo", "ItemInfo.ProductInfo", "ItemInfo.ProductInfo", "Images.Primary.Large", "Images.Primary.Medium", "Images.Primary.Small" ]
}
===== キーワードから商品を検索: レスポンス =====
{
  "SearchResult" : {
    "Items" : [ {
      "ASIN" : "B06ZZH149Y",
      "DetailPageURL" : "https://www.amazon.co.jp/dp/B06ZZH149Y?tag=XXXXX-22&linkCode=osi&th=1&psc=1",
      "Images" : {
        "Primary" : {
          "Large" : {
            "Height" : 500,
            "URL" : "https://m.media-amazon.com/images/I/41Ms+C0NwNL.jpg",
            "Width" : 311
          },
          "Medium" : {
            "Height" : 160,
            "URL" : "https://m.media-amazon.com/images/I/41Ms+C0NwNL._SL160_.jpg",
            "Width" : 100
          },
          "Small" : {
            "Height" : 75,
            "URL" : "https://m.media-amazon.com/images/I/41Ms+C0NwNL._SL75_.jpg",
            "Width" : 47
          }
        }
      },
      "ItemInfo" : {
        "ByLineInfo" : {
          "Contributors" : [ {
            "Locale" : "ja_JP",
            "Name" : "河合祥一郎",
            "Role" : "著",
            "RoleType" : "author"
          } ],
          "Manufacturer" : {
            "DisplayValue" : "祥伝社",
            "Label" : "Manufacturer",
            "Locale" : "ja_JP"
          }
        },
        "ProductInfo" : {
          "IsAdultProduct" : {
            "DisplayValue" : false,
            "Label" : "IsAdultProduct",
            "Locale" : "en_US"
          },
          "ReleaseDate" : {
            "DisplayValue" : "2017-04-21T00:00:00.000Z",
            "Label" : "ReleaseDate",
            "Locale" : "en_US"
          }
        },
        "Title" : {
          "DisplayValue" : "あらすじで読むシェイクスピア全作品 (祥伝社新書)",
          "Label" : "Title",
          "Locale" : "ja_JP"
        }
      }
    }, {
      "ASIN" : "B015BY1Q6Q",
      "DetailPageURL" : "https://www.amazon.co.jp/dp/B015BY1Q6Q?tag=XXXXX-22&linkCode=osi&th=1&psc=1",
      "Images" : {
        "Primary" : {
          "Large" : {
            "Height" : 500,
            "URL" : "https://m.media-amazon.com/images/I/516XD+o35gL.jpg",
            "Width" : 375
          },
          "Medium" : {
            "Height" : 160,
            "URL" : "https://m.media-amazon.com/images/I/516XD+o35gL._SL160_.jpg",
            "Width" : 120
          },
          "Small" : {
            "Height" : 75,
            "URL" : "https://m.media-amazon.com/images/I/516XD+o35gL._SL75_.jpg",
            "Width" : 56
          }
        }
      },
      "ItemInfo" : {
        "ByLineInfo" : {
          "Contributors" : [ {
            "Locale" : "ja_JP",
            "Name" : "メル・ギブソン",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "グレン・クローズ",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "アラン・ベイツ",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ポール・スコフィールド",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "フランコ・ゼフィレッリ",
            "Role" : "監督",
            "RoleType" : "director"
          }, {
            "Locale" : "ja_JP",
            "Name" : "クリストファー・デヴォア",
            "Role" : "Writer",
            "RoleType" : "writer"
          } ]
        },
        "ProductInfo" : {
          "IsAdultProduct" : {
            "DisplayValue" : false,
            "Label" : "IsAdultProduct",
            "Locale" : "en_US"
          },
          "ReleaseDate" : {
            "DisplayValue" : "2015-09-16T00:00:00.000Z",
            "Label" : "ReleaseDate",
            "Locale" : "en_US"
          }
        },
        "Title" : {
          "DisplayValue" : "ハムレット(字幕版)",
          "Label" : "Title",
          "Locale" : "ja_JP"
        }
      }
    }, {
      "ASIN" : "B07WPXRT5W",
      "DetailPageURL" : "https://www.amazon.co.jp/dp/B07WPXRT5W?tag=XXXXX-22&linkCode=osi&th=1&psc=1",
      "Images" : {
        "Primary" : {
          "Large" : {
            "Height" : 375,
            "URL" : "https://m.media-amazon.com/images/I/51CnJBKwu5L.jpg",
            "Width" : 500
          },
          "Medium" : {
            "Height" : 120,
            "URL" : "https://m.media-amazon.com/images/I/51CnJBKwu5L._SL160_.jpg",
            "Width" : 160
          },
          "Small" : {
            "Height" : 56,
            "URL" : "https://m.media-amazon.com/images/I/51CnJBKwu5L._SL75_.jpg",
            "Width" : 75
          }
        }
      },
      "ItemInfo" : {
        "ByLineInfo" : {
          "Contributors" : [ {
            "Locale" : "ja_JP",
            "Name" : "マーク・ベントン",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ジョー・ジョイナー",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "アンバー・アガ",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "リチャード・サイニー",
            "Role" : "監督",
            "RoleType" : "director"
          }, {
            "Locale" : "ja_JP",
            "Name" : "イアン・バーバー",
            "Role" : "監督",
            "RoleType" : "director"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ウィル・トロッター",
            "Role" : "プロデュース",
            "RoleType" : "producer"
          } ]
        },
        "ProductInfo" : {
          "IsAdultProduct" : {
            "DisplayValue" : false,
            "Label" : "IsAdultProduct",
            "Locale" : "en_US"
          }
        },
        "Title" : {
          "DisplayValue" : "第1話",
          "Label" : "Title",
          "Locale" : "ja_JP"
        }
      }
    } ],
    "SearchURL" : "https://www.amazon.co.jp/s?k=%E3%82%B7%E3%82%A7%E3%82%A4%E3%82%AF%E3%82%B9%E3%83%94%E3%82%A2&rh=p_n_availability%3A-1&tag=XXXXX-22&linkCode=osi",
    "TotalResultCount" : 146
  }
}
===== ASIN から商品情報を取得: リクエスト =====
{
  "PartnerType" : "Associates",
  "PartnerTag" : "XXXXX-22",
  "Resources" : [ "ItemInfo.Title", "ItemInfo.ByLineInfo" ],
  "ItemIds" : [ "4391641585", "B010EB1HR4", "B0125SPF90", "B07V52KSGT" ]
}
===== ASIN から商品情報を取得: レスポンス =====
{
  "ItemsResult" : {
    "Items" : [ {
      "ASIN" : "4391641585",
      "DetailPageURL" : "https://www.amazon.co.jp/dp/4391641585?tag=XXXXX-22&linkCode=ogi&th=1&psc=1",
      "ItemInfo" : {
        "ByLineInfo" : {
          "Contributors" : [ {
            "Locale" : "ja_JP",
            "Name" : "サンエックス",
            "Role" : "監修",
            "RoleType" : "consultant_editor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "主婦と生活社",
            "Role" : "編集",
            "RoleType" : "editor"
          } ],
          "Manufacturer" : {
            "DisplayValue" : "主婦と生活社",
            "Label" : "Manufacturer",
            "Locale" : "ja_JP"
          }
        },
        "Title" : {
          "DisplayValue" : "すみっコぐらし検定公式ガイドブック すみっコぐらし大図鑑 (生活シリーズ)",
          "Label" : "Title",
          "Locale" : "ja_JP"
        }
      }
    }, {
      "ASIN" : "B010EB1HR4",
      "DetailPageURL" : "https://www.amazon.co.jp/dp/B010EB1HR4?tag=XXXXX-22&linkCode=ogi&th=1&psc=1",
      "ItemInfo" : {
        "ByLineInfo" : {
          "Brand" : {
            "DisplayValue" : "Hostess Entertainmen",
            "Label" : "Brand",
            "Locale" : "ja_JP"
          },
          "Contributors" : [ {
            "Locale" : "ja_JP",
            "Name" : "ハロウィン",
            "Role" : "アーティスト",
            "RoleType" : "artist"
          } ],
          "Manufacturer" : {
            "DisplayValue" : "ホステス",
            "Label" : "Manufacturer",
            "Locale" : "ja_JP"
          }
        },
        "Title" : {
          "DisplayValue" : "守護神伝 第二章 <エクスパンデッド・エディション>(リマスター)",
          "Label" : "Title",
          "Locale" : "ja_JP"
        }
      }
    }, {
      "ASIN" : "B0125SPF90",
      "DetailPageURL" : "https://www.amazon.co.jp/dp/B0125SPF90?tag=XXXXX-22&linkCode=ogi&th=1&psc=1",
      "ItemInfo" : {
        "ByLineInfo" : {
          "Brand" : {
            "DisplayValue" : "ルービーズジャパン(RUBIE'S JAPAN)",
            "Label" : "Brand",
            "Locale" : "ja_JP"
          },
          "Manufacturer" : {
            "DisplayValue" : "ルービーズジャパン(RUBIE'S JAPAN)",
            "Label" : "Manufacturer",
            "Locale" : "ja_JP"
          }
        },
        "Title" : {
          "DisplayValue" : "ハロウィン ロッキング パンプキン ホームデコレーション用小物 H 130cm",
          "Label" : "Title",
          "Locale" : "ja_JP"
        }
      }
    }, {
      "ASIN" : "B07V52KSGT",
      "DetailPageURL" : "https://www.amazon.co.jp/dp/B07V52KSGT?tag=XXXXX-22&linkCode=ogi&th=1&psc=1",
      "ItemInfo" : {
        "ByLineInfo" : {
          "Contributors" : [ {
            "Locale" : "ja_JP",
            "Name" : "リアン・リース",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ジェイミー・リー・カーティス",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ウィル・パットン",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ジュディ・グリア",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ヴァージニア・ガードナー",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ジェファーソン・ホール",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "アンディ・マティチャック",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ニック・キャッスル",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ジェームス・ジュード・コートニー",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ハルク・ビルギナー",
            "Role" : "出演",
            "RoleType" : "actor"
          }, {
            "Locale" : "ja_JP",
            "Name" : "デヴィッド・ゴードン・グリーン",
            "Role" : "監督",
            "RoleType" : "director"
          }, {
            "Locale" : "ja_JP",
            "Name" : "デヴィッド・ゴードン・グリーン",
            "Role" : "Writer",
            "RoleType" : "writer"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ダニー・マクブライド",
            "Role" : "Writer",
            "RoleType" : "writer"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ジェフ・フラッドリー",
            "Role" : "Writer",
            "RoleType" : "writer"
          }, {
            "Locale" : "ja_JP",
            "Name" : "マレク・アカッド",
            "Role" : "プロデュース",
            "RoleType" : "producer"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ジェイソン・ブラム",
            "Role" : "プロデュース",
            "RoleType" : "producer"
          }, {
            "Locale" : "ja_JP",
            "Name" : "ビル・ブロック",
            "Role" : "プロデュース",
            "RoleType" : "producer"
          } ]
        },
        "Title" : {
          "DisplayValue" : "ハロウィン (字幕版)",
          "Label" : "Title",
          "Locale" : "ja_JP"
        }
      }
    } ]
  }
}

BUILD SUCCESSFUL in 13s
2 actionable tasks: 2 executed
```

## 参考資料

- [PA-API v5移行ガイド - アソシエイト・セントラル](https://affiliate.amazon.co.jp/help/node/topic/GZH32YX29UH5GACM)
- [Introduction · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/)
  - [Without SDK · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/without-sdk.html)
  - [Common Request Parameters · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html)
  - [SearchItems · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/search-items.html)
  - [GetItems · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/get-items.html)
  - [Sending a Request · Product Advertising API 5\.0](https://webservices.amazon.com/paapi5/documentation/sending-request.html)
- [署名バージョン 4 署名プロセス \- AWS 全般のリファレンス](https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html)
- [一般的なリクエストヘッダー \- Amazon S3 Glacier](https://docs.aws.amazon.com/ja_jp/amazonglacier/latest/dev/api-common-request-headers.html)