プログラマでありたい

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

開発用プロキシ、「CocProxy」が便利

 CocProxyというツールがあります。名前の通りプロキシサーバーなのですが、用途が開発用です。置換プロキシと言うらしいですが、これがとっても便利です。例えば、クローラーの開発や、HTML,CSS,JavaScriptの修正をする際に、このCocProxyを利用するとダウンロード待ちのイライラが解消され、効率アップ間違いなしです。ローカルのみで完結するのと、Webへのアクセスが必要とするのでは、スピードが数十〜数百倍違います。一回だけでみたら、数百ミリSecの差ですが累積で考えると大きいですよ。

CocProxyとは?



 Ruby製のProxyServerです。かつ標準ライブラリで動くことを目標としているので、Rubyの環境があれば、ダウンロードして直ぐに使えます。また基本的には1ファイルで完結するので、導入も簡単です。Ruby1.9をベースにしていますが、Ruby 2.0でも特に問題なく動くようです。
 動作としては、Webファイルの取得時に、まずローカルのファイルもしくはキャッシュを確認して、存在したらそれを返します。なければWebファイルを取得にいきます。その際、キャッシュとしても保存するので、2回目以降は取得しないようになります。
 ローカルファイルについてですが、filesというディレクトリを作成してファイルをおいておくと、まずそれを見てくれるようになります。つまり上手くやると、オフラインでも開発できるようになります。一致条件については、下記のルールとなります。

#{File.basename(req.path_info)}",
#{req.host}#{req.path_info}",
#{req.host}/#{File.basename(req.path_info)}",
.#{req.path_info}",

具体的にいうと、 http://blog.takuros.net/entry/2014/04/15/070434 にアクセスする場合、最初に070434というファイルを見て、次にhttpなしのFQDN、ドメイン名+ファイル名、絶対パスの順で照合していきます。下のケースですと、無いので取得にいってキャッシュしていますね。

Checking files/070434
Checking files/blog.takuros.net/entry/2014/04/15/070434
Checking files/blog.takuros.net/070434
Checking files/./entry/2014/04/15/070434
Cached: http://blog.takuros.net/entry/2014/04/15/070434

CocProxyのインストールと起動



 インストールは、簡単です。下記のURLからCocProxyをダウンロードするだけです。
http://svn.coderepos.org/share/lang/ruby/cocproxy/proxy.rb

 起動は、proxy.rbをrubyからキックするだけです。
デフォルトで、5432ポートを利用します。

$ ruby proxy.rb 
Use default configuration.
Port : 5432
Dir  : files/
Cache: true
Rules:
    1. #{File.basename(req.path_info)}
    2. #{req.host}#{req.path_info}
    3. #{req.host}/#{File.basename(req.path_info)}
    4. .#{req.path_info}
Checking files//
Checking files/www.yahoo.co.jp/
Checking files/www.yahoo.co.jp//
Checking files/./
Cached: http://www.yahoo.co.jp/
Checking files/070434
Checking files/blog.takuros.net/entry/2014/04/15/070434
Checking files/blog.takuros.net/070434
Checking files/./entry/2014/04/15/070434
Cached: http://blog.takuros.net/entry/2014/04/15/070434

 ちなみに1ファイルでない、完全版は下記のURLからダウンロードできます。
http://svn.coderepos.org/share/lang/ruby/cocproxy/

CocProxyの利用 With Nokogiri



 CocProxyは、各アプリからの設定でProxyとして指定することで利用できます。Nokogiriの場合は、次のような感じです。

require 'nokogiri'
require 'open-uri'

doc = Nokogiri::HTML(open('http://www.yahoo.co.jp', :proxy => 'http://localhost:5432'))

puts doc.title # => Yahoo! JAPAN

 動作はこの通りです。2回目は、キャッシュを参照しているのが解ります。

