[WinRT] ストアアプリで Amazon の商品検索をする方法

Kindle Launcher を作るときに、Amazon から商品検索をしたかったので、これに嵌りました。サンプルコード自体は Amazon で提供されているものの、WinRT(Windows ストアアプリ)では動かなかったんですよね。

■アフリエイトキーを取得する

http://affiliate.amazon.co.jp/

上記からアフリエイトキーを作成します。AWS とは違うので注意が必要(暫く使っていなかったので探した探した)。まあ、商品検索 API を使いたいのにクレジットカード登録とかは変な話なので。

API キーの取得等は以下を参照してください。アプリでは「アクセスキー」と「シークレットキー」の2つを使います。

Access Key IDとSecret Access Keyの取得 – Amazon Web サービス
http://www.ajaxtower.jp/ecs/pre/index1.html

■SignedRequestHelper.cs を修正する

C# では、アフリエイト API を使うときにヘルパークラスを使うと便利です。WPF や Windows Phone の場合は、提供されている SignedRequestHelper のままでいいのですが、WinRT の場合はちょっと違います。

[C#] Amazon Product Advertising API の利用(REST サンプルコードの修正) | プログラミング生放送
http://pronama.azurewebsites.net/2014/05/23/csharp-amazon-product-advertising-api-rest-sample-code-modified/

↓こんな風に修正してください。

SignedRequestHelper.cs for WinRT
https://gist.github.com/moonmile/16c950174d0c276cc2c3

System.Security.Cryptography じゃなくて、Windows.Security.Cryptography.Core を使うってのがミソですね。下に元ネタがあります。

c# – Working with hmacsha256 in windows store app – Stack Overflow
http://stackoverflow.com/questions/13293420/working-with-hmacsha256-in-windows-store-app

SHA で MAC キーを使うところが少し違うだけで、ヘルパークラスの使い方は変わりません。
おそらく、WinRT での違いがあって、ストア版の Amazon アプリが増えないのかもしれません。セキュリティ回りの名前空間とクラスが再整理(改悪?)されているので、いままでのコードが使えないのが面倒です。互換クラスを作ってしまえばよいような気もするのですが。

■指定したキーワードで商品情報を検索する

SignedRequestHelper を使って Amazon の商品を検索しているところがこんな感じです。

private const string MY_AWS_ACCESS_KEY_ID = "YOUR_AWS_ACCESS_KEY_ID";
private const string MY_AWS_SECRET_KEY = "YOUR_AWS_SECRET_KEY";
private const string DESTINATION = "ecs.amazonaws.jp";

/// <summary>
/// 検索ボタンをクリック
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private async void OnButtonItemSearch(object sender, RoutedEventArgs e)
{
    var helper = new SignedRequestHelper(MY_AWS_ACCESS_KEY_ID, MY_AWS_SECRET_KEY, DESTINATION, &quot;mycompany-22&quot;);
    var param = new Dictionary<string, String>();
    param[&quot;Service&quot;] = &quot;AWSECommerceService&quot;;
    param[&quot;Version&quot;] = &quot;2011-08-01&quot;;
    param[&quot;Operation&quot;] = &quot;ItemSearch&quot;;
    param[&quot;Keywords&quot;] = this.vm.Title + &quot; Kindle&quot;;
    param[&quot;Author&quot;] = this.vm.Author;
    param[&quot;SearchIndex&quot;] = &quot;All&quot;; // &quot;Books&quot;;
    param[&quot;ResponseGroup&quot;] = &quot;Large&quot;;

    try
    {
        var requestUrl = helper.Sign(param);
        var request = new System.Net.Http.HttpClient();
        var res = await request.GetStringAsync(new Uri(requestUrl));
        // Debug.WriteLine(res);

        var doc = ExDocument.LoadXml(res);
        int TotalPages = doc * &quot;TotalPages&quot;;
        int TotalResults = doc * &quot;TotalResults&quot;;
        vm.Total = TotalResults;

        int count = 0;
        vm.Books.Clear();

        for (int i = 1; i <= TotalPages; i++)
        {
            param[&quot;ItemPage&quot;] = i.ToString();

            await Task.Delay(1000); // ちょっとだけ待つ
            var reqUrl = helper.Sign(param);
            res = await request.GetStringAsync(new Uri(reqUrl));
            doc = ExDocument.LoadXml(res);
            var ASINs = doc * &quot;Item&quot; * &quot;ASIN&quot;;
            foreach (var it in ASINs)
            {
                count++;
                var asin = it.Value;
                var item = it.Parent;
                var format = item * &quot;ItemAttributes&quot; * &quot;Format&quot;;

                if (format == &quot;Kindle本&quot;)
                {
                    string title = item * &quot;ItemAttributes&quot; * &quot;Title&quot;;
                    string iconurl = item * &quot;LargeImage&quot; * &quot;URL&quot;;
                    Debug.WriteLine(&quot;{0} ASIN:{1} Title:{2} img:{3} &quot;, count, asin, title, iconurl);

                    vm.Books.Add(new Book { ASIN = asin, Title = title, IconUrl = iconurl });
                    vm.TotalKindle = vm.Books.Count();
                    vm.TotalStr = string.Format(&quot;{0}/{1}&quot;, vm.TotalKindle, vm.Total);
                }
            }
        }
    }
    catch (Exception ex)
    {
        // MessageBox.Show(&quot;API制限に達しました。暫く経ってから実行してくださいn&quot; + ex.Message);
        Debug.WriteLine(ex.Message);
    }
}

ItemSearch で戻される検索はページ単位で返ってくるのと、検索数が制限されています。サイトでは結構な数で検索できるのですが、API の場合は、4000件(400ページ)程度のようです。
この検索では、商品画像(本の表紙)を取るために Large にして商品データを取っています。まあ、Kindle Launcher の書籍検索がちょっと遅いのはそれが原因でもあります。ResponseGroup を変更するともうちょっとデータ量を絞れるでしょう。
SearchIndex を Books だけにすると「和書」だけになるので、All にしています。洋書も検索結果に入れたかったのです。Keywords に Kindle を含めているのは「Kindle本」に絞るためです。こうすると、おそらく Kindle 対象の本だけが検索できるはずです。そして、もういちど取得したデータを改めて」「Kindle本」で検索しています。

データは XML 形式で取得できるので適当にパースします。ここでは自前の ExDoc を使ってパースをしています。

https://github.com/moonmile/ExDoc/

にあるのは、Windows デスクトップ版なので、近いうちに PCL 版をアップしておきます。

XML データからクラスを作成する VS のアドイン(だっけ?)もあるので、それを使うのもよいでしょう。

■検索した商品を表示する

表示自体は GridView でやっています。
そのままのテンプレートでは使いづらいので「GridView.ItemTemplate」で自前テンプレートを作ります。

<!-- 水平スクロール グリッド -->
<GridView
    Grid.Column=&quot;1&quot; Grid.Row=&quot;1&quot;
    x:Name=&quot;itemGridView&quot;
    ItemsSource=&quot;{Binding Books}&quot;
    TabIndex=&quot;1&quot;
    Padding=&quot;10&quot;
    SelectionMode=&quot;{Binding SelectMode}&quot;
    IsSwipeEnabled=&quot;false&quot;
    Tapped=&quot;itemGridView_Tapped&quot;>
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid HorizontalAlignment=&quot;Left&quot; Width=&quot;250&quot; Height=&quot;250&quot;>
                <Border Background=&quot;{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}&quot;>
                    <Image Source=&quot;{Binding IconUrl}&quot; Stretch=&quot;UniformToFill&quot; />
                </Border>
                <StackPanel VerticalAlignment=&quot;Bottom&quot; Orientation=&quot;Vertical&quot;
                    Background=&quot;{StaticResource ListViewItemOverlayBackgroundThemeBrush}&quot;>
                    <TextBlock Text=&quot;{Binding ASIN}&quot;
                        Height=&quot;18&quot;
                        Foreground=&quot;{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}&quot;
                        TextWrapping=&quot;NoWrap&quot; Margin=&quot;3&quot;/>
                    <TextBlock Text=&quot;{Binding Title}&quot;
                        Foreground=&quot;{StaticResource ListViewItemOverlayForegroundThemeBrush}&quot;
                        Height=&quot;30&quot; Margin=&quot;3&quot;
                        TextWrapping=&quot;NoWrap&quot;/>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

IconUrl とか ASIN とかをバインドしておけば、自動的に表示されますね。

こんな風に表示されます。

カテゴリー: C#, WinRT パーマリンク