こんにちは。エンジニアのAraoです。今回は、RubyでのWebスクレイピングで役に立つ、Mechanizeというgemの簡単な使い方を紹介します。

Mechanizeって何?

Webスクレイピング入門、のような感じの技術系ブログの記事を読むと、例えばRubyの場合は、open-uriで指定のURLにアクセスし、NokogiriでHTMLから欲しい情報を取り出してくる、といったことがよく紹介されていると思います。もちろんそれは大切なことなんですが、せっかくならもう少し複雑な、ログインが必要なページにアクセスして情報を取得する、なんてこともやってみたいと思いますよね?

Mechanizeは、そんな希望をサポートしてくれるRubygemです。特に複雑なコードを書かなくても勝手にCookieを管理してくれて、Webページ上のフォームを簡単に操作できるため、ログインが必要なWebページを対象にしたスクレイピングにはもってこいです。また、Selenium + Headless Chromeと異なりRubygemで完結しているため、ChromeやChromedriverのバージョンが合わない、といったこともなく、動作も軽快です。ただし、通常のブラウジングでは動作するJavaScriptの実行ができない、という欠点もあります。そのため今時のJavaScriptがゴリゴリ動いてコンテンツが描画されていくようなWebページのスクレイピングには不向きです。裏側でAPIを叩いてそのレスポンスをいい感じに描画する程度の処理であれば、こちらでもそのAPIを叩いてそのレスポンスをパースして情報を取り出すこともできますが、今回は省略します。

この記事で扱うこと

ログインフォームのテキストボックスへの入力とフォームの送信を扱います。できることは他にもいろいろあるのですが、Mechanizeを使いたい、と思う理由のおそらく8割以上は先ほども説明したログインが必要なWebページを対象にしたスクレイピングなので、ひとまずこの記事で紹介することが自在にできるようになれば十分です。

今回対象にするサンプルページ

この記事のために、ログインとログアウトができるだけの簡単なWebページを作りました。

ログインページ

IDのテキストボックスにsample_id、パスワードのテキストボックスにsample_passを入力して、ログインボタンを押してみてください。さっき入力したIDとログアウトボタンだけのそっけないユーザーページが表示されると思います。ログアウトボタンを押すと、ログアウトして先ほどのログインページに戻ります。誤ったID、パスワードを入力するとログインできずログインページのままですが、特にエラーメッセージなどは表示されません。

Mechanizeのサンプルコード解説

まずは準備として、Mechanizeをインストールしましょう。Bundlerを使ってgemを管理している方は、自分が管理したいようにMechanizeをインストールしてください。

$ gem install mechanize

先ほどのページで、ログインページへアクセスし、フォームにID、パスワードを入力し、そのフォームを送信してユーザーページへアクセスし、ログアウトボタンを押してログインページに戻る、という操作をMechanizeで行うためのRubyのソースコードを紹介します。動作確認にはRuby 2.6.2を使っていますが、極端に古いバージョンでもない限り、問題なく動作すると思います。2.4以前のRubyだとppを使うのにrequire 'pp'が必要になるため、2.4以前のRubyを使っている方は、ソースコードの先頭に追記しておいてください。

require 'mechanize'

agent = Mechanize.new
agent.max_history = 2
agent.user_agent_alias = 'Mac Firefox'
agent.conditional_requests = false

login_page = agent.get('http://13.113.101.228:4567')
puts login_page.title

login_form = login_page.form_with(name: 'login')
sleep 1
pp login_form

login_form.field_with(name: 'id').value = 'sample_id'
login_form.field_with(name: 'pass').value = 'sample_pass'
# login_form.id = 'sample_id'
# login_form.pass = 'sample_pass'
sleep 1
pp login_form

sleep 1
user_page = login_form.submit
puts user_page.title

logout_form = user_page.form_with(name: 'logout')
sleep 1
pp logout_form

sleep 1
logout_page = logout_form.submit
puts logout_page.title

実行してみると、1秒間隔で何かいろいろ出力されると思います。ソースコードと出力内容を順番に追いかけていきましょう。

