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に従わないようになっています。
# don't obey the robots exclusion protocol :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を利用したタイプでも作ってみようと思います。
まとめ
ざっとanemoneの構造を確認してみました。ページ取得の部分は、coreの部分と密な結合になっているようです。反対にページ解析やデータ保存については、比較的疎結合になっています。もともと、ページ取得部分だけ取り替え可能を知りたくて調べました。http.rbを呼び出している部分を置き換えれば出来なくもなさそうですが、それよりもページ解析機能やデータ保存機能を移植する方が楽そうです。
anemoneは比較的小さなライブラリですが、色々な要素があります。ソースを順番に読んでいくと、中々勉強になりました。興味がある方は、時間があるときに一度読んでみてはいかがでしょうか? enjoy!!
PR
anemoneの解説を含めて、Rubyによるクローラー開発の本を書きました。
クローラーの概念から実際の構築・運用手順を網羅しています。
See Also:
オープンソースのRubyのWebクローラー"Anemone"を使ってみる
JavaScriptにも対応出来るruby製のクローラー、Masqueを試してみる
複数並行可能なRubyのクローラー、「cosmicrawler」を試してみた
参照:
chriskite/anemone · GitHub
Rubyist Magazine - 標準添付ライブラリ紹介 【第 6 回】 委譲
anemone RDoc
PythonとかScrapyとか使ってクローリングやスクレイピングするノウハウを公開してみる! - orangain flavor
Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例
- 作者: るびきち,佐々木拓郎
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/08/25
- メディア: 大型本
- この商品を含むブログ (4件) を見る