30分ぐらいでAmazonの注文履歴をjavascriptの勉強をしながら取得する方法

年の瀬が迫っている中、ふと、「僕ってどれぐらいAmazonの売り上げに貢献しているんだろう」という疑問がわいた。

Amazonのサイトでアカウントログインして、注文履歴を追っていけば、把握はできるが操作の手間がある。これをスクレイビング的にスクリプトで抽出することはそんなに難しくないはず。先人が居られるだろうと思って検索したら、案の定、次のようなサイトが見つかった。

すばらしい!!っと思って、早速これらを実行をしようとしたのだが、うまく履歴を取得できないようだった。スクリプトが作られた時期は2013年の半ば。今から一年半程度前のことで、きっと、その間にAmazonサイトのHTMLスタイルの改変などが行われたために、スクリプトの情報取得がうまく機能しなくなっているのだと考えられる。

この手の情報取得スクリプトはどうしても賞味期限がある。重要な情報を持っているサイトのスタイルが改変されると、せっかく作ったスクリプトは使い物にならなくなってしまうことが多い。例えば、10年ほど前に購入した以下の本(Amazonで購入)

Spidering hacks―ウェブ情報ラクラク取得テクニック101選

Spidering hacks―ウェブ情報ラクラク取得テクニック101選

 

は、掲載されているスクリプトのうち今でも機能するのがどれほどあるのか、怪しいもんだ。(確認していないけどね)(でも、とってもよい本です)

では、どうすべきかと考えると、更新頻度の高いサイトから情報取得する方法は、使い捨てのスクリプトになることを許容して、最小限の手間でプログラミングをするしかない、と思う。

というわけで、ここでは、先人様のサイトのスクリプトを参考にしつつ、2014年12月現在のAmazonの注文履歴の画面を分析しながら、javascriptコンソールで最小限のコマンド入力によって情報取得する方法を紹介したい。

注意事項

Webブラウザのjavascriptコンソールを利用するので、実行は自己責任でお願いします。

実際のところ、ログイン状態でのブックマークレットの利用は、注意しておいたほうがよい。例えば、amazonにログインした状態で別サイトのjavascriptを実行することは、そのスクリプトの中でログインのcookie情報を扱えるということなので、セキュリティには注意が必要である。

事前準備

適当なブラウザ、(Chrome or firefox)で、次のURLにアクセスして、Amazonにログインする。

https://www.amazon.co.jp/gp/css/order-history

でもってその画面で、javascriptコンソールを開く。

jQueryを使えるようにする

javascriptコンソールから以下のコマンドを入力する。

(function(s){s.src='https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js';document.body.appendChild(s)})(document.createElement('script'));

jQueryのライブラリのscriptタグを追加して、jQueryの文法が使えるようになる。

htmlから注文データを抽出する

jQueryの文法を利用してhtmlのデータから注文データを取得する方法を考える。先のURLにログイン・アクセスすると以下の画面が出るはずである。(Chromeの場合)

f:id:skzy:20141223011411j:plain

今回は、簡単のため注文頻度と注文金額を把握したい。一回の注文単位の日付と注文金額は、上の画面の赤い枠の中を取得すればいい。

javascriptコンソールのElementsタブを開いて、htmlのタグにマウスをあわせるとその対象のオブジェクトが画面で選択表示される。これを用いて、上画面の赤枠に該当するタグ or クラスを調べていく。

すると、class="order-info"が赤枠を示していることがわかる。この中のvalueクラスが日付や金額などの文字列を表してそうである(※2014年12月現在の状態)。試しに、javascriptコンソールで次のように打ってみる。(事前に前項のjQueryライブラリ読み込みを行わないとエラーになるので注意。)

$(".order-info").map(function(i,s){return $(".value",s).map(function(i,s){return $.trim(s.textContent);});});

jQueryセレクターを使ってorder-infoクラスとvalueクラスのテキストを2重配列で抜き出す。うまく抽出していれば、2重配列の中に赤枠の情報が含まれているはずである。これを関数化するために、以下をjavascriptコンソールで入力する。

function getOrder(html){
  return $.merge([],
    $(".order-info",html).map(function(i,s){
      return $(".value",s).map(function(j,t){
        return $.trim(t.textContent);
      });
    })
  );
}

※$.mergeはjQueryのオブジェクトを配列に変換するもの。配列のほうが個人的に扱いやすいため。

別のURLにアクセスする

前項では今表示されているhtmlに対してしか、情報抽出できていない。過去の年の注文にアクセスするには、javascriptでURLへのアクセスをしなければならない。XMLHttpRequestのGETを使おう。

function getOrderHtml(urlstr) {
  http = new XMLHttpRequest();
  http.open('GET',urlstr,false);
  http.setRequestHeader("Content-Type","text/html");
  http.send(null);
  return http.responseText;
}

