課題
amazon.co.jp での今年に購入した電子書籍の電子領収書を一括で取得する。(MacOSX, firefox の環境で)
電子書籍を大量に購入していて、その購入領収書を提出する必要がある場合に利用する。
方法
- ruby で selenium-webdriver をつかって firefox を自動操作して、
amazon の購入履歴ページ (2014 年分) の全画面の html ソースを取得する。
amazon のページにアクセスするための情報はコマンドラインの引数として渡す。 - 各購入品の領収書のページにアクセスして、スクリーンショットを取得する。
$ ruby amazon.rb 登録メールアドレス パスワード
プログラムコードはこの記事の末尾に示す。
プログラムを実行すると、firefox が自動起動する。
自動で amazon へのログイン、購入履歴ページの閲覧、ログアアウト、firefox の終了が行われる。
実行後は、./screeshots 以下に (page-*.html:一覧ページ、 order-*.png:領収書) が保存される。
./scrennshots フォルダはあらかじめ作成しておく。
後処理の例
上のプログラム実行で情報は取得できるが、これを加工する例を2つ示す。
- 電子領収書を1つの PDF ファイルにまとめる。
convert -resize 575x823 -gravity north -background white -extent 595x842 *.png 1.pdf
- 表計算ソフトで管理できるように [購入日、価格、値引き額、タイトル、商品情報 URL] を
csv 形式で得る。
$ ruby make-index.rb > 1.csv
$ cat head.csv 1.csv > 2.csv
プログラムコードはこの記事の末尾に示す。
生成した csv を元に列名、価格合計セルを追加した例:
ソースコード
Gemfile
source "https://rubygems.org"
gem 'selenium-webdriver'
gem 'nokogiri'
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 では、
- javascript をつかって別 window を開く
- 開いた window に制御を移す。そして 領収書画面を load する。
- 処理が終わったら、window を close する。
- 一覧ページの window に制御を戻す。
という操作を行わせている。
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
日付,価格,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