Amazon Product Advertising API を利用して ASIN ISBNから商品名を取得する - C#

Amazon Product Advertising API を利用して ASINやISBNから商品名を取得するコードを紹介します。

概要

Amazon Product Advertising APIを呼び出して、ASINやISBNから商品名を取得します。
ASINやISBNから商品名を取得する場合は、リクエストの"Operation"パラメータに"ItemLookup"を指定し、"ItemId"パラメータに取得したいASIN ISBNを設定します。"ResponseGroup"パラメーターに"Small"を指定します。

事前準備

Amazon Product Advertising API のサインアップとアクセスキーの取得

こちらの記事を参照して、Amazon Product Advertising API にサインアップし、アクセスキーIDとシークレットアクセスキーを取得します。

SignedRequestHelper.cs の取得

下記のURLからサンプルコードを取得し、SignedRequestHelper.cs ファイルを入手します。
https://aws.amazon.com/code/Product-Advertising-API/2480

プログラム

UI

下図のUIを作成します。TextBox2つとButtonを1つ配置します。

コード

下記のコードを記述します。
(ここに アクセスキーID を入力します。)の場所に、取得したアクセスキーIDを入力します。(ここに シークレットアクセスキー を入力します。)の場所に、取得したシークレットアクセスキーを入力します。
FormMain.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
using System.Net;

namespace SimpleLookup
{
  public partial class FormMain : Form
  {
    private const string MY_AWS_ACCESS_KEY_ID = "(ここに アクセスキーID を入力します。)";
    private const string MY_AWS_SECRET_KEY = "(ここに シークレットアクセスキー を入力します。)";
    private const string DESTINATION = "ecs.amazonaws.jp";
    private const string ASSOCIATE_TAG = "YOUR_ASSOCIATE_TAG"; //今回は未指定でOKです。


    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      SignedRequestHelper helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION, ASSOCIATE_TAG);
      string asin = textBox2.Text;

      IDictionary<string, string> request = new Dictionary<string, String>();
      request["Service"] = "AWSECommerceService";
      request["Operation"] = "ItemLookup";
      request["ItemId"] = asin;
      request["ResponseGroup"] = "Small";
      string requestUrl = helper.Sign(request);

