今日は、RubyでWebサイトを解析するときに強い味方となるライブラリ、HpricotとWWW::Mechanizeを紹介します。

どちらも非常に強力なので、覚えておいて損はないよ！

以下ではまずHpricotでHTMLを解析・編集する方法について解説します。 次に、「はてなダイアリーの自動更新」を例にWWW::Mechanizeの使い方を解説します。

Hpricot

HpricotはHTMLを解析するためのライブラリです。

例えば「あるページのリンクだけを全部抜き出したい」と思ったとき、どうしますか？scrAPIを使う？でもscrAPIはやっぱり ちょっと使いたいだけなのにパーザ(Scrape)用のクラスを定義するのが面倒なんだよね！

Hpricotなら、たったこれだけで解析完了です。

require 'hpricot' require 'open-uri' doc = Hpricot( open("http://www.kmc.gr.jp/").read ) (doc/:a).each do |link| puts "#{link.inner_html} → #{link[:href]}" end

HTMLを読み込むには「Hpricot()」という関数にHTMLデータの文字列を与えればOK。 例ではOpenURIを使ってネットから取得しています。

タグを検索するには、読み込んだデータの「/」というメソッドにシンボルか文字列を渡します。

tableタグを全て探す doc/:table <div id="hoge">を全て探す doc/"div#hoge" <span class="moge">を全て探す doc/"span.moge"

CSSに慣れている人なら気付いたと思いますが、2番目、3番目の書き方は「CSSセレクタ」と呼ばれるものです。 Hpricotでは他にもさまざまなCSSセレクタを 使用することができます。

<table id="list">の中のtdタグを全て探す doc/"table#list td" "index.cgi?"から始まるリンクを全て探す doc/"a[@href^='index.cgi?']" テキストに"new"という文字列が入っているリンクを全て探す(※version 0.5以降) doc/"a[text()*='new']"

上の例はどれも「〜なタグを全て探す」でしたが、条件に当てはまるものを一つだけ取り出したいこともありますよね。 そんな時はdoc.at()が使えます。

一番最初のリンクを探す doc.at(:a)

ElementsとElem

タグ一覧はHpricot::Elementsのインスタンスになります。これはArrayのサブクラスなので、 firstで最初の要素を取り出したり、eachでループを回したり、find_allで条件に合うものだけ抜き出したりできます。

またそれぞれのタグはHpricot::Elemのインスタンスになります。

Elemには以下のようなメソッドが定義されています。

attributes : 属性一覧

["href"] : 属性hrefの値を得る

name : タグ名

parent : 親要素

containers : 子要素(タグのみ)

children : 子要素(テキストやコメントも含む)

css_path : その要素のCSSパス

previous_sibling, next_sibling : 隣の要素(タグのみ)

previous_node, next_node : 隣の要素(テキストやコメントも含む)

each_child{|elem| ..} : 子要素について繰り返す

inner_html, inner_text : 内部のHTML, 内部のテキスト

to_html : そのタグも含めたHTML

to_original_html : そのタグも含めたHTML(なるべく元のHTMLと同じように出力する)

to_plain_text : 読みやすいテキストに整形する

実はElemやElementsにも「/」や「at」が定義されているので、「/」による検索結果に対し「その中をさらに探す」なんてこともできます。

例：

divタグの中のリンクを全て探す doc/:div/:a 各divタグの中の最初のリンクを探す

(doc/:div).each do |div| first_link = div/:a end

HTMLに変更を加える

Hpricotの凄いところはHTMLへの変更もサポートしている点です。 linkをあるリンクタグだとすると、link.inner_html に文字列を代入すると中身のHTMLが変更されたり、 link[:href] = "foo.html" とするとリンク先が"foo.html"になったりします。

これらの変更は、to_htmlなどが生成するHTMLに反映されます。 例えば「サーバ移転したのでexample.jpへのリンクをexample.orgに直したい」という場合は、

