rubyの最近のブログ記事
この前作ったライブラリにクラスを追加して、
ニコニコ動画の「注目のタグ」一覧も毎日取ってみています。
上位は全然変わらないみたいだけどね。
これです→http://www.nicovideo.jp/tag
もうちょっと人にやさしく配置してくれるとうれしいのですが・・・
スクレイパー的にはありがたいんだけどw
Rubyスクリプトは下記になります。
nico.rbがライブラリで、tag.rbがクライアントプログラムです。
# nico.rb
require 'net/https'
require 'kconv'
require 'rubygems'
require 'hpricot'
module Nico
class Tags
def read (sid)
@tags = []
Net::HTTP.start("www.nicovideo.jp", 80) do |http|
response = http.get(
"/tag",
"Cookie" => "user_session=user_session#{sid}")
doc = Hpricot(response.body.tosjis)
doc.search("a/nobr").each do |e|
@tags << e.inner_text
end
end # start
end # def
def save (fname)
File.open(fname, "w") do |file|
@tags.each {|e| file.puts e }
end # open
end # def
end
def self.get_sid (mail, password)
sid = nil
https = Net::HTTP.new("secure.nicovideo.jp", 443)
https.use_ssl = true
https.start do |w|
data = "next_url=&mail=#{mail}&password=#{password}"
response = w.post("/secure/login?site=niconico", data, "Content-Length" => "#{data.length}")
sid = $1 if response["Set-Cookie"] =~ /user_session=user_session([0-9_]+)/
end
sid
end
end # module
#! ruby -Ks
# tag.rb
# ライブラリロード
require 'date'
require 'nico'
today = Date.today.strftime("%Y%m%d")
sid = Nico::get_sid(メールアドレス, パスワード)
t = Nico::Tags.new
t.read sid
t.save "tag_#{today}.csv"
ちなみに9/2~9/18で比較して上位10位までは変化なしでした。
1. ゲーム
2. アニメ
3. 音楽
4. エンターテイメント
5. MAD
6. 歌ってみた
7. プレイ動画
8. スポーツ
9. アイドルマスター
10. その他
ところで何順で並んでいるんだろう?
タグ新規登録数なのか、新規投稿動画のタグなのか、
マイリストやコメントや再生に紐付いてるタグなのか、
総数なのか、差分なのか、気になります。
昨日の続きです。
もう一回挟もうとしたんだけど
面倒臭くなったのでのでCSV出力までやることにしました。
やりたいことは、
「ニコニコ動画のランキング一覧をファイルに書き出す」
だけです。
まずは書き出す情報を定義します。
module Nico
class Douga
attr_accessor(
:title, # タイトル
:url, # URL
:posted, # 投稿日
:length, # 動画の長さ(秒)
:mylist, # マイリスト登録数
:view, # 再生数
:res # コメント数
)
def self.head_csv
"URL,タイトル,投稿日,動画の長さ,マイリスト登録数,再生数,コメント数"
end
def to_csv
cell = Array.new(7)
cell[0] = @url
cell[1] = "\"#{@title}\""
cell[2] = @posted.strftime("%Y/%m/%d %H:%M:%S")
cell[3] = @length
cell[4] = @mylist
cell[5] = @view
cell[6] = @res
cell.join(",")
end
end # class
HTMLから取得したデータの動画情報1件分がDougaクラスのインスタンスに対応します。
データの設定はアクセッサ経由で外部でやります。
to_csvでCSVデータ1行分の文字列を返します。
続いてランキング一覧をクラス化します。
今回利用するランキングは「今までの合計」に限定しました。
取得するデータのマイリスト数、再生数、コメント数は投稿時点からの累積になります。
ランキングの種類としてはマイリスト順、再生順、コメント順の3パターンがありますが、
コンストラクタ(initialize)に種類(kind)を引数として渡すことで対応します。
マイリスト順ランキング:http://www.nicovideo.jp/ranking/mylist/total/all?page=1
再生順ランキング:http://www.nicovideo.jp/ranking/view/total/all?page=1
コメント順ランキング:http://www.nicovideo.jp/ranking/res/total/all?page=1
URLを見比べると、/raking/○○/total/allとなっているので、
○○の部分がmylistならマイリスト、viewなら再生、resならコメントだとわかります。
ランキングは1ページに100件まで載っています。
そしてページが10まであります。なので1000位まで取れるということになります。
Rankingクラスには一覧データ取得のreadメソッドと、
取得したデータをファイル出力するsaveメソッドを定義します。
class Ranking
def initialize (kind)
@kind = kind
@dougas = Array.new(1000) {Douga.new}
end
def read (sid)
Net::HTTP.start("www.nicovideo.jp", 80) do |http|
(1..10).each do |page|
warn "read #{@kind}:#{page}..."
response = http.get(
"/ranking/#{@kind}/total/all?page=#{page}",
"Cookie" => "user_session=user_session#{sid}")
doc = Hpricot.parse(response.body.tosjis)
i = (page - 1) * 100
doc.search("h3/a").each do |e|
@dougas[i].title = e.inner_text
@dougas[i].url = e[:href]
i += 1;
end
i = (page - 1) * 100
doc.search('p.TXT12[@style="color:#666;"]').each do |e|
@dougas[i].mylist = /([0-9,]+)/.match(e.inner_text)[1].delete(",").to_i
i += 1;
end
i = (page - 1) * 100
n = 0;
doc.search('td/p.TXT12/strong').each do |e|
case n % 4
when 0: @dougas[i].posted = DateTime.strptime(e.inner_text, "%Y年%m月%d日 %H:%M:%S")
when 1: @dougas[i].length = $1.to_i * 60 + $2.to_i if /(\d+)分(\d+)秒/ =~ e.inner_text
when 2: @dougas[i].view = e.inner_text.delete(",").to_i
when 3: @dougas[i].res = e.inner_text.delete(",").to_i
i += 1;
end
n += 1
end
end # each
end # start
end # def
def save (fname)
File.open(fname, "w") do |file|
file.puts Douga.head_csv
@dougas.each {|d| file.puts d.to_csv }
end # open
end # def
end # class
readメソッドが実際にHpricotを使ってスクレイピングしている部分になります。
Hpricotで使われる主なメソッドはsearchとatです。
searchはマッチする要素を全て取得します。
atはマッチする最初の要素のみ取得します。
まず1回目のsearchでh3タグの中のaタグを全てピックアップし、
タグ中の文字列を動画のタイトルとし、href属性をURLと判断します。
Hpricotの構文ではタグの階層を下っていくのに「/」が使われます。
i = (page - 1) * 100
doc.search("h3/a").each do |e|
@dougas[i].title = e.inner_text
@dougas[i].url = e[:href]
i += 1;
end
2回目のsearchでマイリスト数を取得します。
ちょっとややこしいですが、「p.TXT12[@style="color:#666;"]」の部分です。
「.」はCSS同様class属性を限定します。
また、[@hoge="..."]でhoge属性の値が...なものに限定します。
よって「p.TXT12[@style="color:#666;"]」は<p class="TXT12" style="color:#666;">...</p>にマッチします。
そしてマッチした文字列から数値部分のみ切り出してカンマを削除して整数変換して保持します。
i = (page - 1) * 100
doc.search('p.TXT12[@style="color:#666;"]').each do |e|
@dougas[i].mylist = /([0-9,]+)/.match(e.inner_text)[1].delete(",").to_i
i += 1;
end
3回目のsearchで残りの、投稿日時、動画の長さ、再生数、コメント数をまとめて取得します。
この4つはHTMLで同じレベルでタグ記述されているため、全部持ってきて、何個目かによって判断させています。
こういう時はおなじみのパターンですが、ループの中でインデックスの余りをとって処理を振り分けます。
1個目だったら投稿日時、2個目だったら動画の長さ、3個目だったら再生数、4個目だったらコメント数です。
でそれぞれ都合の良いように加工してから保持します。
Hpricotのタグ表現は、「td/p.TXT12/strong」となっているので、
tdタグの中の、pタグでclassがTXT12の中の、strongタグにマッチします。
i = (page - 1) * 100
n = 0;
doc.search('td/p.TXT12/strong').each do |e|
case n % 4
when 0: @dougas[i].posted = DateTime.strptime(e.inner_text, "%Y年%m月%d日 %H:%M:%S")
when 1: @dougas[i].length = $1.to_i * 60 + $2.to_i if /(\d+)分(\d+)秒/ =~ e.inner_text
when 2: @dougas[i].view = e.inner_text.delete(",").to_i
when 3: @dougas[i].res = e.inner_text.delete(",").to_i
i += 1;
end
n += 1
end
Nicoモジュールの最後に、セッションID取得処理を関数化します。
メールアドレスとパスワードを引数にとってセッションIDを返します。
中身は昨日のものと同じです。
def self.get_sid (mail, password)
sid = nil
https = Net::HTTP.new("secure.nicovideo.jp", 443)
https.use_ssl = true
https.start do |w|
data = "next_url=&mail=#{mail}&password=#{password}"
response = w.post("/secure/login?site=niconico", data, "Content-Length" => "#{data.length}")
sid = $1 if response["Set-Cookie"] =~ /user_session=user_session([0-9_]+)/
end
sid
end
end # module
ここまでのコードがNicoモジュールで、nico.rbという1ファイルになります。
次がこのモジュールを利用する側のコードです。
#! ruby -Ks
require 'net/https'
require 'date'
require 'kconv'
require 'rubygems'
require 'hpricot'
require 'nico'
today = Date.today.strftime("%Y%m%d")
sid = Nico::get_sid(メールアドレス, パスワード)
["mylist", "res", "view"].each do |kind|
r = Nico::Ranking.new(kind)
r.read sid
r.save "#{kind}_#{today}.csv"
end
これでマイリスト登録数・再生数・コメント数の3つの一覧CSVファイルが出力されます。
タスクスケジューラやcronで毎日回して、1ヶ月くらいとってから
Excelで集計すればなんか面白いグラフが見れるんじゃないかな、と期待しています。
釣り師的なタイトルで申し訳ないです。
今仕事でリスト取得ツールを作っているのですが、
その応用としてニコニコを実験場としたサンプルを作ってみることにします。
スクレイピングというのはHTMLパースのことですね。
RubyのスクレイピングライブラリとしてはHpricotがあります。
Hpricotの名前の由来は不明ですが、エイチプリコットと呼んでいます。
このHpricotはCSS風の書き方で気軽にスクレイピングできるのでとても使いやすいです。
とりあえずいきなりコード。
ニコニコのデフォルトランキング、本日のマイリスト登録数ランキング100から
動画のタイトルをひっぱってくるサンプルです。
#! ruby -Ks
require 'net/https'
require 'kconv'
require 'rubygems'
require 'hpricot'
sid = nil
https = Net::HTTP.new("secure.nicovideo.jp", 443)
https.use_ssl = true
https.start do |w|
data = "next_url=&mail=メールアドレス&password=パスワード"
res = w.post("/secure/login?site=niconico", data, "Content-Length" => "#{data.length}")
sid = $1 if res["Set-Cookie"] =~ /user_session=user_session([\d_]+)/
end
Net::HTTP.start("www.nicovideo.jp", 80) do |w|
res = w.get("/ranking/mylist/daily/all", "Cookie" => "user_session=user_session#{sid}")
doc = Hpricot.parse(res.body.tosjis)
doc.search("h3").each {|e| warn e.inner_text }
end
実行する前に、シェルで「gem install hpricot」とたたいて、
Hpricotをインストールしておいてください。
流れとしては、
1. 認証用サーバ(secure.nicovideo.jp)に接続
2. アカウント情報(メールアドレスとパスワード)を送信
3. クッキー(セッションID)を取得
4. 本サーバ(www.nicovideo.jp)に接続
5. 取得したセッションIDをくっつけて欲しいページをリクエスト
6. 欲しいページが手に入ったら煮るなり焼くなり
6番がHpricotでスクレイピングしてるところなんです。
上のサンプルだと次の2行になります。
doc = Hpricot.parse(res.body.tosjis)
doc.search("h3").each {|e| warn e.inner_text }
1行目で取得したHTMLをSJISにしてからスクレイピング用のHpricotオブジェクトに変換しています。
2行目で、対象HTMLの中からh3タグのみをピックアップして、h3の中身を文字列化して表示しています。
h3が動画のタイトルを表しているのはどうしてわかるのかと言うと、
該当ページのHTMLソースを見たからです。
当然、ニコニコの仕様が変われば動かなくなることもありえます。
この辺は人力です。人力なやり方のことをスクレイピングと言うのでしょう、多分。
今回のサンプルはただとってきて表示しただけなので何の役にも立ちませんが、
次回はもうちょっと詳しい情報を取ってきて加工してみることにします。
その次あたりで毎日CSV出力させてグラフ化とかしてみたいと思います。
Rails勉強会に初参加してきました。
前半は初心者セッション。
Migrationを使えば、テーブルの初期化でSQL書かないでいいなんて知らなかったorz
RadRailsはEclipse RCP+RDTだったなんて知らなかったorz
次期Mac ServerにはRailsが標準で入るらしい。
Macのエディタtextmateがいいらしい。Mac欲しい。
script/console使おう自分。
後半はTDDB。
BDDというのはTDDの言い方を変えただけらしい。
でもそれが重要だとか。あとでちゃんと調べよう。
redgreenでテスト結果に色付けできる。
autotest(zentest?)でファイル変更時に自動でテストを走らせる。
ところでgemって何? とは聞けない罠…
懇親会はZEST。
なんか雰囲気かっこいいのでまた行きたいです。
技術的に刺激的な話をいっぱい聞けて良かったです。
なんかこう、ちっさいことで悩んでるなぁ、
そんなもんちゃちゃっと終わらせて次進まんと!
ってな感じでテンション上がりました。
ただ風邪なのに無理したせいで、
月曜日は会社休んで寝込んでしまいました。
眠っては本読んでの繰り返しってのも、たまにはいいよね。
まだ喉が痛くて、鼻が詰まって、脳力が落ちてる。
Ruby on Railsの統合開発環境RadRailsを日本語化する際の手順。
環境
・Windows XP
・Rad Rails v0.7.1
Rubyの一番のウリと言えばブロック引数メソッドでしょう。
メソッド(関数)とクロージャとブロックの関係についてちょっと掘り下げてみました。
たまにはプログラム関係のことでも書きましょうかな。
言語の特徴を見るための例題ってのはいろいろありますが、
パスカルの三角形はなかなかいい基準ではないかと思います。
Rubyでパスカルの三角形を書いてみると、こんな感じ。
