Edited at

[ruby] webdriver を使って amazon.co.jp での購入履歴情報を取得する


課題

amazon.co.jp での今年に購入した電子書籍の電子領収書を一括で取得する。(MacOSX, firefox の環境で)

電子書籍を大量に購入していて、その購入領収書を提出する必要がある場合に利用する。


方法


  1. ruby で selenium-webdriver をつかって firefox を自動操作して、
    amazon の購入履歴ページ (2014 年分) の全画面の html ソースを取得する。
    amazon のページにアクセスするための情報はコマンドラインの引数として渡す。

  2. 各購入品の領収書のページにアクセスして、スクリーンショットを取得する。

$ ruby amazon.rb 登録メールアドレス パスワード

プログラムコードはこの記事の末尾に示す。

プログラムを実行すると、firefox が自動起動する。

自動で amazon へのログイン、購入履歴ページの閲覧、ログアアウト、firefox の終了が行われる。

実行後は、./screeshots 以下に (page-*.html:一覧ページ、 order-*.png:領収書) が保存される。

./scrennshots フォルダはあらかじめ作成しておく。


後処理の例

上のプログラム実行で情報は取得できるが、これを加工する例を2つ示す。


  1. 電子領収書を1つの PDF ファイルにまとめる。

convert -resize 575x823 -gravity north -background white -extent 595x842 *.png 1.pdf

生成した PDF の例:

amazon-orders.png


  1. 表計算ソフトで管理できるように [購入日、価格、値引き額、タイトル、商品情報 URL] を
    csv 形式で得る。

$ ruby make-index.rb > 1.csv

$ cat head.csv 1.csv > 2.csv

プログラムコードはこの記事の末尾に示す。

生成した csv を元に列名、価格合計セルを追加した例:

amazon-order-index.png


ソースコード


Gemfile

source "https://rubygems.org"

gem 'selenium-webdriver'
gem 'nokogiri'


amazon.rb


amazon.rb

# -*- coding: utf-8 -*-

# 1. amazon の購入履歴を取得する。(scrennshots/* に保存される)
# $ ruby amazon.rb email password
#
# 2. 取得した情報から、明細書(*.png) を1つの PDF にまとめたものを作成する。
# (imagemagic の convert コマンドを使う)
# $ convert -resize 575x823 -gravity north -background white -extent 595x842 screenshots/ord*.png 1.pdf
#
# 3. 取得した情報から、csv 形式で購入物一覧表を作成する。
# $ ruby make-index.rb > 1.csv

require 'rubygems'
require 'selenium-webdriver'

SCREENSHOTS_DIR = './screenshots'

module Amazon
class Driver
# 新しいタブで 指定された URL を開き、制御をそのタブに移す。
def open_new_window(wd, url)
a = wd.execute_script("var d=document,a=d.createElement('a');a.target='_blank';a.href=arguments[0];a.innerHTML='.';d.body.appendChild(a);return a", url)
a.click
wd.switch_to.window(wd.window_handles.last)

wd.find_element(:link_text, '利用規約')
yield
wd.close
wd.switch_to.window(wd.window_handles.last)
end

# 現在の画面からリンクが張られている購入明細を全て保存する。
def save_order(wd)
wd.find_element(:link_text, '利用規約')
orders = wd.find_elements(:link_text, '領収書/購入明細書')
orders.each do |ord|

open_new_window(wd, ord.attribute('href')) do
@order_seq += 1
wd.save_screenshot("#{SCREENSHOTS_DIR}/order_#{format('%03d', @order_seq)}.png")
end
end
end

def save_order_history(wd, auth)
@page_seq = 0
@order_seq = 0

# 購入履歴ページへ
wd.get 'https://www.amazon.co.jp/gp/css/order-history'

# ログイン処理
wd.find_element(:id, 'ap_email').click
wd.find_element(:id, 'ap_email').clear
wd.find_element(:id, 'ap_email').send_keys auth[:email]

wd.find_element(:id, 'ap_password').click
wd.find_element(:id, 'ap_password').clear
wd.find_element(:id, 'ap_password').send_keys auth[:password]

wd.find_element(:id, 'signInSubmit-input').click

unless wd.find_element(:xpath, "//form[@id='order-dropdown-form']/select//option[4]").selected?
wd.find_element(:xpath, "//form[@id='order-dropdown-form']/select//option[4]").click # 今年の注文
end
wd.find_element(:css, "#order-dropdown-form > span.in-amzn-btn.btn-prim-med > span > input[type=\"submit\"]").click