Checking files//
Checking files/www.yahoo.co.jp/
Checking files/www.yahoo.co.jp//
Checking files/./
From Cache: http://www.yahoo.co.jp/
Checking files//
Checking files/www.yahoo.co.jp/
Checking files/www.yahoo.co.jp//
Checking files/./
From Cache: http://www.yahoo.co.jp/

 Nokogiriなどを使ってスクレイピングする場合は、なかなか一度で思い通りのデータを取得することは難しいです。何度も試行錯誤すると、当然ながら何度もWebサイトにアクセスすることになります。迷惑掛けていないか心苦しく思うことが多々あります。そんな際に、CocProxyがあれば思う存分試行錯誤できます。

改善点



 ローカルファイル参照とキャッシュ参照の仕組みの二つがあるのは面白いです。一方で自分が使ってて思うのは、わざわざローカルファイルを置くのは面倒臭いというのがあります。初回取得時にキャッシュではなく、ローカルにファイルを置く機構があれば便利かなと思います。また、ローカルファイルの取得もURLのハッシュ値で取得するだけで充分です。また、ユーザーエージェントが取得先の動作に影響する場合があるので、考慮が必要な場合があります。その辺りは作者の方と利用想定が違うと思うので、自分用に改造して使っています。その辺り、次回くらいで紹介します。

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

遂にKindleに登場!!塩野七生の「ローマ人の物語」

 連休中、諸事情によりせっせこパソコンに向かって頑張っています。そんな合間に心躍る発見をしました。かねてから切望していた、塩野七生の「ローマ人の物語」がKindle版に登場しました。ローマ人の物語は、言わずと知れた塩野七生さんの出世作にして代表作。ローマの興りから滅亡まで、人に焦点をあてて追っていくというスタイルで、15巻に及ぶ大作です。その中で産まれた名句は数知れず、むさぼるように読んでいたものです。
 一方で、15冊ということもあり本棚の圧迫率が高く、第何弾目かの本棚圧縮キャンペーンと共にさっていきました。しかし、たまに読みたくなることも多く、Kindel化を待ち望んでいた本の1つです。Kindle PaperWhiteに入れて、風呂に入りながらじっくり読みたいですね。


気にいってる言葉

人材は興隆期にだけ現れるものではない。衰退期にも現れる。しかもその人材の質は、興隆期には優れ衰退期には劣るわけではないのだ。興隆期と衰退期の人材面での唯一のちがいは、興隆期には活用されたのに、衰退期に入ると活用されない、ということだけである。ゆえに、亡国の悲劇とは、活用されずに死ぬしかなかった多くの人材の悲劇、と言ってよいと思う

人は自分の見たいものしか見ない


 今のところ、最初の2冊しか出ていない模様です。他の巻も順次でてくるでしょう。後は、司馬遼太郎の「街道をゆく」をKindle化してくれないかなぁ?


See Also:
今まで読んで良かった本 100冊
本の読み方がもう少し上手くなるの5つのTips
半端でない成毛さんの影響力 「本は10冊同時に読め!」
「読書について」を読まずに本を読むべからず
社会人向けに本の選び方について一言
ジュンク堂書店さんに聞いてみた。「売れると思ったのになぜ売れぬ」の書籍リスト
角川文庫の70%オフセールで物色した本(1/28まで)


ローマは一日にして成らず──ローマ人の物語[電子版]I

ローマは一日にして成らず──ローマ人の物語[電子版]I

ハンニバル戦記──ローマ人の物語[電子版]II

ハンニバル戦記──ローマ人の物語[電子版]II

プラレールの三角ネジ(△ネジ)もプラスドライバー2号サイズも、これ1つで大丈夫。

