AmazonRanking

提供: j8takagi wiki
移動: 案内検索

『Amazonランキングの謎を解く-確率的な順位付けが教える売上の構造』がすごく面白かったので、触発されてAmazonからランキングデータを取得してグラフ化してみました。グラフは、SVGでの表示ができる場合はSVG、できない場合はPNGで表示します。

作成したグラフの目次はこちらです。1時間に1回自動更新されます。

グラフ作成の手順

  1. 準備
  2. ISBNリストファイルISBN.txtを作成
  3. バッチファイルmakehtml_batch.shで、HTMLファイル作成。バッチファイル内部でmakeindex.rbとmakehtml.rbを呼び出している
  4. バッチファイルbookrank_batch.shで、CSV形式のAmazonランキングファイルを作成。バッチファイル内部でbookrank.rbを呼び出している
  5. バッチファイルplot_batch.shで、SVG形式およびPNG形式のグラフ画像ファイルをまとめて作成。バッチファイル内部でplot.Rを呼び出している
  6. cronで、指定した時間にコマンドが実行されるよう設定

準備

  1. アマゾン ウェブ サービス(AWS)からアクセスキーを取得
  2. Ruby(1.9.2)をダウンロード
  3. ruby-aawsをインストール、設定
  4. Rをインストール
  5. RでRSvgDeviceをインストール

ISBN.txtの作成

ISBN.txtは次のような、各行に1つのISBNを記述したテキストファイル。手動で作成。

475981339X
4004150930
4140807431
4774141291
4860650530
4864010048
4864010056
4492521453
453231139X
4004308569

HTMLファイルの作成

全体の目次ファイルindex.htmlと、各書籍のグラフを表示するHTMLファイル{ISBN}.htmlを作成する。 index.htmlは、makeindex.rbで作成。{ISBN}.htmlは、makehtml.rbで作成。どちらも、bookdesc.rbが必要。

バッチmakehtml_batch.shは、ISBN.txtをもとにindex.htmlと複数の{ISBN}.htmlを作成する。ISBN.txtを変更したときには、makehtml_batch.shを実行すれば更新される。

bookdesc.rb

# -*- coding: utf-8 -*-
require 'amazon/aws/search'
include Amazon::AWS
include Amazon::AWS::Search

class File
  def bookdesc(isbn)
    look = ItemLookup.new( 'ASIN', { 'ItemId' => isbn } )
    look.response_group = ResponseGroup.new( 'Small' )
    req  = Search::Request.new
    res = req.search(look)
    attr = res.item_lookup_response.items.item.item_attributes
    print attr.author, "『", attr.title, "』(", attr.manufacturer, ")"
  end
end

makeindex.rb

# -*- coding: utf-8 -*-
require './bookdesc'

htmlfile = "index.html"
isbnfile = "ISBN.txt"

open(htmlfile, "w") { |f|
  f.print <<EOS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>Amazonランキング</title>
</head>

<body>
<h1>Amazonランキング</h1>
<ul>
EOS

  open(isbnfile, "r") { |lines|
    while l = lines.gets
      l = l.chomp
      f.print "<li><a href=\"", l, ".html\">"
      f.bookdesc(l)
      f.print "</a> <a href=\"http://www.amazon.co.jp/dp/", l, "\">Amazon</a></li>\n"
    end
  }
  f.print <<EOS
</ul>
</body>
</html>
EOS
}

次のコマンドを実行すると、ISBN.txtをもとにindex.htmlが作成される。ISBN.txtの存在が前提

$ ruby {DIR}/makeindex.rb

$ ruby makeindex.rb

makehtml.rb

# -*- coding: utf-8 -*-
require './bookdesc'

