まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:amazon

以前に PHP で同様のコードを作ったことがあった(知る人ぞ知る)のですが、訳あって Node.js で動かしたくなって移植に挑戦しました。

2017120600


Amazon の Product Advertising API はどちらかというと「AWS ではない Amazon の API」で、エンジニアには馴染みのない人がいるかもしれませんが、アフィリエイトのための商品情報を集める場合に使う API です。こちらで紹介している方法を使うなどして、事前にアクセスキーとアクセスシークレットを取得した上で利用します:
http://dotnsf.blog.jp/archives/1064227473.html


ここで取得したアクセスキーとアクセスシークレットを(7、8行目に)使い、以下のような Node.js コード(amazon.js)を作りました:
//. amazon.js
var crypto = require( 'crypto' );
var request = require( 'request' );
var urlencode = require( 'urlencode' );
var xml2js = require( 'xml2js' );

var aws_key = 'aws_key'; //. アクセスキー
var aws_secret = 'aws_secret'; //. アクセスシークレット

var node = '52905051'; //. スキンケアのカテゴリコード


var request_url = 'http://ecs.amazonaws.jp/onca/xml?';
var local_dt = new Date();
var dt = local_dt.toISOString();  //. GMT timestamp

//. パラメータ
var params = 'AWSAccessKeyId=' + aws_key + '&BrowseNode=' + node;
params += ( '&MaximumPrice=1000&MinimumPrice=1&Operation=ItemSearch&ResponseGroup=ItemAttributes%2CSmall%2CImages&SearchIndex=Beauty&Service=AWSECommerceService&Timestamp=' + urlencode( dt ) + '&Version=2009-01-06' );

//. 署名
var str = 'GET\necs.amazonaws.jp\n/onca/xml\n' + params;
var hash = crypto.createHmac( 'sha256', aws_secret );
hash.update( str );
var hashed_str = hash.digest( 'base64' );

request_url += ( params + '&Signature=' + urlencode( hashed_str ) );

var options = { url: request_url, headers: { 'Host': 'ecs.amazonaws.jp' }, method: 'GET' };

request( options, ( err, res, body ) => {
  if( err ){
    console.log( 'error: ' );
    console.log( err );
  }else{
    //. XML で送られてくる結果を JSON 化して解析
    xml2js.parseString( body, ( err0, xml ) => {
      if( err0 ){
        console.log( err0 );
      }else{
        if( xml && xml.ItemSearchResponse && xml.ItemSearchResponse.Items ){
          var Items = xml.ItemSearchResponse.Items[0];

          var totalpages = Items.TotalPages[0];
          for( var idx = 0; idx < Items.Item.length; idx ++ ){
            var Item = Items.Item[idx];

            var image_url = '', manufacturer = '', brand = '', title = '', listprice = '', ean = '', asin = '';

            try{
              image_url = Item.MediumImage[0].URL[0];
            }catch( e ){
            }
            try{
              manufacturer = Item.ItemAttributes[0].Manufacturer[0];
            }catch( e ){
            }
            try{
              brand = Item.ItemAttributes[0].Brand[0];
            }catch( e ){
            }
            try{
              title = Item.ItemAttributes[0].Title[0];
            }catch( e ){
            }
            try{
              listprice = Item.ItemAttributes[0].ListPrice[0].Amount[0];
            }catch( e ){
            }
            try{
              ean = Item.ItemAttributes[0].EAN[0];
            }catch( e ){
            }
            try{
              asin = Item.ASIN[0];
            }catch( e ){
            }

            if( listprice == '' ){
              listprice = 0;
            }

            console.log( 'ean = ' + ean + ', title = ' + title + ', image_url = ' + image_url + ', manufacturer = ' + manufacturer + ', brand = ' + brand + ', listprice = ' + listprice + ', asin = ' + asin );
          }
        }
      }
    });
  }
});