同期にすると厄介なので、http.openでfalseの非同期設定にして実際にアクセスが終わってからresponseTextを返すようにしている。そのためちと時間がかかるが、たかだか個人のデータを取得するのにそんな性能要件は必要ない。

※ちなみに、jQueryの$.getも試したけれどうまくいかなかったのでXMLHttpRequestを使ってます。

データのあるURLを繰り返し取得する再帰処理

Amazonの注文履歴表示は、(※2014年12月現在)10件以上の注文がある場合、複数ページに分かれて下のページ番号で選択するようになっている。そのため、注文履歴を全部取得するためのURLアクセスは、"年の選択...orderFilter"と、"何ページ目か?...startIndex(0が1ページ目,10が2ページ目...)"をパラメータで指定して取得することができる。

つぎの関数を定義したい。

  • 年を指定すると、その年の注文をすべて取得する
  • 注文はgetOrder関数の結果のconcatにする
  • 再帰を使う。(短くしたいので)
function getDataOfYear(yy,idx){
  console.log(yy+","+idx);
  var urlstr="https://www.amazon.co.jp/gp/css/order-history?ie=UTF8&orderFilter="+yy+"&startIndex="+idx;
  var rowd=getOrder(getOrderHtml(urlstr));
  if (rowd.length==0 || !(idx<999) ) { return [];
  } else { return getDataOfYear(yy,idx+10).concat(rowd);
  }
}

getOrderの結果が0になるまで、startIndexのページを増やしてconcatする再帰関数である。console.logで、今のURL取得状況を確認する。これでたとえば、

getDataOfYear("year-2014",0);

と実行してみると、2014年の注文を全部取ってきた結果を返してくれる。

全データをタブ区切りで文字列化する

さて、年を表すパラメータは"year-YYYY"というフォーマットなので、必要なだけデータを作ればいいけれど、ここでは画面のhtmlから取得する。上のほうのドロップダウンのアイテムが年のパラメータを選択しているので、

YEARS=$(".a-dropdown-container option").map(function(i,s){return s.value;}).filter(function(j,t){return j>1;});

と入力すると、"year-2014","year-2013",...という配列が取得できる。

このYEARS配列と、さっきほど用意したgetDataOfYear関数を使えば、すべての年の注文情報を得られる。

TSVDATA=YEARS.map(function(i,s){
  return getDataOfYear(s,0).map(function(t){
    return t[0]+"	"+t[1];}).join("
");
  });

getOrder関数で取得したデータのid=0が日付でid=1が金額である。これをtsvにしてリストを改行区切りで文字列化する。実行すると、すべての注文データをとりに行くため少々時間がかかる。console.logで進捗を見ながらしばし待つこと。

tsvを表示する

最後に、さっき取得したTSVDATAをlocation.hrefで表示させる。

location.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent($.merge([],TSVDATA).join("
"));

ずどーんと取得したデータが表示されるので、あとはこぴぺして煮るなり焼くなり好きにするがよい。

まとめ

実行したjavascriptの全貌:

(function(s){s.src='https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js';document.body.appendChild(s)})(document.createElement('script'));

function getOrder(html){
  return $.merge([],
    $(".order-info",html).map(function(i,s){
      return $(".value",s).map(function(j,t){
        return $.trim(t.textContent);
      });
    })
  );
};

function getOrderHtml(urlstr) {
  http = new XMLHttpRequest();
  http.open('GET',urlstr,false);
  http.setRequestHeader("Content-Type","text/html");
  http.send(null);
  return http.responseText;
}

function getDataOfYear(yy,idx){
  console.log(yy+","+idx);
  var urlstr="https://www.amazon.co.jp/gp/css/order-history?ie=UTF8&orderFilter="+yy+"&startIndex="+idx;
  var rowd=getOrder(getOrderHtml(urlstr));
  if (rowd.length==0 || !(idx<999) ) { return [];
  } else { return getDataOfYear(yy,idx+10).concat(rowd);
  }
}

YEARS=$(".a-dropdown-container option").map(function(i,s){return s.value;}).filter(function(j,t){return j>1;});

TSVDATA=YEARS.map(function(i,s){
  return getDataOfYear(s,0).map(function(t){
    return t[0]+"	"+t[1];}).join("
");
  });

location.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent($.merge([],TSVDATA).join("
"))

※これをそのままjavascriptコンソールこぴぺしても成功しない。scriptファイルにしてブックマークレットとすることはできると思う。

最後に

取得したtsvを適当に修正したあと、Tableau でグラフ化してみた。

f:id:skzy:20141223113533j:plain

年間で5万から20万、多いとして22万円利用している。明らかにAmazon依存症。2012年ごろから、注文頻度が急激に上昇しているのは、kindleを導入したため、無料の注文が多く発生したからである。

わりと、妥当なことがわかりました。