f:id:dkfj:20140503085552j:plain


 最近子供に貰った京阪電車のプラレール。電池をセットしようにも、自宅のドライバーとネジ穴の大きさが微妙に合わなくて困っていました。精密ドライバーではネジ穴が大きすぎて、普通のドライバーではネジ穴が小さすぎてという状態です。合うドライバーを買おうにも、そもそもプラスドライバーの規格について全く知らないという状態でした。
 プラレールの公式サイトを見ても、ネジ穴のスペックについては何も情報はありません。そこで、タカラトミーのお客様相談室にWebで聞いてみると、2号のプラスドライバーを使えば良いということです。そこで、プラスドライバーの規格を調べてみると、1,2,3というサイズがあるようです。1号,2号,3号もしくは、#1,#2,#3と記載されるようです。#1の方が小さく、#3が大きいサイズです。自宅にあったのは、たぶん#3ということだったのでしょう。(京阪電車以外は、ネジ回せたのですが。。。)



 Amazonさんで買おうとしたのですが、ついでに今までの懸案である三角ねじも対応出来るものを探しました。マクドナルドのハッピーセットで貰った「ゆふいんの森」号の連結部分が壊れたものの、三角ねじの為に修理出来なかったからです。
 Amazonで探すと、2号サイズと三角ドライバーどころか、38種類の特殊なネジ穴に対応したものが1,000円以下で売っていました。iPhoneやMacなどでも利用出来るようです。開ける気はないですが。。。
f:id:dkfj:20140503085722j:plain


 ちなみにプラレールの部品の中で一番壊れやすい連結部分。ちゃんと修理用に交換部品が売っています。単品で買うと送料が高いので、合わせ買いするか近所のお店で探すのがお勧めです。


プラレール 連結部品

プラレール 連結部品

Chefの辞典こと、「Chef活用ガイド コードではじめる構成管理」

 f:id:dkfj:20140421194425j:plain

 献本頂いて随分と時間が経っているのですが、@sawanobolyさんに「Chef活用ガイド コードではじめる構成管理」を頂きました。執筆段階から原稿見せて頂いていたのですが、製本したものを見ると圧巻としか言えない分量の大作です。頂いたものの、分厚すぎて持ち歩けないので即効で電子版で買ったほどです。ある人は、鈍器のようなChef本と呼んでいます。


 そんな分厚いChef本ですが、内容は分厚さに違わずChefの網羅率および詳しさはピカイチです。Chefの概念から始まり、Chef-ServerやSoloなど色々あるChefの利用形態、縁の下の力持ちであるOhaiについてそれぞれ章を費やしています。また、Chefの基本的な使い方が始まり、実際のChef利用の肝となるData Bag、Environment、Roleも詳しく書かれています。その上、開発だけでなくテストや運用フェーズまで含まれています。実際にChefを使う場合に重要になってくるのは、運用フェーズに入ってからです。そういった意味で貴重ですね。
 充実度については目次を見れば直ぐに解ります。およそChefを使う上で必要となる情報は殆ど網羅しています。あれ無いのと思ったのは、(Chefと直接的な関係はないものの)ServerSpecくらいです。きっとそのうち、ServerSpecを専門に取り扱ったものが出てくるでしょう。

目次
第1部 Chefってなに?

第1章 Chefの概要
1.1 Chefとは
1.2 Chefの背景とInfrastructure as Codeの概念

第2章 Chefの利用形態
2.1 Chef-Server/Client構成
2.2 Chef-Solo
2.3 どちらの構成を採用するか
2.4 Chefの機能要素
2.5 Chef-Repo

第3章 Ohai
3.1 Ohai概要
3.2 Ohaiが収集するNode Attribute
3.3 Ohaiの活用
3.4 Ohaiリファレンス

第4章 Chef-Server
4.1 Chef-Serverの種類
4.2 Chef-Serverのコンポーネント
4.3 Chef-Serverの導入
4.4 Chef-Serverの設定

第5章 Chef-Client
5.1 Chef-Clientとは
5.2 Chef-Client主要オブジェクト解説
5.3 Chef-Clientの動作の流れ
5.4 Chef-Clientの導入
5.5 Chef-Clientの設定
5.6 Chef-Clientの実行
5.7 Chef-Clientの機能を拡張する
5.8 イベントディスパッチャ(Chef::EventDispatcher)