まず3行目から6行目です。ここではMechanizeの初期設定を行なっています。agentはブラウザのようなものと考えるのが一番理解しやすいです。4行目では、キャッシュを何件まで保持するかを指定しています。デフォルトでは無限に保持しますが、取得されたHTMLとそのHTMLをパースしたNokogiriオブジェクトなどを保持するため、たくさんのページを続けてアクセスしていくとメモリを食い潰す恐れがあります。0にしてしまうと現在開いているページの情報を取得するのにも苦労するため、特にこだわりがなければ2ぐらいにしておくのが良いです。5行目では、User Agentを設定しています。agent.user_agent = 'ユーザーエージェント'で直接User Agentを設定することもできますが、MechanizeにはデフォルトでそれっぽいUser Agentのエイリアスがあるため、それを設定しています。User Agentを設定しなかった場合、明らかにプログラムによるアクセスっぽいUser Agentが使われるので、普通は何か設定しておきます。6行目は、キャッシュに存在するページへの再アクセス時に更新チェックを行うかどうかを設定しています。デフォルトではこれはtrueなので、接続先からHTTPステータスコード304(Not Modified)が返ってきたときはキャッシュを再利用します。しかしそれが原因で不都合が生じることが稀にあるため、ここでは念のためfalseに設定しています。

続いて8行目と9行目、ここは大変分かりやすく、先ほど設定したagentでログインページへアクセスし、そのページのタイトルを出力しています。

次に11行目から13行目です。ここでは、先ほど取得したログインページに含まれるフォームのうち、nameがloginのものを指定して、そのフォームの情報を出力しています。name以外の条件も指定できたり、また正規表現が使えたりもするので、スクレイピング対象ページによって細かく使い分けていきましょう。フォームの情報はこんな感じに出力されると思います。

#<Mechanize::Form
 {name "login"}
 {method "POST"}
 {action "/login"}
 {fields
  [text:0x3fec428e41f8 type: text name: id value: ]
  [field:0x3fec428e9824 type: password name: pass value: ]}
 {radiobuttons}
 {checkboxes}
 {file_uploads}
 {buttons [submit:0x3fec428e9234 type: submit name:  value: ログイン]}>

今回のポイントはfieldsの部分です。type: text name: idがIDのフィールドで、type: password name: passがパスワードのフィールドです。それぞれvalue: と書かれている部分がそのフィールドに入力されている値なので、ページにアクセスした時点ではどちらも空欄になっています。最後のボタンはログインボタンで、そのフォームをsubmitすることが分かります。

フィールドへの値の入力を行なっているのが15行目と16行目です。先ほど取得したログインフォームのうち、IDとパスワードのフィールドをそれぞれ特定して、そのフィールドのvalueを設定しています。また、サンプルコードではコメントアウトしていますが、17行目と18行目のように書くこともできます。フィールドのnameがちゃんと設定されていて、なおかつ特殊文字を含まない場合は、このように書いた方が見た目がスッキリするのでオススメです。また、20行目でフィールドに値を入れた後のフォームの情報を出力しているので、そちらも合わせて確認しましょう。valueに値が入っているのが分かります。

#<Mechanize::Form
 {name "login"}
 {method "POST"}
 {action "/login"}
 {fields
  [text:0x3fec428e41f8 type: text name: id value: sample_id]
  [field:0x3fec428e9824 type: password name: pass value: sample_pass]}
 {radiobuttons}
 {checkboxes}
 {file_uploads}
 {buttons [submit:0x3fec428e9234 type: submit name:  value: ログイン]}>

23行目では、先ほど値を入力したフォームの送信を行なっています。その結果取得されたページのタイトルを、24行目で出力しています。「ユーザーページ」と出力されているはずです。

最後のログアウト処理ですが、フィールドこそないもののログアウトボタンもフォームなので、先ほどと同様にフォームを特定して、ボタンを押すためにsubmitする、ということをしています。一番下の32行目で、ログアウト後のページのタイトル「ログインページ」が出力されているはずです。

終わりに

一番単純なパターンのログインを、Mechanizeを使ってスクリプトで実行する方法を紹介しました。この記事ではテキストボックスしか扱いませんでしたが、セレクトリストやチェックボックス、ラジオボタンを特定、操作するためのメソッドも、もちろん用意されています。agentの初期設定の部分も、スマホのUser Agentを設定してスマホ向けのページを取得したり、User Agent以外にAccept-Languageなどを指定することで、取得するコンテンツの言語を変更することができます。Basic認証なんかにも対応しているので、Mechanizeを使うことで、より高機能なWebスクレイピングスクリプトを作成できます。皆さんも是非挑戦してみてください。