プログラマでありたい

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

Ruby製のクローラー AnemoneでストレージをSQLite3, MongoDBに変更する

 Anemoneのストレージを変更するシリーズ、ついでにSQLite3とMongoDBも書いておきます。

AnemoneでSQLite3を利用する



 AnemoneでSQLite3を利用するには、オプションで:storage => Anemone::Storage::SQLite3()と指定するだけです。引数を渡さなければ、anemone.dbという形式のsqlite3ファイルが作成されます。プログラムとしては、次のような形ですね。

require 'anemone'
 
urls = []
urls.push("http://www.yahoo.co.jp")

opts = {
  :storage => Anemone::Storage::SQLite3(),
  :obey_robots_txt => true,
  :depth_limit => 0
}

Anemone.crawl(urls, opts) do |anemone|
  anemone.on_every_page do |page|
    puts page.url
    p page.doc.xpath("//title/text()").to_s if page.doc
  end
end

 SQLite3を使った場合の嬉しいところは、2回目以降の起動でも既読スキップが有効になる点です。これは、初期化処理でPStoreと違いファイルの削除なども行わず、テーブル作成時も既存テーブルがあればちゃんと残している点です。

      def initialize(file)
        @db = ::SQLite3::Database.new(file)
        create_schema
      end

      def create_schema
        @db.execute_batch <<SQL
          create table if not exists anemone_storage (
            id INTEGER PRIMARY KEY ASC,
            key TEXT,
            data BLOB
          );
          create index  if not exists anemone_key_idx on anemone_storage (key);
SQL
      end

 一方で、既読スキップはURLのみで判断しています。訪問後、N日の場合は再取得といった処理はできません。何故なら、前回取得時の日時情報を持っていないからです。

      def has_key?(url)
        !!@db.get_first_value('SELECT id FROM anemone_storage WHERE key = ?', url.to_s)
      end

 この辺りは、少し改善したいですね。

AnemoneでMongoDBを利用する



 AnemoneでMongoDBを利用する場合も、オプションで:storage => Anemone::Storage::MongoDB()と指定するだけです。前提として、ローカルにMongoDBのデーモンが起動されている必要があります。

require 'anemone'
 
urls = []
urls.push("http://www.yahoo.co.jp")

opts = {
  :storage => Anemone::Storage::MongoDB(),
  :depth_limit => 0
}

Anemone.crawl(urls, opts) do |anemone|
  anemone.on_every_page do |page|
    puts page.url
    puts page.doc.xpath("//title/text()").to_s if page.doc
  end
end

 MongoDBで利用するDB名やCollection名も変更可能ですが、DB名の変更の方法は微妙です。mongoのGemを呼び元でrequireして、DBを作成の上で渡す必要があります。

require 'anemone'
require 'mongo'

opts = {
  :storage => Anemone::Storage::MongoDB(Mongo::Connection.new.db('crawler'),"documents")
}

 AnemoneのStorageクラスで、下記のような処理をしている為です。

    def self.MongoDB(mongo_db = nil, collection_name = 'pages')
      require 'anemone/storage/mongodb'
      mongo_db ||= Mongo::Connection.new.db('anemone')
      raise "First argument must be an instance of Mongo::DB" unless mongo_db.is_a?(Mongo::DB)
      self::MongoDB.new(mongo_db, collection_name)
    end

 こんな感じの方が、使い易いのではと思います。

def self.MongoDB(db_name = nil, collection_name = 'pages')
  db_name ||= "anemone
  mongo_db = Mongo::Connection.new.db(db_name)

 ちなみに、AnemoneのMongoDBは、初期化処理でCollectionを削除しています。その為、2回目以降の処理で既読スキップは出来ません。(必要であれば、コメントアウトするなり対処しましょう。)

      def initialize(mongo_db, collection_name)
        @db = mongo_db
        @collection = @db[collection_name]
        @collection.remove
        @collection.create_index 'url'
      end