HugoでAmazonのブログカード作成

2020-06-22 Hugo ブログカード

目次

やりたいこと

  • HugoでAmazonのリンクをブログカードで表示したい
  • 商品画像込で表示したい
  • レスポンシブなデザインにしたい(スマートフォンで見たときに画像サイズ等を変更したい)

作成したAmazonブログカードの例:

やったこと

1. レスポンシブなブログカードを作成可能にする

以前の記事で,Hugoでレスポンシブなブログカードを作成する方法を書きました. 以前の記事の方法をベースに,この記事ではAmazonの商品リンクを作成します.

上記の方法でAmazon商品のリンクを作成した場合の問題として,商品画像が表示されないことがありました. 原因は,

  • 上記の方法では,画像はOGP情報(HTMLのヘッダ部分の ‘og:image’)から抽出している
  • Amazonの商品ページには,OGP情報やog:imageの設定がされていない

ということだと思います.

2. Amazon PA-API (v5)を使用可能にする

今回は,Amazonの商品画像を取得するために,PA-API (v5)を使用します(理由の補足は後述).

PA-APIの使用方法については,下記のサイトを参考にしました. 前回の記事では,OGP情報取得のサーバをNodeで書いたので,PA-API情報取得もNodeで一緒に行うことにしました.

上のサイトの通りでほぼ問題ないのですが,PA-APIについて,「429エラーになる」という問題が報告されています.

HTTP Status: 429: Too Many Requests
The request was denied due to request throttling. Please verify the number of requests made per second to the Amazon Product Advertising API.

私の場合も(最初から)このエラーが出ました. 数時間放置して再挑戦すると,通るようになりました. 認証キーを取得してからすぐにAPIを叩くと,上記のエラーが出るのかもしれません.

3. OGPを取得するサーバに,Amazonリンク用の機能を追加する

前回の記事のapp.jsを下記のように変更しました. 3行目でインポートしている'./src/indexは,PA-APIのSDK(node)内にあるソースのディレクトリです. Node超初心者としては,非同期関数api.getItems()から取得した情報をexpressResponseに渡す方法に苦労しました. 最終的には,クロージャを使う方法を参考にすることで,なんとか解決しました.

また,現時点ではまだ例外処理等は十分に書けていません.

//app.js
var express = require('express');
var client = require('cheerio-httpcli');
var ProductAdvertisingAPIv1 = require('./src/index');

var app = express();

function parseResponse(itemsResponseList) {
  var mappedResponse = {};
  for (var i in itemsResponseList) {
    if (itemsResponseList.hasOwnProperty(i)) {
      mappedResponse[itemsResponseList[i]['ASIN']] = itemsResponseList[i];
    }
  }
  return mappedResponse;
}

// PA-APIの応答(data)から画像URLを抽出し,応答する
function resolver(result,expressResponse,getItemsRequest){
    return function(data) {
      var getItemsResponse = ProductAdvertisingAPIv1.GetItemsResponse.constructFromObject(data);
      var response_list = parseResponse(getItemsResponse['ItemsResult']['Items']);
      var itemId = getItemsRequest['ItemIds'][0];
      var item = response_list[itemId];
      img = item["Images"]["Primary"]['Medium']['URL'];
      url = item['DetailPageURL'];
      result.image = img;
      result.url = url;
      expressResponse.json(result);
    }
}

app.get("/getogp", (expressRequest, expressResponse, expressNext) => {
    const url = expressRequest.query.url;
    client.fetch(url, (err, $, res, body) => {
        if (err) {
            expressNext(err)
            return;
        }

        var docInfo = $.documentInfo();
        var url_redirected =  docInfo.url;
        // console.log(url_redirected);

        var result = {
            exists: false,
            title: "",
            description: "",
            url: "",
            image: "",
            site_name: "",
            type: "",
        }

        const ogTitleQuery = $("meta[property='og:title']");
        // console.log(ogTitleQuery)
        // console.log(ogTitleQuery.length)        

        if (ogTitleQuery.length > 0) {
            result.exists = true;
            result.title = $("meta[property='og:title']").attr("content");
            result.description = $("meta[property='og:description']").attr("content");
            result.url = $("meta[property='og:url']").attr("content");
            result.image = $("meta[property='og:image']").attr("content");
            result.site_name = $("meta[property='og:site_name']").attr("content");
            result.type = $("meta[property='og:type']").attr("content");
            expressResponse.json(result);                        

        } else if (url_redirected.indexOf('https://www.amazon.co.jp/') === 0) {
        	 // Amazon用機能
            result.exists = true;
            result.title = $("meta[name='title']").attr("content");
            result.description = $("meta[name='description']").attr("content");
            result.site_name = 'https://www.amazon.co.jp/';

            const url_split = url_redirected.split('dp/')[1].split('/')[0];
            const asin = url_split;

            var defaultClient = ProductAdvertisingAPIv1.ApiClient.instance;
            defaultClient.accessKey = '(PA-APIのアクセスキー)';            
            defaultClient.secretKey = '(PA-APIの秘密鍵)';            
            defaultClient.host = "webservices.amazon.co.jp";
            defaultClient.region = 'us-west-2';
            var api = new ProductAdvertisingAPIv1.DefaultApi();
            var getItemsRequest = new ProductAdvertisingAPIv1.GetItemsRequest();
            getItemsRequest['PartnerTag'] = '(アソシエイトID)';
            getItemsRequest['PartnerType'] = 'Associates';
            getItemsRequest['ItemIds'] = [asin];
            getItemsRequest['Condition'] = 'New';
            getItemsRequest['Resources'] = ['Images.Primary.Medium','Offers.Listings.Price']; //, 'ItemInfo.Title', 'Offers.Listings.Price'];

            api.getItems(getItemsRequest).then(
                    resolver(result,expressResponse,getItemsRequest));

        }else {
            result.title = $("head title").text();
            result.description = $("meta[name='description']").attr("content");
            expressResponse.json(result);            
        }

        // expressResponse.json(result);
    });

})

app.listen(6060, () => console.log('Listening on port 6060'));

上記のアプリをhttp://localhost:6060に立ち上げ,shortcodeからgetJSON http://localhost:6060?url=(Amazonの商品URL)とします. これにより,商品の情報を持つjsonをshortcode内で取得できます.

他に検討した方法/断念した理由

ASINから画像URLを取得する

画像が取得できない商品がありました.

ASINから画像URLを取得する方法では,例えば下記のURLを商品画像として使用します.

https://images-na.ssl-images-amazon.com/images/P/${ASIN}.09.MZZZZZZZ.jpg

しかし,リンク先にも記載されている通り,一部の商品ではこの方法では画像が取得できません.

一部の商品では、上記のURLから画像を取得できない場合があります。画像が取得できない商品については、“Amazon Product Advertising API"を利用して画像を取得する必要があります。

既存のwebアプリを使用してASINから商品情報を取得する

例えば,このブログでは, amazon-product-jsonというWebアプリを使用してAmazonの商品を表示する方法が紹介されています. しかし,ブログの執筆が2017年であり, PA-APIのv5に対応したものではないと思われるため, 今回新たにv5に対応した方法を紹介しました.

作成したリンクの例

Amazonから商品をランダムに選択してみました.

今後の課題

  • app.jsを整理する
comments powered by Disqus