Nokogiriを使ってみた

fizzbuzzをやってみた - うんたらかんたらRuby - Rubyist
anarchy golf - FizzBuzz
golfでは、67位なんだけど早さでは何位なんだろうかと思って
scrapeしてsortしてみようと思い立った。



ここら辺(Ruby Scraping - FrontPage)をみながら
xpathとかcssセレクタとかを簡単に扱えるらしい。」ということで
Nokogiri(tenderlove's nokogiri at master — GitHub)をインストールしてみることに。

インストール

gem install nokogiri
で簡単に入れれるらしい。

Building native extensions.  This could take a while...
ERROR:  While executing gem ... (Gem::Installer::ExtensionBuildError)
    ERROR: Failed to build gem native extension.


でもエラー。。
gemを更新して再度やったらうまくいった。
gem update --system

早速使ってみる

xpathで簡単にデータが取得できるのはわかったけど、
このhtml(anarchy golf - FizzBuzz)には
idとかclassとかが適切に設定されていないので
Rubyの結果だけを抜き出せるようなxpathが書けない。


htmlの構造は↓みたいな感じになってる。
 html
 body
 a Ruby(言語名)....
 table Rubyの結果
 a Perl(言語名)....
 table Perlの結果
  ・
  ・
  ・

悩んだ結果、「言語名のaタグ」の後に「結果のtableタグ」が必ずくるので
それぞれ別々の配列に取得して、使う時に同じindexを使えば
欲しい言語の結果が取り出せそう。

書いてみた

require 'rubygems'
require 'nokogiri'
require 'open-uri'
  
abort('no argument err.') if ARGV.size == 0

doc = Nokogiri::HTML(open('http://golf.shinh.org/p.rb?FizzBuzz#Ruby'))

#言語名を配列にセット
prog_names = []
doc.xpath('/html/body/h3/a[@href]').each do |nsa|
  href = ""
  href = nsa.get_attribute("href")
  if href =~ /\/l\.rb\?.*/
    prog_names << nsa.inner_text
  end
end

#tableタグを配列にセット
#tables最後のtable要素は「Language Ranking」が含まれる。(言語名より1つ多い)
tables = []
tables = doc.xpath('/html/body/table')

rank_info = Hash.new
prog_names.each_with_index do |prog, i|
  if ARGV[0].upcase == prog.upcase
    p prog
    tables[i].xpath('tr').each do |tr|
      user = tr.xpath('td[2]').inner_text
      time = tr.xpath('td[4]').inner_text
      if user != "" && time != ""
        rank_info[user] = time
      end
    end
  end
end

puts "Rank User Time"
puts "------------------------------"
rank_info.sort_by {|key, val| val}.each_with_index{|x, n| puts "#{n + 1} #{x[0]} #{x[1]}"}

引数で指定した言語の結果を早さ順に並べ替えます。

結果

$ ruby noko.rb ruby
"Ruby"
Rank User Time
------------------------------
1 leonid (alnum) 0.0003
2 Duane Bailey 0.0071
3 Matthew 0.0118
4 crazyjimbo 0.0171
5 khtokage 0.0197
6 yuya 0.0296
7 rawwell 0.0302
8 flagitious(alnum) 0.0344
9 rickrickrick 0.0355
10 shrapnel 0.0373
11 rochefort 0.0373
12 Steve Cooper 0.0432
13 mokos 0.0487
14 lw 0.0491
15 smly 0.0492


以下略

なんとか、11位(同順で10位。ここは考慮できてない)。

疑問点

今回は、sortにhashを利用したけど、構造体の配列みたいなので本当はやりたい。
でもよくわかんない(2次元配列とかのソートが)。

user名でソートとかを簡単にするには、多次元配列のソートを書かないといけないのかな。
簡単にできる方法はないのかな。