      GetResponse(requestUrl);

    }

    public void GetResponse(string url)
    {
      HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
      HttpWebResponse response = (HttpWebResponse)request.GetResponse();

      XmlDocument xmlDocument = new XmlDocument();
      xmlDocument.Load(response.GetResponseStream());

      XmlNamespaceManager xmlNsManager = new XmlNamespaceManager(xmlDocument.NameTable);
      xmlNsManager.AddNamespace("ns", "http://webservices.amazon.com/AWSECommerceService/2011-08-01");

      XmlNodeList nodeList = xmlDocument.SelectNodes("/ns:ItemLookupResponse/ns:Items/ns:Item/ns:ItemAttributes/ns:Title", xmlNsManager);
      if (0 < nodeList.Count) {
        textBox1.Text += nodeList[0].InnerText;
      }
    }
  }
}
サンプルコードから取得した、SignedRequestHelper.cs をプロジェクトに追加し、変更します。
SignedRequestHelper.cs
/**********************************************************************************************
 * Copyright 2009 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file 
 * except in compliance with the License. A copy of the License is located at
 *
 *       http://aws.amazon.com/apache2.0/
 *
 * or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under the License. 
 *
 * ********************************************************************************************
 *
 *  Amazon Product Advertising API
 *  Signed Requests Sample Code
 *
 *  API Version: 2009-03-31
 *
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Security.Cryptography;

namespace SimpleLookup
{
  class SignedRequestHelper
  {
    private string endPoint;
    private string akid;
    private string associateTag;
    private byte[] secret;
    private HMAC signer;

    private const string REQUEST_URI = "/onca/xml";
    private const string REQUEST_METHOD = "GET";

    /*
     * Use this constructor to create the object. The AWS credentials are available on
     * http://aws.amazon.com
     * 
     * The destination is the service end-point for your application:
     *  US: ecs.amazonaws.com
     *  JP: ecs.amazonaws.jp
     *  UK: ecs.amazonaws.co.uk
     *  DE: ecs.amazonaws.de
     *  FR: ecs.amazonaws.fr
     *  CA: ecs.amazonaws.ca
     */
    //public SignedRequestHelper(string awsAccessKeyId, string awsSecretKey, string destination)
    public SignedRequestHelper(string awsAccessKeyId, string awsSecretKey, string destination, string associateTag)
    {
      this.endPoint = destination.ToLower();
      this.akid = awsAccessKeyId;
      this.secret = Encoding.UTF8.GetBytes(awsSecretKey);
      this.associateTag = associateTag;
      this.signer = new HMACSHA256(this.secret);
    }

    /*
     * Sign a request in the form of a Dictionary of name-value pairs.
     * 
     * This method returns a complete URL to use. Modifying the returned URL
     * in any way invalidates the signature and Amazon will reject the requests.
     */
    public string Sign(IDictionary<string, string> request)
    {
      // Use a SortedDictionary to get the parameters in naturual byte order, as
      // required by AWS.
      ParamComparer pc = new ParamComparer();
      SortedDictionary<string, string> sortedMap = new SortedDictionary<string, string>(request, pc);

      // Add the AWSAccessKeyId and Timestamp to the requests.
      sortedMap["AWSAccessKeyId"] = this.akid;
      sortedMap["Timestamp"] = this.GetTimestamp();
      sortedMap["AssociateTag"] = this.associateTag;

      // Get the canonical query string
      string canonicalQS = this.ConstructCanonicalQueryString(sortedMap);

      // Derive the bytes needs to be signed.
      StringBuilder builder = new StringBuilder();
      builder.Append(REQUEST_METHOD)
          .Append("\n")
          .Append(this.endPoint)
          .Append("\n")
          .Append(REQUEST_URI)
          .Append("\n")
          .Append(canonicalQS);

      string stringToSign = builder.ToString();
      byte[] toSign = Encoding.UTF8.GetBytes(stringToSign);

      // Compute the signature and convert to Base64.
      byte[] sigBytes = signer.ComputeHash(toSign);
      string signature = Convert.ToBase64String(sigBytes);

      // now construct the complete URL and return to caller.
      StringBuilder qsBuilder = new StringBuilder();
      qsBuilder.Append("http://")
          .Append(this.endPoint)
          .Append(REQUEST_URI)
          .Append("?")
          .Append(canonicalQS)
          .Append("&Signature=")
          .Append(this.PercentEncodeRfc3986(signature));

      return qsBuilder.ToString();
    }

    /*
     * Sign a request in the form of a query string.
     * 
     * This method returns a complete URL to use. Modifying the returned URL
     * in any way invalidates the signature and Amazon will reject the requests.
     */
    public string Sign(string queryString)
    {
      IDictionary<string, string> request = this.CreateDictionary(queryString);
      return this.Sign(request);
    }

    /*
     * Current time in IS0 8601 format as required by Amazon
     */
    private string GetTimestamp()
    {
      DateTime currentTime = DateTime.UtcNow;
      string timestamp = currentTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
      return timestamp;
    }

    /*
     * Percent-encode (URL Encode) according to RFC 3986 as required by Amazon.
     * 
     * This is necessary because .NET's HttpUtility.UrlEncode does not encode
     * according to the above standard. Also, .NET returns lower-case encoding
     * by default and Amazon requires upper-case encoding.
     */
    private string PercentEncodeRfc3986(string str)
    {
      str = HttpUtility.UrlEncode(str, System.Text.Encoding.UTF8);
      str = str.Replace("'", "%27").Replace("(", "%28").Replace(")", "%29").Replace("*", "%2A").Replace("!", "%21").Replace("%7e", "~").Replace("+", "%20");

      StringBuilder sbuilder = new StringBuilder(str);
      for (int i = 0; i < sbuilder.Length; i++) {
        if (sbuilder[i] == '%') {
          if (Char.IsLetter(sbuilder[i + 1]) || Char.IsLetter(sbuilder[i + 2])) {
            sbuilder[i + 1] = Char.ToUpper(sbuilder[i + 1]);
            sbuilder[i + 2] = Char.ToUpper(sbuilder[i + 2]);
          }
        }
      }
      return sbuilder.ToString();
    }

    /*
     * Convert a query string to corresponding dictionary of name-value pairs.
     */
    private IDictionary<string, string> CreateDictionary(string queryString)
    {
      Dictionary<string, string> map = new Dictionary<string, string>();

      string[] requestParams = queryString.Split('&');

      for (int i = 0; i < requestParams.Length; i++) {
        if (requestParams[i].Length < 1) {
          continue;
        }

        char[] sep = { '=' };
        string[] param = requestParams[i].Split(sep, 2);
        for (int j = 0; j < param.Length; j++) {
          param[j] = HttpUtility.UrlDecode(param[j], System.Text.Encoding.UTF8);
        }
        switch (param.Length) {
          case 1: {
              if (requestParams[i].Length >= 1) {
                if (requestParams[i].ToCharArray()[0] == '=') {
                  map[""] = param[0];
                }
                else {
                  map[param[0]] = "";
                }
              }
              break;
            }
          case 2: {
              if (!string.IsNullOrEmpty(param[0])) {
                map[param[0]] = param[1];
              }
            }
            break;
        }
      }

      return map;
    }

    /*
     * Consttuct the canonical query string from the sorted parameter map.
     */
    private string ConstructCanonicalQueryString(SortedDictionary<string, string> sortedParamMap)
    {
      StringBuilder builder = new StringBuilder();

      if (sortedParamMap.Count == 0) {
        builder.Append("");
        return builder.ToString();
      }

      foreach (KeyValuePair<string, string> kvp in sortedParamMap) {
        builder.Append(this.PercentEncodeRfc3986(kvp.Key));
        builder.Append("=");
        builder.Append(this.PercentEncodeRfc3986(kvp.Value));
        builder.Append("&");
      }
      string canonicalString = builder.ToString();
      canonicalString = canonicalString.Substring(0, canonicalString.Length - 1);
      return canonicalString;
    }
  }

  /*
   * To help the SortedDictionary order the name-value pairs in the correct way.
   */
  class ParamComparer : IComparer<string>
  {
    public int Compare(string p1, string p2)
    {
      return string.CompareOrdinal(p1, p2);
    }
  }
}

