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。
なんか雰囲気かっこいいのでまた行きたいです。

技術的に刺激的な話をいっぱい聞けて良かったです。
なんかこう、ちっさいことで悩んでるなぁ、
そんなもんちゃちゃっと終わらせて次進まんと!
ってな感じでテンション上がりました。

ただ風邪なのに無理したせいで、
月曜日は会社休んで寝込んでしまいました。
眠っては本読んでの繰り返しってのも、たまにはいいよね。
まだ喉が痛くて、鼻が詰まって、脳力が落ちてる。

RadRailsの日本語化

| | コメント(10)

Ruby on Railsの統合開発環境RadRailsを日本語化する際の手順。

環境
・Windows XP
・Rad Rails v0.7.1

Rubyの一番のウリと言えばブロック引数メソッドでしょう。
メソッド(関数)とクロージャとブロックの関係についてちょっと掘り下げてみました。

パスカルの三角形

| | コメント(6)

たまにはプログラム関係のことでも書きましょうかな。

言語の特徴を見るための例題ってのはいろいろありますが、
パスカルの三角形はなかなかいい基準ではないかと思います。
Rubyでパスカルの三角形を書いてみると、こんな感じ。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちrubyカテゴリに属しているものが含まれています。

前のカテゴリはprogrammingです。

次のカテゴリはschemeです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 4.01