chakokuのブログ(rev4)

テック・コミック・ごくまれにチャリ

Amazonの購入履歴を抽出してみる

普段はKindle無料コミックを読んでいるのだが、累計で2000冊近くになっているようだ。一体どんなタイトルを読んできたのか、リストアップしたいと思った。普通に考えると購入履歴から取ってくればいいのでは?となるが、2000冊近くになると一覧でも表示されず、何回もページ送りが必要。さらに、Amazonの購入履歴はCSV形式等によるダウンロードする機能がない。
困ったAmazon利用者は購入履歴ダウンロードツールを作ったりしているようだが、人の作ったアプリにAmazonのID/PWDを入れるのはちょっと怖いなーというのと、勉強を兼ねる意味もあって、作ってみることにした。
おおよそ、、以下のステップが必要
(1)購入履歴サイトにアクセスしてHTMLファイルを取得
(2)HTMLファイルをパージングして、購入日、購入物を取得
(3)取得した情報をExcelかDBに入れて一覧表示化、分析できるようにする

まず(1)のためのクローラをC#で作った。左がその画面。AmazonのサイトはREST形式(と理解していますが)で、リソースにアクセスするためのURLが一意に決まっており、購入履歴もページ指定を変えれば任意の月日の履歴にアクセスすることが可能。クローラでは、URLの可変部分を変数化してテンプレートを指定して、for文のように、初期値、最終値、増加値でURLが自動生成される仕組みにした。で、、HTMLが読み込めると、自動でファイルに出力する機能を付けた。これにより、何十ページにも渡る過去の購入履歴がテキストとしてローカルPCに取得できる。


次に、、(2)のパージングについて、正攻法だと、HTMLファイルを読み込んでパースしてツリーを作成して、木構造から必要とする情報を取得するアプローチをとるべきだけど、、木構造のマッチというのが大変そうで、表層的(場当たり的)なキーワードマッチで代用した。以下は手抜きパージングプログラム。まだデバッグ中で改良の余地あり。でもだいたい動く。

#!/usr/bin/python
#
# Purchase Log Filter
#
import sys

nextLineIsOut = False
for line in sys.stdin:
    line = line.rstrip('\r\n')
    if nextLineIsOut:
        if not '<' in line:
            print line+',', 
        nextLineIsOut = False

    if 'a-color-secondary' in line:
        nextLineIsOut = True
    else:
        nextLineIsOut = False
        if 'a-box-group a-spacing-base order' in line:
            print "\n----------------------------"
        elif '/gp/product/' in line:
            print line
            nextLineIsOut = True  # product name

さらに、、HTMLでは書籍タイトル等の漢字文字列が数値文字参照
(例)

&#xFF2E;&#xFF2F;&#xFF49;&#xFF33;&#xFF25;

(表示は、NOiSE)という形式で扱われておりこれをマルチバイト?の漢字コードに変換が必要。例えば、、pythonのunichrコマンドを使うことで以下のようなコードで文字参照->バイナリ->unicode->utf8に変換可能

#!/usr/bin/python

def chref2utf8(ref):
  str = u''
  for ch in ref.split(';'):
     if ch:
       ch = ch.replace('&#x','')
       ch = int(ch,16)
       str += unichr(ch)
  return str.encode('utf8')

html='&#xFF2E;&#xFF2F;&#xFF49;&#xFF33;&#xFF25;'     # -> [NOiSE] in KANJI
print "[{:s}]".format(chref2utf8(html))

これでおおよそCSVに落ちるので、ExcelなりDBに入れてあれこれ分析してみることが可能。。
(今は手抜きフィルダーのバグせいでまだCSVまで進んでいません。でもまぁややこしい所はクリアしたので時間次第と思ってますが)

■追記
数値文字参照→漢字変換付き簡易パーザ(購入履歴のページをダウンロードしたHTMLに対して商品名等を抜き出すフィルタ)

#!/usr/bin/python
#
# Purchase Log Filter
#

import sys
import re

def chref2utf8(ref):
    str = u''
    for ch in ref.split(';'):
        if ch:
            matched = re.match('(.*)&#x([0-9a-zA-Z]{4})(.*)',ch)
            if matched:
                (pre,knj,post)=matched.groups()
                if knj != '':
                    knj = int(knj,16)
                    knj = unichr(knj) 
                str += (pre.decode('utf8') + knj + post.decode('utf8'))
            else:
                str += ch.decode('utf8')

    return str.encode('utf8')


nextLineIsOut = False
for line in sys.stdin:
    line = line.rstrip('\r\n')
    if nextLineIsOut:
        if not '<' in line:
            if '&#x' in line:
                kanji = chref2utf8(line)
                kanji = kanji.replace(' ','')
                print "{:s},".format(kanji),
            else:
                print "{:s},".format(line.replace(' ','')),
        nextLineIsOut = False

    if 'a-color-secondary' in line:
        nextLineIsOut = True
    else:
        nextLineIsOut = False
        if 'a-box-group a-spacing-base order' in line:
            print ""
        elif '/gp/product/' in line:
            match = re.match('.*/gp/product/([0-9A-Z]+)/.*',line)
            if match:
                print "{:s},".format(match.groups()[0]),
            else:
                print "{:s},".format(line.replace(' ','')),
            nextLineIsOut = True  # product name

動作例


$ ./filter_pp.py < purchase.html
注文日, 2016年2月5日, 合計, ¥0, 注文番号, D01-5677795-0140206, B011N9AB5A, , B011N9AB5A, ギフト±1, Kindle版, 販売:, , ,
注文日, 2016年2月5日, 合計, ¥0, 注文番号, D01-7947709-6805863, B00HYP18VY, , B00HYP18VY, はねバド!(1)(アフタヌーンコミックス), Kindle版, 販売:, , ,
注文日, 2016年2月3日, 合計, ¥0, 注文番号, D01-2314051-3516213, B01AUG7W9U, , B01AUG7W9U, セトウツミ 1【期間限定 無料お試し版】(少年チャンピオン・コミックス), Kindle版, 販売:, , ,
購入したKindleコンテンツ総数は1992件であった。(9割以上は無料コミック)

$ grep -i kindle order_log.csv |wc -l
1992
■追記
GWからKindle無料コミックをすべて収集するぐらいのノリでクローリングに取り組んできたけど、しばらく経つと収集したデータも古くなる(ASINが変わるとか、無料期間が終わるとか)。Amazonの無料コンテンツの変化に追いつくにはクローリング頻度を上げる必要があり、ゆるゆると収集するような速度では多分到底追いつかない。。というわけで、、クローリングの限界を感じて段々と飽きてきた。。はぁ。。