解説

button1_Click

下記コードでSignedRequestHelper のインスタンスを作成します。
  SignedRequestHelper helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION, ASSOCIATE_TAG);

テキストボックスからASIN/ISBN-10番号を取得します。
  string asin = textBox2.Text;

リクエストパラメータの準備をします。"Operation"には"ItemLookup"を指定し、"ItemId"にはテキストボックスに入力されたASIN/ISBN番号、"ResponseGroup"には"Small"を指定します。
  IDictionary<string, string> request = new Dictionary<string, String>();
  request["Service"] = "AWSECommerceService";
  request["Operation"] = "ItemLookup";
  request["ItemId"] = asin;
  request["ResponseGroup"] = "Small";
  string requestUrl = helper.Sign(request);

APIへのリクエストと戻り値のXMLのパーシングを実行します。
  GetResponse(requestUrl);

GetResponse

下記コードでHTTPアクセスのリクエストを実行し、レスポンスを受け取ります。動作の詳細はこちらの記事を参照してください。
  HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
  HttpWebResponse response = (HttpWebResponse)request.GetResponse();

HttpWebResponseのGetResponseStream()メソッドで取得できる、ストリームをXmlDocumentのLoadメソッドに与え、XMLのパーシングを実行します。今回の例では、XMLの要素の取得には、XPathを利用しています。XPathの動作の詳細はこちらの記事を参照してください。(Amazon Product Advertising API の返却XMLはネームスペースが設定されています。)
取得した "/ns:ItemLookupResponse/ns:Items/ns:Item/ns:ItemAttributes/ns:Title" タグ内の商品名をテキストボックスに表示します。

  XmlDocument xmlDocument = new XmlDocument();
  xmlDocument.Load(response.GetResponseStream());

  XmlNamespaceManager xmlNsManager = new XmlNamespaceManager(xmlDocument.NameTable);
  xmlNsManager.AddNamespace("ns", "http://webservices.amazon.com/AWSECommerceService/2011-08-01");

  XmlNodeList nodeList = xmlDocument.SelectNodes("/ns:ItemLookupResponse/ns:Items/ns:Item/ns:ItemAttributes/ns:Title", xmlNsManager);
  if (0 < nodeList.Count) {
    textBox1.Text += nodeList[0].InnerText;
  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


上部のTextBox(TextBox2)にASINを入力し、[button1]をクリックします。


下部のTextBoxに商品名が表示されます。


別のASIN番号を入力しても動作します。


書籍の場合は、"ISBN-10"の番号を入力します。


参考URL

http://pronama.azurewebsites.net/2014/05/23/csharp-amazon-product-advertising-api-rest-sample-code-modified/
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2018-02-13
作成日: 2016-05-10
iPentec all rights reserverd.