GW に親父に頼まれてこんなのを作ったときの備忘録
- 複数のサイトからデータをスクレイピングして tsv ファイルに書き出し
- tsv ファイルをまとめて zip
- これらを Web アプリとして提供
本当は親父の PC ローカルで完結させたかったんだけど、Windows だし、環境構築がダルいしってことで、Ruby で Sinatra で Heroku に設置するかんじになりました。
僕のバックグラウンドとしてはこの程度
- iOS & Objective-C しか知らない
- Ruby は細々と書き捨てなスクリプトは書いたことがある
- JavaScript は大昔に Sleipnir のスクリプトを書いたことがある
ということで
- Ruby でそれなりのコード量を書くのははじめて
- Web アプリ的なのははじめて
- Heroku を使うのも Hello World 以外で使うのははじめて
という感じだったので大分屈折した感じで間違ったことを書いているとは思います。
スクレイピング
基本的に Nokogiri でやりました。このライブラリは割とよく使っているので慣れてはいました。DOM の指定はなんとなく css セレクタを使ってます。Chrome とかの Web Inspector とかだと Copy XPath とかあるからそっちの方が楽だったかもとか思ったりもしますがまあ勉強になるのでいいかなぁと。
ハマったのはあるサイトの pre タグ内に "<1234>" みたいな文字があったのですが Nokogiri だとこの文字列が欠落してしまうことがありました。Hpricot を使ってみたらどうなるかなぁと思ったら、inner_text メソッドを呼び出すと Hpricot 内部で落ちてしまって万事休す。かと思いましたが、以下のようにして open して取ってくるときにバイナリで取ってくるようにしたらなんとか期待通りの動作になりました。
Hpricot(open(url, "r:binary").read.toutf8)
Sinatra
Background Job
構成としては以下のようなシンプルな画面です。
- スクレイピング開始用のボタン
- 生成できた zip ファイルをダウンロードするボタン
問題だったのはスクレイピング開始周りです。当然ながらスクレイピングは時間がかかります。同期的に処理をしてしまうと timeout してしまってどうにもなりません。iOS 的なコードでいうところのこんな感じの処理を書きたくなりました。
- (id)startScraping { self.cleanUp; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^(void) { [self scrapeWithURL:url]; }); return response; }
スクレイピング自体はバックグラウンドに回してしまってとりあえずはレスポンスを返しておくイメージです。調べてみると Heroku で Sinatra を動かす場合 EventMachine の中で動いているらしいので
EM::defer do doHeavyTask() end
とすると Block 内部は別スレッドで実行できるようです。EM:defer は実行ブロックと完了ブロックを渡せるので iOS でやっていたようなノリでできます。
iOS で inline block を書くときはこんな感じで書けますが、
id(^completionBlock)(void) = ^(void) { return @"test"; };
Ruby だとこんな感じで書けるんですね。
callback = proc do "" end
HTML
最初は erb を使っていましたが、
- 構造は同じ
- ただし、具体的な文字等はバラツキあり
- 上記を複数回繰り返し出力したい
ということを実現する方法がよくわからなかったのですが、ググっていると haml を使って自分のやりたいことと似ていることをしているサンプルを見つけたのでやってみようと思ったところ実際にできたので途中から haml に変更しました。
CSS
よく分からないので Twitter Bootstrap を使って適当にサンプルから HTML をコピペしました。
まとめ
初体験でしたが、GW のアソビとしてはなかなか楽しかったです。iOS で学んだものが多少なりとも活きている気がしたのがうれしかったです。課題としてはスクレイピングが終わったらページの書き換えを出来るようにしたいです。誰かやり方教えてください。