第6章 Workstation
6.1 WorkstationとKnife
6.2 Knifeの役割
6.3 Knifeのセットアップ
6.4 Knifeの設定
6.5 knifeコマンドラインの共通オプション
6.6 knifeサブコマンド
6.7 Knifeの基本操作
6.8 Knifeを使ったワークフロー
6.9 WorkstationからNodeを管理する
6.10 Knifeの拡張
6.11 独自のKnifeプラグインを作成する


第2部 もっと詳しく

第7章 Search API
7.1 問い合わせ書式
7.2 検索対象オブジェクト
7.3 問い合わせキー
7.4 問い合わせパターン
7.5 論理演算子
7.6 特殊文字の取り扱い
7.7 Partial Search

第8章 Data Bag
8.1 Data Bagの構成
8.2 Data Bagの作成
8.3 暗号化Data Bag
8.4 Data Bagの利用

第9章 Environment
9.1 Environmentの構成と基本要素
9.2 要素の詳細と記述例
9.3 Environment _defaultについて
9.4 EnvironmentをNodeに適用する

第10章 Role
10.1 Roleの構成と基本要素
10.2 要素の詳細と記述例
10.3 RoleをNodeに適用する

第11章 Cookbookの基本
11.1 Cookbookとは
11.2 Cookbookの基本要素一覧
11.3 Cookbook要素の読み込み順序
11.4 Recipe
11.5 Resource共通項目
11.6 Attribute
11.7 Recipe DSL
11.8 ResourceとProviderの関係
11.9 ディープマージ

第12章 他のCookbookコンポーネント
12.1 Cookbook Metadata
12.2 Cookbook Version
12.3 Cookbook File
12.4 Cookbook Template
12.5 Cookbook Library
12.6 Cookbook Definition

第13章 Lightweight Resources and Providers(LWRP)
13.2 LWRPの組み込み
13.3 簡単なLWRPを作成する
13.4 Lightweight Resource
13.5 Lightweight Provider
13.6 LWRPの活用と作成例

第14章 テスト
14.1 knife cookbook testサブコマンド
14.2 foodcritic
14.3 ChefSpec
14.4 test-kitchen
14.5 Cookbookを継続的インテグレーションする

第15章 Cookbookの管理ツール
15.1 Cookbook管理のアンチパターン
15.2 Berkshelf
15.3 Librarian-Chef

第16章 Chefの運用
16.1 Chefとシステム構築
16.2 Enterprise Chef Server有償アドオン
16.3 事例紹介

付録A Chef-ClientのEventDispatcherイベント一覧
付録B Chef公式LWRP
付録C ResourcesとProvidersの一覧
付録D IaaS系Knifeプラグイン


 あとお勧めのポイントとして、各コマンドについて実行方法や結果などがちゃんと載っているので、実際にどう使えば良いのか解りやすいです。また、巻末の付録としてChef-Clientのイベント一覧や公式のクックブックなど、200ページ以上の付録がついています。全体の1/3くらいを占めるので、付録と呼んでよいのか疑問が呈されるレベルです。そんなこともありまして、Chefが初めての人も、使ってきている人も、是非手元に置いておいて欲しい1冊です。まさしくChefの辞典と呼んでよいです。ただし、持ち運びに向かないので、その場合は電子版を買いましょう。Kindle版と達人出版会の両方があります。個別の内容については、順次紹介していこうと思います。


 ちなみに、@sawanobolyさんは、プライベートでChefに関して色々教えを受けています。その上、最近では仕事上でもChefに限らず運用全般の改善のコンサルタントをして頂くなど、公私ともにお世話になっています。Chef単体の話でなく、運用の最適化という大きな文脈のなかで如何に活用するか、実践的な経験の中で産まれた本です。
 5月に、Chefの第一人者達によるChef実践入門も出ることもあり、目が離せないですね。


Chef活用ガイド コードではじめる構成管理

Chef活用ガイド コードではじめる構成管理

  • 作者: 澤登亨彦,樋口大輔,クリエーションライン株式会社
  • 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
  • 発売日: 2014/04/25
  • メディア: 大型本
  • この商品を含むブログを見る
