プログラマでありたい

おっさんになっても、プログラマでありつづけたい

WishリストからKindleの安売り情報の通知

 最近の読書体験を振り返ると、9割近くがKindleでの読書です。読みたい本を買っておいて、好きな時に好きな本を読めるのは非常にありがたいです。一方で、そのスタイルだと積読が多くなります。読みたいリストは無数にあって積読が多いのであれば、買うタイミングは安くなった時に買っておくのがベストでしょう。Amazonでは、様々なKindleセールが行われます。しかし、その告知方法は充分ではないので、ユーザ自身がウォッチしておく必要があります。人間がやるのもバカバカしいので、クローラーにやってもらいましょう。

実現方法案



 実現する為には、下記の前提・手順を踏みます。

1. いつか読みたい本は、ウィッシュリストに入れておく
2. ウィッシュリストは公開にしていて、認証なしでアクセス可能にしておく
3. クローラーはウィッシュリストを取得して、対象の商品一覧を取得する
4. 対象商品を1件づつ巡回して、Kindleの商品であれば値引率をチェック
5. 一定の値引率だと通知

実装



 それでは、順番に実装方法を考えていきましょう。

ウィッシュリストの取得

 Amazonにログイン後に、http://www.amazon.co.jp/gp/registry/wishlist/にアクセスしてください。自分の、1つ、もしくは、複数のウィッシュリストがあるはずです。
f:id:dkfj:20151220230539p:plain

そこをクリックすると、固有のIDがついたウィッシュリストのURLのページに遷移するはずです。私の場合は、次のURLです。
http://www.amazon.co.jp/gp/registry/wishlist/3G4653SB32HMZ/

ウィッシュリストの公開設定

 リストの公開設定は、右上の方の「リストの設定」から行えます。ポップアップから該当のリストを選んで、公開とするだけです。一方で、リストを公開とすることにより、リスクも皆無ではないです。内容を理解した上で、自己責任でお願いします。
f:id:dkfj:20151220230950p:plain

対象商品の一覧を取得する

 もはやスクレイピングの仕方を忘れつつありますが、こんな感じで取れます。classの値がa-link-normalを取得すると、ページ本体とレビューのURLが取得できます。本体の方は、相対パスなので補完して表示しています。

require 'nokogiri'
require 'open-uri'

doc = Nokogiri::HTML(open('http://www.amazon.co.jp/gp/registry/wishlist/3G4653SB32HMZ/'))
objects = doc.xpath("//a[@class='a-link-normal']")
objects.each {|element|
  url = element[:href]
  puts "http://www.amazon.co.jp#{url}" if not url.match(/http/)
}

 さて問題は、リンク先のページの商品がKindleかどうかです。Amazonは商品をASINというコードで管理しています。紙の本は、ASIN=ISBNとなっています。Kindelやその他の商品の場合、Amazonが付番するコードになっています。ISBNかどうかはコードをチェックすれば出来るけど、Kindleか他の商品かはコードからは、読み取れません。(体系あるのであれば、教えてください。)
 ということで、効率悪いけど一度アクセスして個々に確認する方法をとります。この方法のメリットとしては、紙の本しか無かったものがKindle化している場合の検知もできます。

詳細ページの取得

 詳細ページについては、UserAgentを偽装しておかないと503になります。(いつからだっけ?)本来であれば、API経由で取ることを推奨しますが、今回は省略です。対象ページのカテゴリーは、IDがnav-subnavのdeta-categoryの属性を参照すれば取得できます。

require 'nokogiri'
require 'open-uri'

UserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36 '

url="http://www.amazon.co.jp/dp/4787710176?_encoding=UTF8&colid=3G4653SB32HMZ&coliid=I168IVRW8TS0E4"
url="http://www.amazon.co.jp/ebook/dp/B00VESYKDU/ref=tmm_kin_swatch_0?_encoding=UTF8&coliid=I3I1RV6FI82PJ5&colid=3G4653SB32HMZ&qid=&sr="
doc = Nokogiri::HTML(open(url, 'User-Agent' => UserAgent))
puts doc.xpath("//div[@id='nav-subnav']").attr("data-category")

 紙の書籍の場合はbooksで、Kindleの場合はdigital-textとなります。

紙の書籍の場合の処理

 紙の書籍の場合は、Kindleのリンクがあるか探してみましょう。あまり良いやり方ではないですが、Kindle版がある場合は全てのフォーマットのうちで一番左に表示されます。ということで、とりあえず一番左のものをとってKindleだったら処理対象とするという方針にします。
f:id:dkfj:20151221105833p:plain

ブラウザで見ているものと、JavaScriptが動かないOpen-URIで取得したもので多少差異があるので戸惑うかもしれませんが、下記のような感じで取得できます。

if doc.xpath("//span[@class='a-button a-spacing-mini a-button-toggle format']/span/a/span").first.text == "Kindle版"
    puts "http://www.amazon.co.jp#{doc.xpath("//span[@class='a-button a-spacing-mini a-button-toggle format']/span/a").first[:href]}"
end

※いろいろ面倒臭がってるので、マサカリ飛んできそうですが。

値引き情報の取得

 Kindleの値引きは、2種類あります。ポイント還元と純粋な値引きです。今回は、値引きを対象とします。値引き情報は、savingsRowクラスの中に格納されています。あまり綺麗な形で収まっていないので、正規表現等を併用します。

off_info = doc.xpath("//tr[@class='savingsRow']/td[2]").text
通知

 任意のしきい値以上の値引率だとメールで送ると言った感じの実装をすればよいです。メールを送るだけであれば、AWSのSESとかがお勧めです。

感想



 構造化していなので面倒くさいです。ウィッシュリストだけスクレピングして、後はProduct Advertising APIを使う方が絶対楽です。Rubyでスクレピングするのであれば、Rubyによるクローラー開発技法という本があります。

Rubyによるクローラー開発技法  巡回・解析機能の実装と21の運用例

Rubyによるクローラー開発技法  巡回・解析機能の実装と21の運用例

See Also:
プログラマになりたい Advent Calendar 2015 - Qiita
プログラマになりたい Advent Calendar 2015 - Adventar
http://qiita.com/advent-calendar/2015/crawler