このブログは2015年2月18日に更新を停止しました。すべての記事は https://chroju.github.io へ移行されています。

Mechanizeによるスクレイピングの基本的なことまとめ

Exhibiの内部的な話を書こう書こうと思って忘れてた。とりあえずMechanizeについて。

Mechanizeスクレイピングを楽にしてくれるRubygemsです。ExhibiではMechanizeを使ったスクレイピングのRakeタスクを作成し、それを日次で実行することで、各美術館のサイトから展覧会情報を抽出しています。抽出した情報がDB内に存在していれば無視。存在しないのならDBに追加。こういうクローリングに関しては、ちょうど時同じくしてRubyのクロール入門本が去年出たんですけど未読です。技術的な話のみならず、人様のサイトへ機械的にアクセスする際のお作法的なことも載っているらしく、いつかは読みたいところ。

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例
るびきち 佐々木 拓郎
SBクリエイティブ
売り上げランキング: 8,851

Mechanizeの内部動作

Mechanizeは同じくスクレイピング用のGemであるnokogiriに依存しており、スクレイピング結果はNokogiriのオブジェクトとして扱われるみたいです。nokogiri使ったことないのでよくわかりませんが。例えばあるurlに対してgetリクエストをかけた際、そのレスポンスをpageインスタンスとして受け取った場合、この中身は単なるHTMLではなく、スクレイピング向きにアレンジされてます。page.linksでページ内のリンク一覧が取得できるし、page.search(XPath)XPathを使った検索ができます。

まあだいたいスクレイピングでやることって、ページを取得してきて①その中のリンクをクリックする、②フォームを埋めて送信して次のページヘ進む、③ページ内の要素からデータを抽出する、というところだと思うので、これぐらい押さえておけばなんとかなります。ちなみにurl与えてページを持ってくるときはMechanizeインスタンス(公式チュートリアル内ではagentという変数が振られてます)に対してagent.get(url)というメソッド使えば返ってきます。簡単ですね。

ページ内の要素からデータを抽出する

自分はXPathをよく使います。

page.search('//td[@class="hoge"]').text

タグでももちろんいけます。

page.search('h3')[0].text

こちらだと戻り値が配列になる点に注意。XPathだと対象を一意に特定できるけど、タグだと同一ページ内に複数存在する可能性があるからなんだと思います。他にもやり方あるかもしれませんが、自分はこれしか使ってないです。あと、タグ内のテキスト部分を取り出したい場合は上記のやり方ですが、タグの要素の値を取り出すこともできます。

page.search('img')[0]["src"]

こんな感じで画像のURLを抽出したいときなどに使います。

リンクをクリックする

基本はaタグで挟まれたテキストの値か、href属性の値から特定してクリックします。

page.link_with(text: 'hoge').click
page.link_with(href: '/fuga').click

これでリンク先のpageが返ります。が、クリックしたいのがテキストではない上に、href属性の値も特定できない場合もあります。Exhibiで必要な展覧会情報も基本的には個々の展覧会でパーマリンクが別なので、hrefから特定することはできないです。そういうときはXPathと上手いこと組み合わせます。

hrefs = page.search("//div[@class='hoge']/div/a/@href")

links = Array.new
hrefs.each do |href|
  links.push(page.link_with(href: href.text))
end

links.each do |link|
  page = link.click
  ### なにか処理 ###
end

またpage.linksですべてのリンクが呼び出せるので、イテレータで全リンクに対して何か処理したりってこともできます。

フォームを埋めて送信する

Exhibiではフォームの処理は使っていませんが、一応書き留め。pageに対してformメソッドを使うことで特定のフォームを取得し、値を埋めてsubmitできます。

f = page.form('LoginForm')
f.name = "chroju"
f.password = "password"
page = f.submit

page#formの引数に与えているのは、取得したいformタグのname要素です。以下、inputタグのname要素がメソッドになっているので、ここに値を埋め込んでいき、最後に#submitメソッドを呼び出します。

基本的にやることはこれだけなんですけど、ものによっては単純なフォームの送信ではなく、送信ボタンをクリックした時にonClick要素でJavaScriptを呼び出して処理させていたりします。そういう場合は単純にsubmitしてもバリデーションが通らない可能性があるので、呼び出している関数の中身を解析して対処するしかないです。大抵はhidden要素に対して何かしら値をいれこむような処理をしていることが多いので、その内容さえわかれば、JavaScript内で行われている値の代入処理を手で書いてあげれば無事にsubmitできたりします。

あとリンクと同じように、フォームもpage.formsで全要素取得できます。

スクレイピング楽しい!!!

だいぶざっくりめに書いちゃいましたが、やっぱり公式ドキュメント読むのが一番いいとは思います。ここに書いている以外にもやれることはいろいろあるはずなので。

スクレイピングAPI提供していないサイトであっても、すべてのネット上のデータを機械的に扱うことができるようになるので、結構夢が広がります。ただし、セマンティックウェブに則りきちんとしたHTMLを書いているサイトじゃないと上手いことコードが書けなかったりするので、ひとえに万能な魔法というわけではないのが悩ましかったりも。そういうときはウェブ上で簡単にスクレイピングが出来るらしいkimonoとか使うのもアリなのかもしれません。自分は使ったことはないですけど、kimonoの開発者が「セマンティックウェブは失敗した。ネット上のデータ構造化をウェブ製作者側ではなく、データの利用者側がやるべきだ」的なことを言っていて、これにはかなり共感を覚えたりしてます。