Chef活用ガイド コードではじめる構成管理 (アスキー書籍)

Chef活用ガイド コードではじめる構成管理 (アスキー書籍)

Chef活用ガイド コードではじめる構成管理【委託】 - 達人出版会



See Also:
サーバ構築・デプロイの自動化の話。或いはChefとCapistranoの素敵な関係
Knife SoloとAmazon EC2で始めるChef Solo
運用視点でChef ServerかChef Solo + Knife Soloのどちらが良いか考えてみた
手動でサーバの設定をすることを禁ずる。入門Chef Solo

RubyでYahoo! キーフレーズ抽出APIを使ってテキストマイニング

 Rubyでキーワード抽出およびTF-IDFでキーワードの重要度の重み付けが出来んかなと思ってGemを漁ってみました。残念ながら、日本語データに関しては、それらしいのは見つかりませんでした。キーワード抽出については、Mecabで形態素解析後に品詞同士のつながりのパターンを考えれば、割りと簡単に作れます。昔作ったことがあるけど、ソースはどっかに旅立たれていたので、改めて作った上でGitHubに登録しようかなぁと思います。またTF-IDFについては、何と言ってもIDFのデータをどうするかがポイントになります。そのあたりのデータ含めて、PerlのLingua::JA::TFIDFを参考に移植させて貰おうかなぁと考えています。

 一方で、てっとり早く上記の2つを実現する方法もあります。Yahooのキーフレーズ抽出APIを利用すると、キーワード抽出と重要度のスコアリングを同時にやってくれます。ただし、24時間あたり5万リクエストと1リクエストあたりのサイズや、レスポンスが上位20件までしか返さないという制約があります。
 それで充分という場合は、さくっと利用するとよいでしょう。下記は、簡単なサンプルです。Yahooのニュースを抜き出して、重要なキーワードを抜き出すといったイメージです。定期的に流していると、ニュースのトレンドの推移などが解ってくるのではないでしょうか?

require 'open-uri'
require 'rexml/document'
require 'nokogiri'

APPLICATION_ID = ENV["YAHOO_API_KEY"]
BASE_URL   = 'http://jlp.yahooapis.jp/KeyphraseService/V1/extract'

$word_list = Hash::new

def request(text)
  app_id = APPLICATION_ID
  params = "?appid=#{app_id}&output=xml"
  url = "#{BASE_URL}#{params}"+"&sentence="+URI.encode("#{text}")
  response = open(url)
  doc = REXML::Document.new(response).elements['ResultSet/']
  doc.elements.each('Result') {|element|
    text = element.elements["Keyphrase"][0]
    score = element.elements["Score"][0] #キーワードの重要度は、とりあえず切り捨て
    $word_list["#{text}"] = $word_list["#{text}"].nil? ? 1 : $word_list["#{text}"]+1
  }
end

def get_urls(page_url)
  urls = Array.new()
  uri = URI.parse(page_url)
  doc = Nokogiri::HTML(open(page_url))
  doc.xpath("//*[@id='main']//ul[@class='list']//a").each do |anchor|
    url = anchor[:href]
    url = uri.merge(url) if not uri =~ /^http/
    urls << url
  end
  return urls
end

def get_headline_text(page_url)
  text = ""
  doc = Nokogiri::HTML(open(page_url))
  if page_url.to_s.match(/dailynews/)
    text = doc.xpath("//*[@id='detailHeadline']").text
  else
    text = doc.xpath("//*[@id='main']//p[@class='hbody']").text
  end
  return text.gsub(/\n/,"")
end

def get(page_url)
  urls = get_urls(page_url)
  urls.each {|url|
    text = get_headline_text(url)
    #p text
    request(text)
  }
end

page_url = 'http://news.yahoo.co.jp/list/?c=economy'
get(page_url)
$word_list.each{ |key,value|
  p "#{key}=#{value}"
}


 サクッと作っただけなので、悪しからず。