# [次] ページをめくっていく
loop do
wd.find_element(:link_text, '利用規約')
@page_seq += 1
wd.save_screenshot("#{SCREENSHOTS_DIR}/page_#{format('%03d', @page_seq)}.png")
open("#{SCREENSHOTS_DIR}/page_#{format('%03d', @page_seq)}.html", 'w') {|f|
f.write wd.page_source
}

# ページ中の個々の注文を閲覧する。
save_order(wd)

elems = wd.find_elements(:link_text, '次へ »')
break if elems.size == 0
elems[0].click
end

# サインアウト
wd.get 'http://www.amazon.co.jp/gp/flex/sign-out.html/ref=gno_signout'
end
end
end

include Amazon

if ARGV.size != 2
puts "usage: ruby #{$PROGRAM_NAME} account password"
exit 1
end

wd = nil
begin
ad = Amazon::Driver.new
wd = Selenium::WebDriver.for :firefox
wd.manage.timeouts.implicit_wait = 20 # 秒
ad.save_order_history(wd, email: ARGV[0], password: ARGV[1])
ensure
wd.quit if wd
end



ポイント

一覧ページの領収書のリンクを click すると、ページ遷移して領収書画面が表示される。

1つの一覧ページは 10 個の領収書のリンクがある。

"領収書リンクの click", "スクリーンショット取得"、"前ページに戻る" の繰り返し操作では一覧ページを何度もロードすることになる。

そこで、領収書画面は別ウィンドウで開くようにし、スクリーンショットを取得したら、そのウィンドウを閉じる という操作をプログラムで行わせることにした。一覧ページを何度もロードすることを避けている。

webdriver では、

1. javascript をつかって別 window を開く

2. 開いた window に制御を移す。そして 領収書画面を load する。

3. 処理が終わったら、window を close する。

4. 一覧ページの window に制御を戻す。

という操作を行わせている。


make-index.rb


make-index.rb

# -*- coding: utf-8 -*-

require 'open-uri'
require 'nokogiri'
require 'csv'

# 値引き額を得る
# @param url 書籍情報ページの IRL
# @return 値引き額
def saving(url)
off = 0
begin
charset = nil
html = open(url) do |f|
charset = f.charset
f.read # htmlを読み込んで変数 htmlに渡す
end
doc = Nokogiri::HTML.parse(html, nil, charset)
# 値引きデータを抜き出す。
price = doc.css('.savingsRow .price')
if price && price.size > 0
off = price.text.scan(/.+/)[0].scan(/\d+/).join('')
end
rescue => ex
STDERR.puts url
STDERR.puts ex
end
off.to_i
end

# 購入物の一覧を csv 形式で得る。(購入日、価格、値引額、タイトル)
# @return csv 形式の文字列
def generate_csv
csv_string = CSV.generate do |csv|
Dir::glob("screenshots/**/*.html").each do |path|
f = File.open path

page = Nokogiri::XML f
orders = page.css('.action-box')
orders.each do |order|
date = order.css('.order-level > h2').text
price = order.css('.price').text.scan(/\d+/).join('')
title = order.css('.item-title').text.strip
url = order.css('.shipment a').attribute('href').value
off = saving(url)
csv << [date, "#{format('%8d', price)}", off, title, url]
end
f.close
end
end
csv_string
end

puts generate_csv



head.csv


head.csv

日付,価格,OFF,書名,URL

,=SUM(B3:B300),=SUM(C3:C300),


ポイント

値引き額は、購入一覧ページ、領収書ページには記載されていない。

商品ページを訪れ、その中の値引き額情報を検索するようにしている。

購入一覧先にある商品ページは存在していないことがある。(価格や情報が更新された?)

その場合はプログラムを中断せずに、STDERR にメッセージを出して、最後の商品まで処理を行うようにしている。


更新履歴


  • 2019-03-09

    png から pdf を作る ruby スクリプトを追加した。

    1 ページに2件、10ページの pdf を生成することができる。


  • 2014-01-04

    amazon.co.jp からの購入履歴 (領収書ページ) を得るスクリプトを更新した。


    (amazon の購入履歴ページも構造が半年前から変化したため)


    https://github.com/katoy/amazon-orders/blob/master/amazon.rb