htmlfile = ARGV[0].dup <<  ".html"
open(htmlfile, "w") { |f|
  f.print <<EOS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
EOS
  f.print "<title>Amazonランキング - ", ARGV[0], "</title>\n"

  f.print <<EOS
</head>
<body>
EOS

  f.print "<h1>Amazonランキング - ASBN", ARGV[0], "</h1>"
  f.print "<p>"
  f.bookdesc(ARGV[0])
  f.print "</p>\n"
  f.print "<p><a href=\"http://www.amazon.co.jp/dp/", ARGV[0], "\">Amazonのページ</a></p>\n"
  f.print "<p><a href=\"", ARGV[0], ".csv\">データファイル(CSV形式)</a></p>\n"

  f.print <<EOS
<p><a href=\"index.html\">目次へ戻る</a></p>
<div>
EOS

  f.print "<object data=\"", ARGV[0], ".svg\" type=\"image/svg+xml\" width=\"772.70\" height=\"578.16\">\n"
  f.print "<param name=\"src\" value=\"", ARGV[0], ".svg\" />\n"
  f.print "<img src=\"", ARGV[0], ".png\" alt=\"", ARGV[0], "のランキング\" />\n"

  f.print <<EOS
</object>
</div>
</body>
</html>
EOS
}

次のコマンドを実行すると、指定した{ISBN}をもとに{ISBN}.htmlが作成される

$ ruby {DIR}/makehtml.rb {ISBN}

例。475981339X.htmlを作成

$ ruby makehtml.rb 475981339X

makehtml_batch.sh

DIR=.
ISBN=$DIR/ISBN.txt
RUBY=/usr/local/bin/ruby
while read i
do
$RUBY $DIR/makehtml.rb $i
done <$ISBN
$RUBY $DIR/makeindex.rb

次のコマンドを実行すると、ISBN.txtをもとにindex.htmlと複数の{ISBN}.htmlが作成される。ISBN.txtの存在が前提

$ ./makehtml_batch.sh

Amazonランキングファイルの作成

CSV形式のAmazonランキングファイル{ISBN.csv}を作成する。 {ISBN}.csvは、makehtml.rbで作成。 バッチbookrank_batch.shは、ISBN.txtをもとに複数の{ISBN}.csvを作成する。 任意の日時にbookrank_batch.shまたはbookrank_batch.rbを実行すれば、ランキングが追加で記録される。

bookrank.rb

# -*- coding: utf-8 -*-
require 'amazon/aws/search'
include Amazon::AWS
include Amazon::AWS::Search

il = ItemLookup.new( 'ASIN', { 'ItemId' => ARGV[0] } )
request  = Search::Request.new
time  = Time.now
req = request.search il
curdir = "."
csvfile = File.expand_path(curdir << "/" << ARGV[0].dup <<  ".csv")
open(csvfile, "a") { |f|
  f.print ARGV[0], ",", time.strftime("%Y/%m/%d %H:%M:%S"), ",", req.item_lookup_response.items.item.sales_rank, "\n"
}

次のコマンドを実行すると、{ISBN}.csvに書籍{ISBN}のAmazonランキングが追加される。{ISBN}.csvがない場合は、新規に作成される。

$ ruby {DIR}/bookrank.rb {ISBN}

例。475981339X.csvに書籍475981339XのAmazonランキング追加

$ ruby bookrank.rb 475981339X

bookrank_batch.sh

DIR=~/amazonrank        # cronから実行する場合のため、絶対パス指定
ISBN=$DIR/ISBN.txt
RUBY=/usr/local/bin/ruby
while read i
do
$RUBY $DIR/bookrank.rb $i
done <$ISBN

次のコマンドを実行すると、ISBN.txtをもとに複数の{ISBN}.csvにAmazonランキングが追加される。{ISBN}.csvがない場合は、新規に作成される。

$ ./bookrank_batch.sh

bookrank_batch.rb

bookrank_batch.shのRuby版。Windowsでの使用時などを考慮して作成。

# -*- coding: utf-8 -*-
require 'amazon/aws/search'
include Amazon::AWS
include Amazon::AWS::Search

workdir = "."
isbnfile = "ISBN.txt"

open(isbnfile, "r") { |lines|
  while isbn = lines.gets
    isbn = isbn.chomp
    il = ItemLookup.new( 'ASIN', { 'ItemId' => isbn } )
    request  = Search::Request.new
    time  = Time.now
    req = request.search il
    csvfile = File.expand_path(workdir.dup << "/" << isbn.dup <<  ".csv")
    open(csvfile, "a") { |f|
      f.print isbn, ",", time.strftime("%Y/%m/%d %H:%M:%S"), ",", req.item_lookup_response.items.item.sales_rank, "\n"
    }
  end
}

次のコマンドを実行すると、ISBN.txtをもとに複数の{ISBN}.csvにAmazonランキングが追加される。{ISBN}.csvがない場合は、新規に作成される。

