読者です 読者をやめる 読者になる 読者になる

プログラマでありたい

おっさんになっても、プログラマでありつづけたい

あらためてRuby製のクローラー、"anemone"を調べてみた

プログラム crawler

 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の運用例

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例