doc = Hpricot(ARGF.read) (doc/:a).each do |link| link[:href].gsub! %r(http://example.jp/), "http://example.org" end print doc.to_html

という感じで簡単にフィルタスクリプトを書くことができます。

変更系のメソッドには以下のようなものがあります。

["href"]= : 属性hrefの値をセットする

inner_html= : 内部のHTMLをセットする

name= : タグ名をセットする

children= : 子要素をセットする

余談

「/」とか個性的なメソッド名が多いHpricotですが、実は「もっと普通の名前」も用意されています。 例えば「/」には「search」という別名がありますし、 HTMLの読み込みは Hpricot.parse(html) とも書けます。 でもやっぱり短い方を使っちゃうんだよね…!(便利だから)

WWW::Mechanize

WWW::Mechanizeは、Webサイトへのアクセスを自動化するためのライブラリです。

例えばはてなダイアリーに日記を投稿するスクリプトを書くとしましょう。 Net::HTTPだとまず投稿用のURLを調べて、フォームタグの名前を調べてPOSTして、あれ認証ってどうやるんだっけ…？なんて いろいろなことを考えないといけませんが、WWW::Mechanizeを使えば「普段ブラウザを操作するのと同じような感覚で」 スクリプトを書くことができます。

では、ブラウザから日記を書くときの手順を思い出してみましょう。

WWW::Mechanizeなら、これをそのままスクリプトに落とすことができます。

require 'mechanize' require 'kconv' #あとでUTF-8を扱うので agent = WWW::Mechanize.new diary_page = agent.get("http://d.hatena.ne.jp/(自分のはてなid)/")

newでインスタンスを作って、getでWebページを取得しています。簡単ですね。

(2) おおっと、ログインしてなかった。右上の「ログイン」をクリックする

login_link = diary_page.links.text("ログイン".toeuc) login_page = agent.get(login_link.href)

diary_page.linksでページ中のリンク一覧が取得できます。ここでは、全てのリンクの中から テキストが"ログイン"に一致するものを探しています。

login_linkはMechanize::Linkのインスタンスで、login_link.hrefでURLが得られます。agent.getにこのURLを渡してログインページを開きましょう。

(3) ユーザー名とパスワードを入力し、「ログイン」を押す

login_form = login_page.forms.first login_form['key'] = "(ユーザ名)" login_form['password'] = "(パスワード)" redirect_page = agent.submit(login_form)

login_page.formsでformタグの一覧が取得できます。ログインページにはformが一つしかないので、firstで最初のformを選んでいます。

次の行では、<input name="key" ...> というインプットボックスにユーザ名を入力しています。 パスワードも同様に入力します。

agent.submitにこのフォームを渡すとフォームの内容が送信され、送信結果のページが返ってきます。

(4) 「自動でページを移動しています(移動しないときはこちらのリンクを…)」

diary_link = redirect_page.links.text("こちら".toutf8) diary_page = agent.get(diary_link.href)

ログインすると、おなじみの「移動しないときはこちらのリンクを…」というリダイレクトページになります。 WWW::Mechanizeにはリダイレクトを自動で追跡してくれる機能がある…のですが、このリダイレクトページは200 OKなので 自動追跡が効きません(´・ω・｀) 仕方がないので、手動でリンクをクリックしましょう。 またこのページはさっきとちがってUTF-8なので、"こちら"もUTF-8に変換しておきます。

(5) 「日記を書く」をクリック

edit_link = diary_page.links.text("日記を書く".toeuc) edit_page = agent.get(edit_link.href)

ここまで来たらあと一歩です。"日記を書く"のリンクを探し、クリックします。

(6) textareaに日記本文を入力し、「この内容を登録する」を押す

edit_form = edit_page.forms.name("edit").first edit_form["body"] += "

*Rubyから日記を更新してみるテスト。" ok_button = edit_form.buttons.name("edit") agent.submit(edit_form, ok_button)

さあ、いよいよ日記の書き込みです。編集画面には複数のフォームがあるので、edit_page.form("edit") で最初の <form name="edit" ...> というタグを見つけています。また、このフォームには「確認する」と「登録する」という複数のsubmitボタンがあるので、 登録ボタンを探してsubmitに渡しています(「こっちのボタンを押してください」という意味です)。

http://d.hatena.ne.jp/(はてなid)/ を見てみてください。新しい日記が書き込まれましたか？:-)

WWW::Mechanizeではこのように、ブラウザを操作するような感覚でスクリプトを書くことができます。

簡易リファレンス

pageには以下のようなメソッドがあります。

links : リンク(aタグ)一覧

forms : formタグ一覧

form("foo") : name="foo"である最初のformタグ

title : ページタイトル(titleタグの中身)

header : HTTPのレスポンスヘッダ

root : ページの内容を表すHpricotドキュメント

linksやformsはMechanize::Listのインスタンスを返します。これはArrayのサブクラスなので、配列のように扱うことができます。 また簡便のため、「hrefが"index.cgi"であるものを全て探す」という操作を links.href("index.cgi") のように書けたり、 「name属性が"hoge"であるものを全て探す」という操作を forms.name("hoge") と書けるようになっています。

linkには以下のようなメソッドがあります。

href : リンク先のURL

text : aタグの中身のテキスト

node : aタグを表すHpricot::Elem

click : リンクをクリックし、結果のページを返す (newpage = agent.get(link.href) が、 newpage = link.click のように書ける)

formには以下のようなメソッドがあります。

[]=("foo", "bar") : name="foo" であるフィールドに値"bar"をセットする

submit : フォームをsubmitし、結果のページを返す (newpage = agent.submit(form) が、 newpage = form.submit のように書ける)

より詳細なリファレンスはWWW::Mechanize 日本語リファレンスを参照してください。

インストール

さて、そろそろ実際に使ってみたくなったでしょうか？:-) rubygemsをインストール済みなら、

gem install hpricot gem install mechanize

と、簡単なコマンドでインストールできます。

前述のとおりmechanizeはhpricotに依存しているので、 mechanizeを入れればhpricotは自動的に入ります。

Hpricotはつい最近version 0.5が出たので、昔インストールしたことがある人もアップデートをお勧めします。

Hpricotは途中でUnix版を入れる(ruby)かWindows版を入れる(mswin32)か聞かれるので、自分の使っている方を選んでください。

rubygemsを使っていない人はアーカイブをダウンロードし、中に入っているinstall.rbを実行すればOKです(たぶん)

まとめ

本稿では、RubyでWebから情報を得るときに役立つ2つのライブラリ、HpricotとWWW::Mechanizeを紹介しました。

これらを使うことで、HTMLのスクレイプやWebアクセスの自動化など今まで「面倒そうだなぁ」と思っていた処理が非常に簡単に書けるようになります。 Webでの情報収集を自動化したくなったとき、この2つのライブラリのことを思い出してもらえれば幸いです。