プログラマでありたい

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

新幹線のWifiは、どうやってつながるのか?或いは、なぜ遅いのか?

f:id:dkfj:20140604102324j:plain


 東海道新幹線の東京~大阪間で提供されているWifiサービス。スマフォのテザリングが規制によって使えない時に、今でも使っています。しかし、御存知の通り遅いのです。あと、何故、東京~大阪間しか使えないのか。微妙に疑問に思っていました。答えは、すべて通信方式にありました。

 ずばり、LCX(同軸漏洩ケーブル)の余った帯域を、客用に開放しているだけだからです。同軸漏洩ケーブルとは、同軸ケーブルを使った通信方式です。同軸ケーブルから意図的に信号を漏らすことにより、ケーブルの周辺だけ通信できるという方式です。その方式ゆえにトンネル内でも通信できます。実際、もともとの用途は、新幹線の業務用の通信用です。それ以外にも、新幹線内の公衆電話等でも利用していたようです。
 そして、東海道新幹線では、そこを通る通信をデジタル化しています。デジタル化に伴い、ある程度帯域が余ったの、Wifiサービスとして提供しているのです。

 しかし、無限の帯域が余っている訳ではありません。新幹線1編成あたり最大2MBpsとのことです。1両ではないです。1編成です。のぞみであれば、16両編成で700席くらいあるわけです。そのうち%の人が使っているか解りませんが、2MBpsを分け合う訳ですよ。遅いのはあたりまえです。

 ということで、新幹線の中ではネットを使わない時間の過ごし方をするのが吉でしょう。


電波とアンテナが一番わかる (しくみ図解)

電波とアンテナが一番わかる (しくみ図解)

Rubyのtwitterライブラリで、Twitter Streaming APIが扱えるようになっていた

 久々に@sferikによるTwitterのAPIを使ってみると、いつの間にかTwitter Streaming APIも取得できるようになっていました。Twitter Streaming APIは、APIの中でも異色のもので、ひたすらパブリック・タイムラインを取得するといったものです。4年ほど前に出た当初は、かなり話題になって色々な人がタイムラインを取得して分析していました。かく言う私も、AWSのEC2上で動かして、1年ほどTwitterの呟きを取得して遊んでいました。ちょうど4年前はワールドカップがあり、日本の試合がある度にTweet量が爆発して、プログラムも爆発していました。細かい数字は忘れましたが、無料で使える数%に絞ったAPIのうち日本語だけに絞っても、月数千万件レベルでデータがあったと思います。
 そんなこんなのTwitter Streaming APIですが、当時はサードパーティ製のライブラリもなく自前でNet::HTTPやEventMachineを組み合わせて取得しました。それが、ライブラリ1つで簡単に出来るようになっているんですね。ありがたい限りです。

Twetter Streaming APIのサンプルコード



 一番簡単なTwetter Streaming APIのサンプルコードです。APIキーを取得して、環境変数にセットしておけば使えます。

require 'twitter'

config = {
  :consumer_key => ENV['TWITTER_API_KEY'],
  :consumer_secret => ENV['TWITTER_API_SECRET'],
  :access_token => ENV['TWITTER_ACCESS_TOKEN'],
  :access_token_secret => ENV['TWITTER_ACCESS_TOKEN_SECRET']
}
client = Twitter::Streaming::Client.new(config)
client.sample do |tweet|
  if tweet.is_a?(Twitter::Tweet)
    #日本語の呟きだけ表示
    puts tweet.text if tweet.lang == "ja"
  end
end

Twetter Streaming APIのデモ



まとめ



 これだけでは面白くないので、次はAWS Kinesisと組み合わせてみようかと思います。当時も、突発的に大量のデータが飛んできた時の処理能力などで困っていました。今だとKinesisとバックエンドのサーバを組み合わせたら何でもできそうですね。


See Also:
RubyでTwitter Streaming APIを使ってみる
Twitter Streaming APIのJSONの構造

参照:
sferik/twitter · GitHub

RubyのHTTPS通信で、OpenSSL::SSL::SSLErrorが出たら?

 Mac OSのバージョンアップする度に出しているような気がするのが、RubyのHTTPS通信でのエラー。ルート証明書が見つからなくてエラーがでます。

