3年ほど前に、Ruby製のクローラー"anemone"を紹介しました。その当時から完成度が高く、Rubyでクローラーを使う場合はanemoneを利用してきました。最近、他に新しくて良いのがないか調べましたが、機能面の網羅性という意味でanemoneを超えるものは見つけられませんでした。そこで改めてanemoneのソースを読んでみたところ、クローラーが必要とする機能を必要最小限で実装され、やはり中々良い出来です。冬休みの宿題ではないですが、勉強の意味を兼ねてソースを追っていくことにします。
Anemoneが利用しているライブラリ一覧
anemoneが利用しているライブラリは、4種類に分類できます。
- Ruby標準or一般的なライブラリ
- データ取得で利用しているライブラリ
- データ解析で利用しているライブラリ
- データ保存で利用しているライブラリ
この分類別に構造をみるとわかりやすいので、順番に追っていきます。
Ruby標準or一般的なライブラリ
require 'rubygems'
require 'delegate'
require 'forwardable'
require 'optparse'
require 'thread'
delegateとforwardableは、メソッドの委譲を行うruby標準のライブラリです。optparseは、コマンドラインのオプションを取り扱うためのライブラリです。threadは並行プログラミングを行う為のライブラリです。特筆すべきところは、余りありません。唯一気になったのは、delegateとforwardableの併用についてです。anemoneでは、cookie_storeの実装の部分のみdelegateを使い、データ保存の部分で各ストレージ(kyoto_cabinet,pstore,tokyo_cabinet)についてはforwardableを使っています。この2つのモジュールの選択のポイントについては、よく解っていません。ストレージ機能の実装時期が2年ほど後ということもあり、流儀が変わった可能性もあります。もしくは、移譲について明示的に指定するかどうかの所で、移譲するメソッド数による判断の可能性もあります。
データ取得機能の構造
require 'net/https'
require 'webrick/cookie'
require 'robotex'
データ取得機能については、core.rbとhttp.rbで実装されています。データ取得の為のライブラリとしては、通信部分には標準のnet/httpsを利用しています。cookieの取扱は、webrick/cookieを利用しています。名前から解るように、Webサーバー用フレームワークのWebrickを利用してCookieの処理を行っているのですね。そして、robotexです。このライブラリは、anemoneの作者であるChris Kiteによるライブラリです。robots.txtの判定を別モジュールとして外出しにしています。この部分は、自分でクローラーを作成する場合にも利用出来ます。使い方は、anemoneでは次のようになっていました。
デフォルトの設定。robots.txtに従わないようになっています。
:obey_robots_txt => false,
引数でrobots.txtに従うように設定した場合、変数@robotsを作成しています。
@robots = Robotex.new(@opts[:user_agent]) if @opts[:obey_robots_txt]
Robotexモジュールの使い方は、次の通りです。robots.txtに従う場合、Robotexモジュールのallowdメソッドでリンク先を取得可能かの確認をしています。(再度オプションの:obey_robots_txtを見に行っているのは、微妙な気がします。)
def allowed(link)
@opts[:obey_robots_txt] ? @robots.allowed?(link) : true
rescue
false
end
このallowdメソッドが、実際使われている所です。visit_linkメソッドでAnd条件で訪問可能か確認しています。
def visit_link?(link, from_page = nil)
!@pages.has_page?(link) &&
!skip_link?(link) &&
!skip_query_string?(link) &&
allowed(link) &&
!too_deep?(from_page)
end
この実装であれば、同一サイトでも都度robots.txtを確認するような気がします。念の為、Robotexモジュールの実装も確認してみます。結論的には、一度確認したサイトについては、robots.txtの再取得をしないような作りになっています。一安心です。
def allowed?(uri, user_agent)
return true unless @parsed
〜略〜
end
データ解析機能の構造
require 'nokogiri'
require 'ostruct'
データ解析機能については、page.rbで実装されています。そして殆どの処理の実態は、nokogiriでのパースです。その為、anemoneの利用元の方で、nokogiriを使って自由に加工出来ます。
ex)利用例
Anemone.crawl("http://www.example.com/") do |anemone|
anemone.on_every_page do |page|
title = page.doc.xpath("//head/title/text()").first.to_s if page.doc
puts title
end
end
HTML解析の中のリンクの検索については、aタグ中のhrefを検索しているだけのようです。FormやJavaScript等で飛び先を指定しているのは、取れません。クローリングで迷惑を掛けることを防止するには、このaタグのみ取得する実装が賢明だと思います。
def links
return @links unless @links.nil?
@links = []
return @links if !doc
doc.search("//a[@href]").each do |a|
u = a['href']
next if u.nil? or u.empty?
abs = to_absolute(u) rescue next
@links << abs if in_domain?(abs)
end
@links.uniq!
@links
end
データ保存機能の構造
require 'kyotocabinet'
require 'mongo'
require 'tokyocabinet'
require 'pstore'
require 'redis'
require 'sqlite3'
anemoneは、取得したデータの保存先の選択肢が豊富です。初期は、sqlite3のようにRDBMSや、pstoreなどの標準のファイルオブジェクトのみでした。いまでは、tokyocabinet/kyotocabinet・redisのようなキーバリューストアや、mongoDBのようなNoSQLにも対応するようになっています。履歴を見ていると、利用者からのPullRequestがマージされている模様です。
構造的には、anemone/storageにストレージごとの実装を追加するという形になっています。割と簡単に追加できそうなので、試しにAmazon S3を利用したタイプでも作ってみようと思います。