Home > Programming > Ruby

Ruby Archive

SQLite 3では?インデックスの名前はユニークである必要がある

Crêpeのおかげで最近はSQLite 3にどっぷりで、SQLでデータベースを操作するのは非常に楽しいです。

SQLiteではというか、SQLでは高速に検索を実行するためにはインデックスの作成がかかせません。
インデックスを作成しているかしていないかで検索スピードが100倍以上変わることも珍しくありません。

そのためインデックスを作成する訳ですが、 次のような構文で作成することができます。

CREATE INDEX idx_hoge ON piyo(hoge);

piyoがテーブル名、hogeが列名になり、作成されるidx_hogeがhogeに対するインデックス名になります。

逆にインデックスを削除するときは

DROP INDEX idx_hoge;

です。ただ、いつも参考にしている本の一つ、「ぐんぐん実力がつく! 逆算式SQL教科書」ではMySQLを使っている関係上、構文は次のようになります。

DROP INDEX idx_hoge ON piyo(hoge);

ぐんぐん実力がつく! 逆算式SQL教科書
著者: 小野哲
ページ数: 288ページ
出版社: 技術評論社
発売日: 2007年12月26日

これらの構文を見て勝手に勘違いをしました(自分が勝手に勘違いをしただけで、この本自体は何度も参照するとても良い本です)。
インデックス名は同じテーブル内でユニークであればいいと。

でも実際にSQLite 3でスキーマを調べられる.schema命令を使ってみると、作成されていませんでした。
今まではRubyでCREATE INDEXをしていたので、そのときはなぜかエラーが出ていなかったので気づきませんでした(どうもいままではほぼ主キーを使っていたのでうまいこといっていたらしい)。

MySQLでは調べたりなくて、同じなのかどうかちょっとわからないのですが、これからインデックスを作成する際には

CREATE INDEX idx_piyo_hoge ON piyo(hoge);

のようにインデックス名にテーブル名も含めようと思います。

Rubyで自動的にリンクを拾ってくるツールを作ったのだが・・・ 2

先日の続き。
Rubyで自動的にリンクを拾ってきてくるツールを作ったのだが・・・

もう少し具体的に処理内容を書く。

  1. 2万件近くのhtmlファイルをHTTPで取得。
  2. 取得したhtmlを解析し、リンクを見つる。
  3. ある一定条件のリンクであれば、さらにそこから飛んで、とんだ先のhtmlファイルを取得。
  4. その中にある特定条件のリンクを抽出する。

というものである。

このリンクをデータベース化しておく(サーバー側)。
こうすることの利点は2点ある。

  • クライアントアプリが必要なデータにダイレクトにアクセスできるようにする。
  • 将来URLの変更がおこなわれても、クライアント側の修正は不要。

まずはどこに時間がかかったのかを調べようと思って、プロファイラを使うことに決めた。

Rubyのプロファイラには標準でついてくるprofileと、それよりも高速なruby-profがある模様。
profile – Rubyリファレンスマニュアル
ruby-prof

はじめは高速なということに引かれてruby-profをインストール

sudo gem install ruby-prof

使用方法は

ruby -runprof hoge.rb

だそうなので、実際にやってみたところ

ruby: no such file to load -- unprof (LoadError)

と怒られてしまい、うまくいかなかった。

仕方がないので、標準のprofileを使う。
使い方は

ruby -rprofile piyo.rb

今処理の全部をやってしまうと時間がかかりすぎるので100件に絞っておこなった。
プロファイルをやってみたところ(上位5つの処理を抜き出している)

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 31.08    81.24     81.24      197   412.39   760.41  Hpricot.scan
  7.87   101.82     20.58    18912     1.09    19.36  Hpricot.build_node
  5.75   116.86     15.04    32238     0.47     9.89  Array#each
  5.53   131.31     14.45    99767     0.14     0.19  Hash#[]
  5.26   145.07     13.76   232541     0.06     0.06  Array#[]

こんな結果になって、(右端の% time全体時間のパーセンテージということであるから)が全体の40%ほどがhpricotがhtmlを解析する処理に使われていることがわかる。
プロファイルなしで実際の処理時間を測定してみると、35秒ぐらいかかった 1

どうも正規表現の処理は遅くなさそうだということで、(hpricotを使う前はhtml全体をパースしてしまっていたので、それを止めて)anchorタグだけをパースさせることにする。
参考はたのしいRubyのHTMLパーサー

AnchorTagRegexp = /<a\s+[^>]*(?:href=(?:"([^"]*)"|'([^']*)'|([^'"\s]*)))[^>]*>/mi
response.body.scan(AnchorTagRegexp) { |tag|
  href  = $1 || $2 || $3
}
たのしいRuby 第2版
著者: 高橋征義、後藤裕蔵
ページ数: 489ページ
出版社: ソフトバンククリエイティブ
発売日: 2006年8月5日

結果は、、、同じぐらい。

やはり35秒ぐらいかかる。