See Also:
これはセンスが良い。Lingua::JA::TFIDF - プログラマになりたい
特徴語抽出のあれこれ - プログラマになりたい
キーワード抽出モジュール Lingua::JA::Summarize - プログラマになりたい


参照:
Lingua::JA::TFIDF - search.cpan.org
テキスト解析:キーフレーズ抽出API - Yahoo!デベロッパーネットワーク

Ruby製のクローラー Anemoneでストレージをファイルに変更する

 シリーズの如く何度かAnemoneの話を書いています。Anemoneは割りと小さなモジュールなので、ソースを読めば直ぐに解ることが多いです。一方で、ドキュメントが充実しているとは言い難いので、少し違うことをしようとすると、ソース嫁という状態になります。今回のエントリーもそんな話の一つです。

デフォルトストレージを変更する



 Anemoneのデフォルトストレージのデフォルトオプションは、nilです。この場合、どういう動作をするかというと、Anemone::Storage.Hashが指定されます。つまりメモリーです。当然ながらクロール量に比例してメモリの使用量が増えます。そうすると、当然動いている端末もしくはサーバのメモリーを圧迫していくという結果になります。
 当然望ましくないので、本格的に使用する場合はストレージを変更した方が良いでしょう。Anemoneはストレージのオプションが豊富で、PStore(ファイル)やMongoDB,Sqlite3などを始めとして、TokyoCabinet,KyotoCabinet,Redisなどが使えます。mongoなどについては、使ってみたよという事例がちょくちょく出ているのですが、一番単純なPStore(ファイル)についての情報は見たことがないです。そんなこんなで、ちょこっと書いてみます。

AnemoneでPStoreを利用する



 使い方は簡単です。起動時のオプションの中の:storegeで、Anemone::Storage.PStoreを指定するだけです。簡単ですね。しかし、それで起動すると、以下のようなエラーが発生します。

pstore.rb:11:in `initialize': wrong number of arguments (0 for 1) (ArgumentError)
        from C:/Ruby200/lib/ruby/gems/2.0.0/gems/anemone-0.7.2/lib/anemone/stora
ge.rb:13:in `new'
        from C:/Ruby200/lib/ruby/gems/2.0.0/gems/anemone-0.7.2/lib/anemone/stora
ge.rb:13:in `PStore'
        from anemone-pstore.rb:7:in `<main>'

 端的にいうと、引数が足りませんよということです。どこに保存するかの情報がないということでしょう。しかし、オプションでは、ファイル等の指定するところがありません。どうしたら良いのでしょうか?pstore.rbを見てみると、initializeでファイルを引数としています。

pstore.rb

      def initialize(file)
        File.delete(file) if File.exists?(file)
        @store = ::PStore.new(file)
        @keys = {}
      end

 親クラスのpage_store.rbを見ると、次のようになっています。オプション変数をそのまま渡しているっぽいですね。

    def initialize(storage = {})
      @storage = storage
    end

 呼び元のcore.rbは、どうしているかというと、次の通りです。

storage = Anemone::Storage::Base.new(@opts[:storage] || Anemone::Storage.Hash)

 ということで、次のような感じで保存先のファイルと一緒にオプションで指定したら良いです。それだけです。

storage => Anemone
:Storage.PStore('file.txt')


 下記が、それを踏まえたソースです。この場合、ファイルは実行したディレクトリに作成されます。ちなみにファイル名は.txtにしていますが、Mashal.dmp形式のバイナリになっている為、殆ど読めません。

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

opts = {
  :storage => Anemone::Storage.PStore('file.txt'),
  :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("/head/title/text()").to_s if page.doc
  end
end

 一方でストレージに保存しているものの、それを再利用するかというと別の話です。PStoreのイニシャライザを見ての通り、既存のファイルがあれば消してから作成しています。このあたりを何とかしたいのであれば、ライブラリに手を加えるしかないですね。訪問済みのページは訪れないとか、一定期間経過した場合のみ訪れるとか。