GASでPS4 Proが定価販売されたら通知するシステムを作る
これはマイネットエンターテイメント Advent Calendar 5日目の記事です。
要件定義
- Amazonが定価で販売していたら通知したい
- わざわざサーバーは用意したくない
- 定期的、大体10分に一度くらいのスパンでチェックしたい
GASで実装
上3つを満たす選択肢としてGoogle App Script(以下GAS)で作ることにした。
GASにはUrlFetchApp.fetch()というGETリクエストをする関数がある。
とりあえずこれで目的のページのクローリング、スクレピングを試みた。
Product Advertising API
しかしAmazonはクローリングを禁止していてAPI経由の取得のみしか許されていない。
これが実に面倒くさい。
APIを叩くにはアクセスキーの取得とアソシエイト登録が必要だった。
Access Key IDとSecret Access Keyの取得 - Amazon Web サービス
これが済んだら次はAPIの仕様を把握する必要がある。
基本的に把握する必要があるのは次の2つのパラメータ
- Operation
- ResponseGroup
Operationは動作の指定をするパラメータ
カートに商品を追加したり商品情報や売り手の情報などほしい情報や行いたい動作を指定する。
ResponseGroupは動作の詳細を指定するパラメータ
商品情報はASINで探すのか、それともキーワードで検索するのかなどを定義する。
次にAPI用のURLを作る
試すにはアクセスキーの暗号化やタイムスタンプの挿入が必要だがここで勝手にやってくれる。
http://associates-amazon.s3.amazonaws.com/signed-requests/helper/index.html
これが暗号化前に用意したURL
http://ecs.amazonaws.jp/onca/xml? Service=AWSECommerceService& AssociateTag=<アソシエイトID>& Operation=ItemLookup& ResponseGroup=OfferFull& ItemId=B01LRHPUZ4
ItemLookupは指定したIDの商品情報を返すOperation。
OfferFullは出品者情報を返すResponseGroup。
これで現在のPS4 Pro出品者情報を返してくれる。
APIから適切なxmlが返ってくるのを確認したら次はGASスクリプトを組む。
GASソースコード
とりあえず今回は
- 新品を5万円以下で販売している出品者がいる
- トップ画面の販売者がAmazon
このどちらかの場合はメールで通知してくれるスクリプトを作った。
// チェックに引っかかったらメールで通知する function notification(){ var to = '<通知先メールアドレス>'; var title = '【通知】PS4が買い時!'; var message = '早く買え'; GmailApp.sendEmail(to, title, message); } // 指定ASINの出品状況をxml形式でAmazonから取得 function getOfferInfo(asin){ var endPoint = "http://ecs.amazonaws.jp/onca/xml?"; var param = { Service:"AWSECommerceService", AssociateTag:"<アソシエイトID>", Operation:"ItemLookup", ItemId:asin, Timestamp:new Date().toISOString(), AWSAccessKeyId:"<AWSAccessKeyId>", ResponseGroup:"OfferFull" }; var urlParams = Object.keys(param).sort(); urlParams = urlParams.map(function(key){ return key +"="+encodeURIComponent(param[key]); }); var addParams = "GET" + "\n" + "ecs.amazonaws.jp" + "\n" + "/onca/xml" + "\n" + urlParams.join("&"); var secretKeyEncoded = Utilities.base64Encode(Utilities.computeHmacSha256Signature(addParams, "<AWSSecretKey>")); var url = endPoint + urlParams.join("&") + "&Signature=" + encodeURIComponent(secretKeyEncoded); var xml = UrlFetchApp.fetch(url); return xml.getContentText(); } function main(){ // PS4 ProのASIN var asin = "B01LRHPUZ4"; var xml = getOfferInfo(asin); var document = XmlService.parse(xml); var root = document.getRootElement(); var xmlns = 'http://webservices.amazon.com/AWSECommerceService/2011-08-01'; var ecs = XmlService.getNamespace(xmlns); // 新品の最低価格 var amount = root .getChild('Items', ecs) .getChild('Item', ecs) .getChild('OfferSummary', ecs) .getChild('LowestNewPrice', ecs) .getChild('Amount', ecs) .getText(); // ページTOPの出品者 var topMerchant = root .getChild('Items', ecs) .getChild('Item', ecs) .getChild('Offers', ecs) .getChild('Offer', ecs) .getChild('Merchant', ecs) .getChild('Name', ecs) .getText(); // 新品の最低価格が50000円以下、またはAmazonが出品していたら通知 if(amount < 50000 || topMerchant == 'Amazon.co.jp'){ notification(); } Logger.log("トップ出品者 : " + topMerchant); Logger.log("最低価格 :" + String(amount)); }
とりあえずこれを10分間隔で回してる。
詰まったポイント
- ASINで検索しても引っかからない
(ASINはAmazon固有のID)
エンドポイントをhttp://webservices.amazon.com/にしていた
これはアメリカ用のエンドポイントで日本の商品は引っかかったりかからなかったり。
英語の記事見ながら作っていたのでそのまま使ってしまっていた。
- XMLのパースが上手くいかない
確認できるのがGASのログだけなのでオブジェクトの状態がいまいち把握できなかった。
原因はgetChildren()で取得していたからであり、全てに[0]をつけなければいけなかった。
getChildren()は同じ名前のエレメントが複数あるときに使用するもので、
直下にあるエレメント名がユニークならgetChild()で取得すべきだった。
- アマゾンが出品しているかどうかをAPIでは確認できなかった
アマゾンの在庫があればトップに出てくる販売者はアマゾンである確率が高いのでそれを確認。
それと妥協して新品で5万円以下で出品している販売者がいるときも通知の対象とした。
参考
Access Key IDとSecret Access Keyの取得 - Amazon Web サービス
AmazonのProduct Advertising APIを使ってみる | cly7796.net
Lazy Programmer's Wlog / Tech/AmazonAPIでASINが存在するのにエラー
Docs: Product Advertising API (Version: 2013-08-01) : Documentation Archive : Amazon Web Services
アマゾンのAPIを使って得た本の情報をGASのSpreadsheetに保存してみる - Qiita
xmlを取得する関数はここのamazon.gsを丸々使わせてもらいました。