ええい、こうなったらもうC++で書いてしまえということで書いてみた。
結果は、、、同じくらい、というかもっと遅い???(ぉぃ
単純にプログラムの書き方が悪いのか?

ちなみにC++ではlibxml2を使って書こうとしたところ、HTMLのパースでエラーがいっぱい出た。
どういうことかというと、たとえば<meta ・・・/>ではなくて、<meta・・・>となっているからタグの対応関係があっていないとか・・・。
厳密に見ないやり方はよくわからなかったので、仕方がないので上で使った正規表現を用いることにした。

使ったもの

  • 正規表現にはboost::regex。
  • HTTPでの通信部分はboost::asio。
  • EUC-JP → UTF-8への変換はiconv。

たぶん、何で書いても速度が変わらないということはネットワークがボトルネックなんだということで、とりあえず今は納得した。
HTTPの同時接続数を増やすというやり方はどうかと思うので、やらないけれども(やっても4接続ぐらいにしておく)。

もう一つやるべきこととすれば、今はデータベースを丸ごと更新しているので(やり方としてはどうかと思う)、更新されている場合にだけリンクを深追いするという方式にすればもう少し速くなるかもしれない。

ただし、このデータベースにURLを入れておくというやり方は止めて、必要なデータをその都度引っ張ってくる方が色々な意味で優しいのではないかと思えてきた。

  1. エントリを書きながら、プログラムを調節すればいいのですが、あとでエントリを書いているため正確な数字がわからない []

Rubyで自動的にリンクを拾ってきてくるツールを作ったのだが・・・

とある目的のため、Rubyで自動的にリンクをたどって、HTMLファイルを解析し、必要となるリンクを抜き出してきてくれるツールを作った。

基本的にはNet::HTTPを使ってresponseをgetし、response.bodyで中身のHTMLファイルが取得できるので、あとはHTMLパーサーにかけるだけである。

HTMLパーサーにはhpricotというものを使った。
Hpricot, a fast and delightful HTML parser

これで非常に簡単にリンクが取得できる。
たとえば(たぶん)こんな風に(いい加減、ブログにアップロードするときにコードに色をつけてくれるのが欲しいな)。

require "rubygems"
require "hpricot"
require "net/http"

url = ARGV[0]

Net::HTTP.start(url) { |http|
  response = http.get(hoge)

  doc = hpricot(response.body)
  (doc/:a).each { |link|
    p(link[:href])
  }
}

実際に動かしてみると、何かが遅くて1時間ぐらい経っても?(放置していたので正確な時間がわからない)終わっていない(挙げ句の果てには、#<eoferror : end of file reached>とはき出して死んでいたのだが(笑))。

プロファイルしてみないとわからないけれども、ネットワークが遅いのか、Rubyが遅いのか、HTMLのパースが遅いのかのどれかだと思う。
ただ、hpricotはCでバックエンドが書かれているようなので、ここが遅いというのは考えにくいのかも。

こうなったらC++で書くしかないのか?
でも文字列処理が面倒 + httpを扱うものを書いたことがないということで(boost::asioで解決できるかな)、できる限り使いたくはないのだけれど。
しかも元々のHTMLはEUC-JPだしなぁ・・・。

rubyで半角カタカナを全角にする

NKFを使えばできる。

UTF-8には対応していないのかなぁなどと勝手に思っていたら、そんなことはなかったです。

標準添付ライブラリ紹介 【第 3 回】 Kconv/NKF/Iconv

ここに細かいオプションが書いてあります。
UTF-8で入力する場合には ‘-W’
UTF-8で出力する場合には ‘-w’
半角カタカナを全角にするには ‘-X’(デフォルトらしいのでつけなくても変換されるようですが)
MIME encodeをしないので ‘-m0′

ということで、たとえばこんな感じになります。

#!/usr/bin/ruby -Ku
require 'nkf'
p NKF::nkf('-WwX -m0', 'テストだよ。')

日々雑感 2008/05/05

1. SQLiteで遊んでいる。

コマンドラインからでも気軽に試せるし、rubyで少しスクリプトを書いてもよい。
2万件近くレコードがあるのに、一瞬にして結果を表示してくれるのがおもしろくて仕方がない。

図書カードが確か2,500円分あったので、今日はSQLの本を一冊購入してこようと思う。

csvファイルから読み込むには、コマンドラインから

sqlite3 -separator , test.db ".import test.csv test"

なんて書いてもよいのだけれども、自分のつたない知識では主キーを設定できなかったので、rubyで一項目ずつ加えていくことにした。

ただし、そのまま一項目ずつ加えると劇的に遅いので、トランザクションを使う。

db.transaction
begin
  db.execute "insert test (hoge, piyo) values(?, ?)", hoge, piyo
  db.commit
rescue
  db.rollback
end

こんな風に使うらしい。

トランザクションを明示的にしない場合には、裏で一項目追加されるごとにトランザクションをおこない(この書き方があっているかどうかは知らない)、commitされるそうだ。
それで、遅くなる理由が納得できた。

事実、トランザクションを使うと何倍も早く追加ができた。

Ruby on RailsでLighttpdの起動にはまる

 とある事情により、Ruby on Railsで実際にちょこっとしたものを書こうかなぁということになったので、実際にインストール済みのRailsでプロジェクトを一つ作って

ruby script/server

でサーバーを起動させてみました。しかしながら、エラー発生。

 どうもエラーメッセージをよく見てみると

(mod_fastcgi.c.988) execve failed for:
/Volumes/DataHD/***/public/dispatch.fcgi
No such file or directory

と書かれているところで失敗しているようでした。

 今使っているMacはHDDのパーティションを二つに分断して、一つのパーティションはMacBook HD、もう一つはData HDにしています。どうもエラーを見てみると、DataとHDの間のスペースが削られてしまっていて、Rubyのソースコードを見たのですが、原因となるようなところは見つからず・・・。lighttpdのバグかなぁなどと思います(まあディレクトリ名にスペースなんて入れるなといわれるのでしょうが)。

 そこで、本当はイヤなのですが、仕方がないのでドライブ名をDataだけにすることに決めました。まず起動しているアプリを終了。ドライブの名前をDataに変更し、NetInfo マネージャを起動してhomeの欄を変更して、再起動。無事今度はうまく行くようになりました。

 意外とネットで検索してみてもそれらしいのは載っていなかったので、書いておきます。

Home > Programming > Ruby

Feeds
Meta

Return to page top