Go と Ruby と Amazon SQS と ElastiCache で分散して動かせるクローラを書いてみた

More than 3 years have passed since last update.

Go と Ruby と Amazon SQS と ElastiCache で Ranunculus というクローラを書いてみました。

正確には書きかけなのですが、もう日曜日が終わってしまうので、とりあえずまとめておくことにします。


なんで作ろうと思ったのか

理由は 3 つあります。

1 つめは、Ruby で使えるよいクローラが見つからなかったからです。

Python には Scrapy という、クローラのためのいたれりつくせりなライブラリがあります。

ところが、Ruby にはそういういい感じのライブラリがありません。

よく言及されている Anemone というライブラリも、最近はメンテナンスされていないみたいです。

そこで、いっちょ書いてみるか!という気分になりました。

理由の 2 つめは、Go で何か書いてみたかったからです。

よくある、手段が目的になっちゃったやつです。すみません。

理由の 3 つめは、Gem を作って公開してみたかったからです。

よくある、手段が目的になっちゃったやつです。すみませんすみません。


Anemone で私が個人的に改善したかったところ

Anemone は、よいライブラリです。とても気に入っていて、よく使っています。

あえてダメなところをあげるとすると、複数台で分散してクロールできないところです。

DDos にならないように、ドメインあたりのクローラの数は制限するとしても、

並行してクロールできる環境がほしかったのです。


どう改善したか(改善しようとしたか)

Webをクロールしてスクレイピングするプログラムを書くと、たいていボトルネックになるのはクローラの部分です。

そこで、まずはクローラを並列で動かせるようにして、ダウンロードしたデータをローカルやElastiCacheに置いておける仕組みを作りました。

次に、そのクローラの部分を、 DOM を XPath で解析する部分とキューでつなぐために SQS を使う実装を追加しました。

…まだ本当にそこまでしか書いていないので、実際に動かすと DDos みたいになっちゃいますが、まあ続きはボチボチやっていきます。


どんな感じなのか

Ruby のコードはなるべく Anemone を使ったときと同じ感じにしたかったので、SQS と Redis の設定がある以外は同じです。

require 'aws-sdk'

require 'redis'
require 'ranunculus'

# SQS の設定
sqs = Aws::SQS::Client.new(
access_key_id: ENV['ACCESS_KEY_ID'],
secret_access_key: ENV['SECRET_ACCESS_KEY'],
region: 'ap-northeast-1'
)
url_queue = "https://sqs.ap-northeast-1.amazonaws.com/12345/UrlQueue"
result_queue = "https://sqs.ap-northeast-1.amazonaws.com/12345/ResultQueue"

# Redis の設定
redis = Redis.new(:host => "localhost", :port => 6379, :db => 0)

# クローラを作成
crawler = Ranunculus::Crawler.new(sqs, redis, url_queue, result_queue)

# クロールを開始するページを定義
page = Ranunculus::Page.new("http://www.yahoo.com")

# 各ページごとに実行する処理を定義
crawler.on_every_page do |page|
puts page.url
end

# クロール開始する。最初のページから 1 つ先のリンクまでをクロールする。 User-Agent を Ranunculus にする。
c.start(page, 1, "Ranunculus")

Ruby の gem で実装しているのは、クロールしたい URL をSQSに積む処理と、

クロールした結果をSQSとRedisから読んでパースする処理だけです。

一方 Go で書いたクローラの方は、SQSからURLを読みだして、ページを取得して、結果を Redis に書いて、

ステータスコードとかを出力用のキューに書き込む処理をします。

重複したURLを省いたりキャッシュしたりとかは Ruby 側でやる前提なので、Go のクローラは複数台で並列で実行できます。

全体像は、こんな感じです。

シーケンス図

一番右の Ranunculus(worker)ってやつが Go で書かれているクローラです。

こいつを AWS のスポットインスタンスとかにデプロイしちゃえば、その台数の数だけ分散してクロールできる!


これからやりたいこと

go routine を使って1つのプロセスで複数のドメインをクロールできるようにする。

今は Go routine で普通に実装しちゃうと、同じドメインを同じIPからクロールすることになるので、あっさり IP で Ban されてしまうんですよね。。。


そんなことよりも

この土日で Line の bot を作るはずだったのに、全然何もやれてない!!