10行目に検索する商品のカテゴリーブラウズノードを指定します。上記のデフォルト状態では「スキンケア製品」を意味する '52905051' が指定されていますが、変更することもできます。その場合は以下のサイトからブラウズノード一覧をダウンロード&参照し、希望のカテゴリーのノード番号に書き換えて使ってください:
https://affiliate.amazon.co.jp/help/topic/t100


コードの内容はカテゴリや価格帯(今回の例では1円以上1000円未満)を含めた URL に署名付きのパラメータを付けてアクセスし、XML で取得した結果を JSON に変換して取り出す、というシンプルなものです。署名の方法について Node.js でのサンプルがなかったので、別の言語向けに書かれたものを移植して作ってみました。なお、この API では1回の実行で条件を満たす商品を最大10個同時に取得できます。今回のコードでは指定したカテゴリーの商品を10個だけ取得するようになっています(ページング非対応です)。

このコード(amazon.js)を node コマンドで実行すると、以下のように指定したカテゴリーの商品情報を取得して表示します:
$ node amazon.js

ean = 3253581244760, title = ロクシタン(L'OCCITANE) ボンメールソープ ヴァーベナ 100g, image_url = https://images-fe.ssl-images-amazon.com/images/I/41QyQaO75HL._SL160_.jpg, manufacturer = ロクシタンジャポン, brand = ロクシタン(L'OCCITANE), listprice = 864, asin = B00AJFBDKM
ean = 3253581244777, title = ロクシタン(L'OCCITANE) ボンメールソープ ラベンダー 100g, image_url = https://images-fe.ssl-images-amazon.com/images/I/412BXX-d86L._SL160_.jpg, manufacturer = ロクシタンジャポン, brand = ロクシタン(L'OCCITANE), listprice = 864, asin = B00BMW80M0
  :
  :

今回紹介した Amazon Product Advertising API は(特にアフィリエイトサイトの管理を自動化する場合などに)非常に有用なのですが、サイトトップによると「セルフサービスでのご利用を前提としており、Amazon での技術的なサポートは現在行っておりません」とのことです。ハードル高め:
2017120601