/.rvm/rubies/ruby-2.0.0-p353/lib/ruby/2.0.0/net/http.rb:918:in `connect': SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (OpenSSL::SSL::SSLError)


 そんな場合は、まずはRubyの慌てず騒がずNet::HTTPがどこに証明書を探しにいっているかを確認します。

$ ruby -ropenssl -e "p OpenSSL::X509::DEFAULT_CERT_FILE"
"/etc/openssl/cert.pem"


 指し示された場所に、あるのでしょうか? 

$ ls /etc/openssl/cert.pem
ls: /etc/openssl/cert.pem: No such file or directory

 ないですね。OSをバージョンアップすると、何故か消えるっぽいです。


 フォルダ作って、証明書をダウンロードします。

$ sudo mkdir /etc/openssl
Password:
$ cd /etc/openssl/
$ sudo wget http://curl.haxx.se/ca/cacert.pem
--2014-05-17 17:10:23--  http://curl.haxx.se/ca/cacert.pem
Resolving curl.haxx.se... 80.67.6.50, 2a00:1a28:1200:9::2
Connecting to curl.haxx.se|80.67.6.50|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 250283 (244K)
Saving to: 'cacert.pem'

100%[======================================>] 250,283      214KB/s   in 1.1s   

2014-05-17 17:10:25 (214 KB/s) - 'cacert.pem' saved [250283/250283]
$ sudo mv cacert.pem cert.pem


 解決!!

OpenSSL::SSL::SSLError



 このエラーは、当然ながらWindowsでも起こりえます。Macの場合と同様に、デフォルトパスの場所にファイルを配置すれば、問題は解決します。それでは、Windows rubyの場合は、デフォルトの証明書のパスは、どこでしょう?RubyInstallerで入れた場合は、次のようになっていました。

>ruby -ropenssl -e "p OpenSSL::X509::DEFAULT_CERT_FILE"
"C:/Users/Luis/Code/luislavena/knap-build/var/knapsack/software/x86-windows/openssl/1.0.0l/ssl/cert.pem"

 ダメだ。このフォルダは作れない。誰だよ、Luisって!!そういう場合は、環境変数でしています。

>set SSL_CERT_FILE=C:\Ruby200\cacert.pem

 解決!!

RubyでAmazon Product Advertising APIを再び使ってみる

 ちょっと必要になって、再びAmazonのProduct Advertising APIを触ってみました。このAPIは、Amazon謹製のAPIで、商品検索や個別商品情報の取得が出来ます。元々このAPIの名前は、Amazon アソシエイト Web サービスといって、AWSと呼ばれていました。クラウドの方のAWSが出る前の話ですね。その後、クラウドの方がAWSという名称になり、こちらのAPIの名前がProduct Advertising APIに変わっています。
 ちなみに、Amazon Web サービスというサービス名は、現在もクラウドのAWS単体を指すのではなく、AmazonがAPIを使って提供しているサービス全体を指す言葉のようですね。ややこしいけど、全く必要としない豆知識でした。

Product Advertising APIを操作するRuby Gemを選ぶ



 Product Advertising APIは、過去の名称のものも含めて何度かメジャーバージョンアップしています。そして、古いバージョンのものは、一定期間後に使えなくなり気がついていないアプリ開発者の大絶滅を繰り返しているという歴史があったと思います。そんな理由があるので、古いライブラリが打ち捨てられることも多く、イマイチどのライブラリがメジャーなのか解りづらいところです。
 そんな中でGitHubをサクッと検索してみると、上位にくるのは下記の3つです。

  1. GitHub - jugend/amazon-ecs: Amazon Product Advertising Ruby API
  2. GitHub - completelynovel/amazon-product-advertising-api: A nice rubyish interface to the Amazon Product Advertising API, formerly known as the Associates Web Service and before that the Amazon E-Commerce Service.
  3. GitHub - hakanensari/vacuum: Amazon Product Advertising API client


 1つ目のAmazon ECSについては、5年前に記事を書いた時点から生き残っている古株です。その割には、最近でもメンテナンスされています。2つ目のAmazon Product Advertising APIについては、その名もズバリの名前でええのかよという感じですが、もうメンテナンスされていません。最後のvacuumについては、何でその名前を選んだという名称です。作り自体は、Product Advertising APIの薄いラッパーで手軽で使いやすいです。作られたのも最近で、比較的メンテナンスされています。上手くいけば、ちょっと複雑化しているAmazon ECSの対抗馬になると思いますが、名前からAmazon関係のライブラリと気が付かれることもないので、メジャーになりにくいのではと思います。

 そんな訳で、またAmazon ECSを利用します。インストールは、Gemで一発です。

$ gem install amazon-ecs
Fetching: amazon-ecs-2.2.5.gem (100%)
Successfully installed amazon-ecs-2.2.5
Parsing documentation for amazon-ecs-2.2.5
Installing ri documentation for amazon-ecs-2.2.5
Done installing documentation for amazon-ecs after 0 seconds
1 gem installed

Amazon ECSでProduct Advertising APIを操作する



 Product Advertising APIのサインアップがされているという前提で、アクセス・キーとシークレット・アクセス・キーは取得済みの前提とします。ちなみに、これらのキーは、AWSのマスターキーと同じで、かつProduct Advertising APIはIAMアカウントで操作できません。結構危険なので、両方使う人は別のアカウントを作る方がよいでしょうね。
 さて、下記のプログラムが、Amazon ECSを利用して一覧検索と個別商品取得をした例です。基本的には、item_searchメソッドとitem_lookupを覚えていれば事足ります。あとは、レスポンスをppなりで確認しながら弄れば大丈夫でしょう。

require 'amazon/ecs'
require 'pp'

Amazon::Ecs.options = {
  :associate_tag => 'sampleapp-22', 
  :AWS_access_key_id => ENV['AWS_ACCESS_KEY'], 
  :AWS_secret_key => ENV['AWS_SECRET_ACCESS_KEY']
}

#商品検索
res = Amazon::Ecs.item_search('ruby', :country => 'jp')
res.items.each do |item|
  puts item.get('ItemAttributes/Title')
end

#個別商品の詳細表示
res = Amazon::Ecs.item_lookup('B00JXEFT6Y', :response_group => 'Small, ItemAttributes, Images', :country => 'jp')
pp res


## 商品検索 item_searchメソッド
 商品検索のメソッドの実装は以下のとおりです。思いのほか、シンプルです。まぁ基本的には、Amazon Product Advertising APIに値を送っているだけです。optsに何を渡すかは、APIの方を見ないと解りません。

    # Search amazon items with search terms. Default search index option is 'Books'.
    # For other search type other than keywords, please specify :type => [search type param name].
    def self.item_search(terms, opts = {})
      opts[:operation] = 'ItemSearch'
      opts[:search_index] = opts[:search_index] || 'Books'
      
      type = opts.delete(:type)
      if type 
        opts[type.to_sym] = terms
      else 
        opts[:keywords] = terms
      end
      
      self.send_request(opts)
    end

 まずOperationですが、このメソッドを利用すると勝手にItemSearchが指定されます。このAmazon ECSは、幾つかあるAPIのOperationのうちで、3つだけ実装しています。ItemLookupとItemSearch,BrowseNodeLookupです。APIには、下記の通り9つの機能が幾つかあります。商品検索系とカート系の機能ですね。
http://docs.aws.amazon.com/AWSECommerceService/latest/DG/CHAP_OperationListAlphabetical.html

  • BrowseNodeLookup
  • CartAdd
  • CartClear
  • CartCreate
  • CartGet
  • CartModify
  • ItemLookup
  • ItemSearch
  • SimilarityLookup

 ItemSearchのAPIの仕様は以下のURLのとおりです。
http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemSearch.html
オプションの値を、ハッシュ配列に格納して送ることが可能です。例えば、著者指定で検索する例です。

opts = {
  :country => 'jp',
  :author => '北方謙三'
}
res = Amazon::Ecs.item_search('三国志', opts)
res.items.each do |item|
  puts item.get('ItemAttributes/Title')
end


## 商品取得 item_lookupメソッド
 個別商品の取得は、item_lookupメソッドを利用します。商品を一意に識別するために、ASIN(本の場合だと、ISBN)を利用します。
http://docs.aws.amazon.com/AWSECommerceService/latest/DG/ItemLookup.html

# Search an item by ASIN no.
def self.item_lookup(item_id, opts = {})
  opts[:operation] = 'ItemLookup'
  opts[:item_id] = item_id
    
  self.send_request(opts)
end    


## その他のOperationの利用
 Amazon ECSで実装されている以外の、Operationを利用する場合は、send_requestメソッドを利用します。Optionの値を詰め込んで、send_requestメソッドを呼び出します。send_requestメソッドの中身が何をやっているかと、タイムスタンプを付けて、リクエスト用のURLに変換して送ってくれています。まぁ、それだけやってくれれば充分だなぁという作りです。カートなども、これを使えば実装できます。

    # Generic send request to ECS REST service. You have to specify the :operation parameter.
    def self.send_request(opts)
      opts = self.options.merge(opts) if self.options
      
      # Include other required options
      opts[:timestamp] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")

      request_url = prepare_url(opts)
      log "Request URL: #{request_url}"
      
      res = Net::HTTP.get_response(URI::parse(request_url))
      unless res.kind_of? Net::HTTPSuccess
        raise Amazon::RequestError, "HTTP Response: #{res.code} #{res.message}"
      end
      Response.new(res.body)
    end


## まとめ
 商品の情報をまとめて取得しようと、久々に使ってみました。やはりAmazonはAPIがしっかり設計されているので使いやすいですね。


See Also:
RailsでProduct Advertising APIを扱うAmazon ECSを使う


参照:
http://docs.aws.amazon.com/AWSECommerceService/latest/DG/Welcome.html


5/6まで! Kindle本 ゴールデンウィーク セールのお勧め本/買った本

 今知ったのですが、Kindle本のゴールデンウィークセールが開催中です。期間は、2014/5/6まで。つまり今日までです。ざっと見ましたが、全部で164冊とすぐに選べる分量でした。その中で、私が買った本、お勧めする本などを紹介します。

全面改訂 超簡単 お金の運用術 [Kindle版]



 山崎元さんのお金の運用術のKindle版です。紙の本の初版持っていましたが、既に手元にないのと全面改訂と書いているので、買い直しました。山崎さんの運用術は、実は凄いオーソドックスで、銘柄や個別のファンドを追い求めるというスタイルではないです。インデックス・ファンドを、比率を注意して買いなさいという話です。そして、どのような比率で買えば良いのかという考え方を指し示しています。一度、読んでおくと基本が身につくので、かなりお勧めの著者の一人です。あとは、うそ臭い投資術をぶった切っているのも良いですよ。
¥821⇒¥250の70%オフ
全面改訂 超簡単 お金の運用術

全面改訂 超簡単 お金の運用術

稲盛和夫の実学 [Kindle版]



 京セラの創業者の稲盛和夫さんの実学。はるか昔、就職する前に読みました。これも手元にないので、改めて買い直します。社会人になって、仕事の経験を積んだ上で、稲盛さんの哲学がどう響くか楽しみです。「働き方」は、たぶん読んだことがないです。
¥566⇒¥228の58%オフ
稲盛和夫の実学

稲盛和夫の実学

¥1,512⇒¥400の74%オフ
働き方

働き方

お金のIQ お金のEQ ― 世界の幸せな小金持ちが知っているお金の法則 [Kindle版]



 本田健さんの本。今まで読んだことがあるかどうかは不明。一時期読み漁ってた時代があるので、読んでるかもしれません。本田健さんは、不労所得で稼ぐとは何かというテーマを、色々な事例をあげて紹介しています。こういった視点は大事だと思います。
紙版絶版⇒¥50
お金のIQ お金のEQ ― 世界の幸せな小金持ちが知っているお金の法則

お金のIQ お金のEQ ― 世界の幸せな小金持ちが知っているお金の法則

MEDIA MAKERS―社会が動く「影響力」の正体 (宣伝会議) [Kindle版]



 新しいメディアの形を模索しつづける田端さんの著書。これも読んだことがないので、この機会に読んでみます。最近、本の形というのを考え続けていますが、Kindleを始めとする電子出版がメジャーになってきて、劇的に変わっていると思います。本の書き方、編集/出版社の役割と。次の姿を考えるヒントにしてみたいですね。
¥1,728⇒¥450の74%オフ
MEDIA MAKERS―社会が動く「影響力」の正体 (宣伝会議)

MEDIA MAKERS―社会が動く「影響力」の正体 (宣伝会議)

沈没船が教える世界史 (メディアファクトリー新書) [Kindle版]



 タイトル買い。面白かったら書評書きます。
¥799⇒¥310の61%オフ
沈没船が教える世界史 (メディアファクトリー新書)

沈没船が教える世界史 (メディアファクトリー新書)

  • 作者: ランドール・ササキ(水中考古学者)
  • 出版社/メーカー: KADOKAWA / メディアファクトリー
  • 発売日: 2012/10/15
  • メディア: Kindle版
  • この商品を含むブログを見る

鋼の錬金術師1巻 (デジタル版ガンガンコミックス) [Kindle版]



 Kindleで手元においておこうかなぁと思って。
¥432⇒¥216の50%オフ
鋼の錬金術師1巻 (デジタル版ガンガンコミックス)

鋼の錬金術師1巻 (デジタル版ガンガンコミックス)

サクッと読める!「脳」の話



 評判良いみたいなので、買ってみます。
サクッと読める!「脳」の話

サクッと読める!「脳」の話

涼宮ハルヒの憂鬱 (角川スニーカー文庫) [Kindle版]



 今まで読んだことなかったので、読んでみたいです。
¥562⇒¥250の56%オフ
涼宮ハルヒの憂鬱 (角川スニーカー文庫)

涼宮ハルヒの憂鬱 (角川スニーカー文庫)


 Kindleは、セールがあれば一通り目を通してまとめ買いするのがよいですよ。


参照:
Kindle本 ゴールデンウィーク セール

開発用プロキシ、「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