$ ruby bookrank_batch.rb

グラフ

Amazonランキングのグラフを出力。 {ISBN}.csvファイルにbookmark.rbなどで出力したAmazonランキングが格納されていることが前提。

plot.R

library(RSvgDevice)

plotrank <- function(datafile, outfiletype)
{
    df <- read.table(datafile, sep=",")
    x <- strptime(df$V2, "%Y/%m/%d %H:%M:%S")
    y <- df$V3
    yl = ylimit(max(y))
    plot(x, y, type="l", ylim=c(yl, 1), xlab="datetime", ylab="rank", xaxt="n", sub=paste("last update:", max(x), " (", outfiletype, ")", sep=""))
    par(xaxt="s")
    r <- as.POSIXct(round(range(x), "days"))
    axis.POSIXct(1, at=seq(r[1],r[2], by="1 day"), format="%m/%d")
}

ylimit <- function(y)
{
  ylim <- 10
  while(ylim < y) {
    ylim <- ylim * 10 
    while(ylim >= y * 2) {
      ylim <- ylim / 2
    }
  }
  return(ylim)
}

args <- commandArgs(trailingOnly = TRUE)
curdir <- "~/amazonrank/"
png(paste(curdir, args[1], ".png", sep=""), width=720, height=576)
plotrank(paste(curdir, args[1], ".csv", sep=""), "PNG")
invisible(dev.off())
devSVG(paste(curdir, args[1], ".svg", sep=""))
plotrank(paste(curdir, args[1],".csv", sep=""), "SVG")
invisible(dev.off())

次のコマンドで実行

$ R --vanilla --slave --args {ISBN} <{DIR}/plot.R

$ R --vanilla --slave --args 475981339X <plot.R

plot_batch.sh

DIR=~/amazonrank
ISBN=$DIR/ISBN.txt
while read i
do
/usr/local/bin/R --vanilla --slave --args $i <$DIR/plot.R
done <$ISBN

次のコマンドを実行すると、{ISBN}.csvをもとに複数のグラフファイル{ISBN}.pngと{ISBN}.svgが作成される。

$ ./plot_batch.sh

plot_batch.rb

Windows上での利用などを考えて作成

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-

# Rプログラムのパス
rpath = "/usr/local/bin/R"

# 作業用ディレクトリのトップ
workdir = "."

# Rのグラフ出力プログラム
plotfile = workdir.dup << "/plot.R"

# ASINリストファイル
isbnfile = workdir.dup << "/ISBN.txt"

# データCSVファイルを格納するディレクトリ
csvdir = workdir.dup

# HTMLファイルおよびデータCSVファイルを格納するディレクトリ
htmldir = workdir.dup

# プロット時のエラー記録ファイル
ploterrfile = workdir.dup << "/error_plot.log"

open(isbnfile, "r") { |lines|
  begin
    while isbn = lines.gets
      isbn = isbn.chomp
      cmd = rpath.dup << " --vanilla --slave --args " << isbn.dup << " " << csvdir.dup << " " << htmldir.dup << " <" << plotfile.dup << " 2>>" << ploterrfile.dup
      system(cmd)
      raise "ASIN:" << isbn.dup << " コマンド実行エラー" if $? != 0
    end
  rescue => exc
    STDERR.puts exc
    errfile = File.expand_path(ploterrfile.dup)
    open(errfile, "a") { |f|
      f.print "ASIN: ", isbn, "\n"
      f.print "日時: ", Time.now.strftime("%Y/%m/%d %H:%M:%S"), "\n"
      f.print "\n"
    }
    retry
  end
}

次のコマンドを実行すると、{ISBN}.csvをもとに複数のグラフファイル{ISBN}.pngと{ISBN}.svgが作成される。

$ ruby ./plot_batch.rb
  • workdirで作業ディレクトリを指定(初期設定では、カレントディレクトリ".")
  • エラーは、error_plot.logへ出力

cronの設定

定期的にランキングを取得してグラフを更新するため、cronを設定

cronに次の設定をし、毎時0分にランキングを取得し、毎時10分にグラフ更新。ランキング取得にある程度時間がかかるため、10分作業をずらしている。

0 * * * * ~/amazonrank/bookrank_batch.sh
10 * * * * ~/amazonrank/plot_batch.sh