自分は「ねっぴ」(http://neppi.co/)の運用を通じて 2014 年くらいから本格的に使っていて、最新情報を追いかけながらとかいうわけではないのですが、ノウハウなども分かってきた所もあるので、こういった場を通じて(今回みたいな形で)少しずつコミュニティに還元できればと思っています。


Amazon で商品を検索したり、その商品へのリンクを作成する作業を自動化するには Amazon Product Advertising API を使います。その際にアクセスキーとシークレットアクセスキーが必要になります。 これらの取得方法を紹介します。

前提として、AWS のアカウントが必要です。こちらから作成しておきましょう:
https://aws.amazon.com/jp/


API のアクセスキーとシークレットキーを取得するには、まず AWS にログインし、名前の部分をクリックして「セキュリティ認証情報」を選択します:
2017020801


AWS Identity and Access Management ユーザーがうんたらかんたら・・・というメッセージが表示されますが、今は無視して×をクリック:
2017020802


「セキュリティ認証情報」のページで、「アクセスキー(アクセスキーIDとシークレットアクセスキー)」と書かれた箇所をクリック&展開して、「新しいアクセスキーの作成」ボタンをクリックします(同時には2つまでのアクセスキーを所持できるらしいです):
2017020803


アクセスキーが作成されます。その中身を表示すると、アクセスキーID &シークレットアクセスキーがそれぞれ表示されます。 これで目的の情報が取得できました。なおシークレットアクセスキーはこの1回しか表示されません確認する唯一の機会です。忘れてしまった場合は1つ前の画面で一度削除して、再度アクセスキーを作成し、その際に確認する必要があるので注意してください:
2017020804


この API の使い方については公式ドキュメントを参照してください:
https://affiliate.amazon.co.jp/gp/advertising/api/detail/main.html


この情報や API を使ってアフィリエイトを行おうとすると、別途アソシエイト ID も合わせて取得する必要がありますが、その取得方法についてはこちらを参照ください:
Amazon アソシエイト ID の取得手順


主にブログなどを使ったアマゾンアフィリエイトを行う場合、アマゾンアソシエイトのアカウントが必要です。またアフィリエイトリンクを動的に(プログラムで)生成しようとすると、これらに加えて「アソシエイトID」と呼ばれる ID を取得する必要があります。以下にそれらを取得する手順を紹介しますが、アマゾンのアカウント自体は取得済みであるとします。


まずはAmazon アソシエイトページに移動し、「無料アカウント作成」と書かれたボタンをクリックします:
https://affiliate.amazon.co.jp/

2016102801


自分のアマゾン ID とパスワードを指定してサインインします:
2016102901


次にアカウント情報を指定します。個人か法人かなどの質問がありますが、迷うような箇所は特にないと思われます。一通り入力後に「次:Web サイト情報」へ移動します:
2016102902


ここからが肝になる部分です。まずはアソシエイト ID と呼ばれるユニークな ID を指定します(他の人が既に使っている文字列は指定できません)。なお、このアソシエイト ID に "-22" を付加したものが正式なアソシエイト ID になります。

次にアフィリエイトを行うことになるブログや SNS などのウェブサイトの URL を指定します。ここで指定されたサイトのリンクから送客を行ったものがアフィリエイト対象となります。そして、そのウェブサイトの説明や紹介予定の商品についての説明を記載します:
2016102903


その下ではウェブサイトのカテゴリをおおまかに指定します。また収益化のために使っている方法についてを指定します。
2016102904


そのままアンケートに答えていきます。最後に契約条件を確認したかどうかのチェックボックスを ON にします:
2016102905


そしてボックス内に表示されているものと同じ文字列を入力して「完了」ボタンをクリックします。これで申し込みは完了です:
2016102906


数時間から数日後、ここで指定した内容に関する審査結果がメールで届きます。審査が通った場合はアソシエイト ID(自分が指定したもの + "-22")も記載されているはずです:
2016103001



これでアマゾンのアソシエイト ID を取得できました。

ウェブブラウザである Chrome の機能を拡張する Chrome extension を作ってみます。今回は試しに
 Chrome でアマゾンの商品ページを見ている時に、そのページ内の ASIN コードを取り出して表示する
というシンプルな機能を実装してみます。

この Chrome 拡張機能の開発方法に関してはこちらに公式ドキュメントがあります。が、Chrome 自身のバージョンアップにともなって機能変更が加わり、それが混乱を招いてしまっている側面はあると思っています。今回紹介する内容はとりあえず 2016/Jun/23 時点では動くことを確認していますが、将来的な保証はないことを最初にお断りしておきます。

今回用意するファイルは次の3つです:
ファイル名用途・コメント
jquery-3.0.0.min.jsjQuery ライブラリ。ここから最新版をダウンロードし、manifest.json 内にファイル名を記述する
manifest.json拡張機能の定義ファイル
script.js拡張機能の実装ファイル


まず jQuery の最新版ライブラリを使います。jQuery のダウンロードページから最新バージョン(今回の例では 3.0.0)をダウンロードします。

次に定義ファイルである manifest.json です。Chrome 拡張機能を作る際に必須の JSON ファイルで、Chrome はこのファイルの中に記述されている通りに拡張されます。今回は以下の様な内容にします:
{
  "name": "AmazonASIN",
  "version": "0.0.1",
  "manifest_version": 2,
  "description": "Amazon商品ページからASINコードを取り出す拡張",
  "content_scripts": [
    {
      "matches": ["http://www.amazon.co.jp/*","https://www.amazon.co.jp/*"],
      "js": ["jquery-3.0.0.min.js","script.js"]
    }
  ]
}

この中では以下の様な指定を行っています:
  • 拡張の名前(name)は "AmazonASIN"
  • 拡張のバージョン(versoin)は 0.0.1
  • マニフェストのバージョン(manifest_version)は2(固定)
  • description に拡張の説明を記述
  • content_script 内にスクリプトの条件を指定
  • このスクリプトは http(s)://www.amazon.co.jp/ 内のページを参照している時だけ動く
  • 動くファイルは前述の jquery-3.0.0.min.js と後述の script.js

簡単に言うと、アマゾン(www.amazon.co.jp)のページを参照している時に script.js が動く、という指定をしています(jquery-3.0.0.min.js は script.js 内で利用しているのでここに記述しています)。

そして今回紹介する拡張機能の本体となるのが script.js です。この内容は以下のとおりです:
$(function(){
 $(".col2 .pdTab tr:first").each(function(){
  var tr = $(this);
  var td1 = tr.children('td:first');
  if( td1.html() == "ASIN" ){
    var td2 = tr.children('td:nth-child(2)');
    var asin = td2.html();
    window.prompt( "ASIN", asin );
  }
 });
});

jQuery にある程度詳しい人であれば簡単に理解できそうなほどシンプルな内容ですが、中身を一応紹介します。

まず最初に、自動化したい作業はこんな感じです:
  1. アマゾンの商品ページを開いたら、
  2. 「登録情報」の「ASIN」と書かれた箇所(下図参照)を探して、
  3. その横の ASIN 番号を取り出して表示する

2016062301

↑この機能、アフィリエイターに需要ありますかね・・・


そしてこのアマゾン商品ページの HTML ソースを見ると、『「登録情報」の「ASIN」と書かれた箇所』というのが、『col2 クラスを持った div パート内の、pdTab クラスを持った div 内のテーブルの、最初の tr パート』であることがわかります:

2016062302


この tr の2番目の td に目的の ASIN 番号が書かれているのでこれを取り出すのが目的です。というわけで、上記の script.js 内では以下の様な処理を記述しています:
  • Amazon のページでのみ以下を実行する(manifest.json で指定済み)
  • ページが全て読み込まれた後に以下を実行
  • col2 クラス以下の pdTab クラスの更に下にあるテーブルの最初の1行(最初のtr)を取り出す
  • 取り出した tr 内の1つ目の td を取り出し、その中に "ASIN" と書かれていることを確認する
  • 確認できたら tr の2つ目の td (ここに ASIN 番号がある)を取り出して、その HTML 文字列を取り出す
  • 取り出した ASIN 番号を画面に表示する

これで処理の記述はできたので、上記3つのファイルを全て同じフォルダ(下図では c:\tmp\amazon_extension)内に保存します:
2016062303


作成した Chrome 拡張はデベロッパーモードの Chrome 内に読み込ませて利用することができます。Chrome を起動後、右上のメニューボタンから 「その他のツール」-「拡張機能」 を選択します:
2016062304


Chrome 拡張機能のメニューが表示されたら、右上の「デベロッパーモード」にチェックを入れます。これでデベロッパーモードになったので、更に「パッケージ化されていない拡張機能を読み込む」ボタンをクリックします:
2016062305


先程作成した3つのファイルを格納したフォルダ(上記の例では C:\tmp\amazon_extension\)を指定して拡張機能を読み込みます:
2016062306


拡張機能にエラーがなければ正しく読み込まれ、拡張機能一覧に表示されます。「有効」にチェックが入っていることを確認してください(チェックを外すと、この拡張機能は動きません。またその右のゴミ箱アイコンをクリックすることで拡張機能を Chrome から削除することもできます):
2016062307


では改めてアマゾンの商品ページを見てみましょう。例えばこのページを参照してみると、、
https://www.amazon.co.jp/gp/product/B014CGROJW/


商品ページがロードされた後に以下の様なダイアログが表示され、ASIN コードが取り出されたことが確認できるはずです(必要であれば、ここからコピーできます):
2016062308


Chrome 拡張機能のごくシンプルな例ですが、こんな感じで作れます。実体はクライアントサイドの JavaScript ですが、クロスサイトスクリプティングの制約を受けることなく AJAX が実行できたりするので、この中で外部連携も含めた様々な処理を記述できます。例えばここで紹介されているサンプルでは Chrome 拡張から Watson API を実行していたりします:
女優・ディベロッパー池澤あやかさんが作成したアプリとは? |Bluemix Developers Lounge


Amazon EC2IDCF のサーバーインスタンスを使っていますが、どうしても気になるのはデフォルト状態ではスワップ領域が確保されていないことです:
2015020601


↑メモリは1GBで残り65MBほど。
 もうすぐメモリが足りなくなりそう、でもスワップ領域はゼロ・・・


特に高速化のために memcached とかを使うアプリを動かそうとすると、どうしてもメモリが不足しがちになります。スワップ領域がない状態で、一瞬でもメモリが足りなくなってしまうとアウトです。回避するにはなんとかしてスワップ領域を確保する必要があります。


EC2 や IDCF クラウドでは静的にスワップ領域が確保されているわけではないため、EBS などの追加ディスクを使う方法もありますが、これだとスワップ領域のために料金がかかる上、EBS は I/O にも課金されるので、スワップファイルを作る先としてはコスト的に不利です。

というわけで、インスタンスの起動時にディスクの空き部分を使ってスワップファイルを作ってスワップ領域とする、という方法を紹介します。これなら(ディスクに空きがあれば、の前提が必要ですが)ディスクを追加せずにスワップ領域を確保することができます。

具体的には /etc/rc.local あたりに以下のような内容を追加します。この例ではスワップサイズをメモリ量から動的に変更するようにしていますが、あくまで一例です。固定値を書き込んでしまってもいいと思います(青字は僕のコメント):
  :
  :
SWAPFILENAME=/swap.img スワップファイル名
MEMSIZE=`cat /proc/meminfo | grep MemTotal | awk '{print $2}'` 現在のメモリ量(KB)を取得

メモリ量からスワップ領域のサイズを決定
if [ $MEMSIZE -lt 1012293 ]; then
  SIZE=${MEMSIZE}k メモリ 1GB 以下の場合、スワップ領域はメモリサイズと同じ
elif [ $MEMSIZE -lt 2097152 ]; then
  SIZE=${((MEMSIZE * 2))}k メモリ 2GB 以下の場合、スワップ領域はメモリサイズの倍
elif [ $MEMSIZE -lt 8388608 ]; then
  SIZE=${MEMSIZE}k メモリ 8GB 以下の場合、スワップ領域はメモリサイズと同じ
elif [ $MEMSIZE -lt 67108864 ]; then
  SIZE=${((MEMSIZE / 2))}k メモリ 64GB 以下の場合、スワップ領域はメモリサイズの半分
else
  SIZE=4194304k メモリ 64GB 以上の場合、スワップ領域は8GB
fi

スワップファイルを作成してスワップオン
fallocate -l $SIZE $SWAPFILENAME && mkswap $SWAPFILENAME && swapon $SWAPFILENAME
  :
  :

/etc/rc.local は他の初期化スクリプトが実行された最後に実行される設定コマンドファイルです。なのでサービスやらの自動実行が行われた最後にこのコマンドが実行され、/swap.img というスワップファイルが作成されて、スワップ領域として動き始めます。このスワップファイルのサイズは物理メモリサイズに応じて動的に切り替わるようにしています。


これでサーバーインスタンスを再起動すると、今度は起動時に上記のスクリプトが実行され、スワップ領域が動的に作成されます。これで少し安心:
2015020602



(参考)
http://dev.classmethod.jp/cloud/ec2linux-swap-bestpractice/

 

このページのトップヘ