君の瞳はまるでルビー - Ruby 関連まとめサイト

Mechanize で JavaScript 部分にどう対処するかの考察

最終更新: 2015-03-31 (火) 21:53:19 (2318d)

目的

Mechanize を使ってウェブアクセスを自動化する際に障壁となるのが JavaScript です。

Mechanize は JavaScript を認識してくれません。

例えば以下の処理が入っただけで Mechanize は期待通りの動きをしてくれません。

  • onClick イベントを使った操作

jQuery などの JavaScript ライブラリが発達し、ページを動的に生成するということが自然と行われるようになった昨今Mechanize は無力なのでしょうか?

そんなことはありません。ほんの一工夫で目的を達成する方法をまとめていきます。

前提

本ページが想定するのは「特定ページに対する操作の自動化」です。

不特定多数のページにアクセスする自動巡回情報収集などの自動化は含みません。 不特定ページを扱おうとした場合、JavaScript 部分に関して足掛かりとなる前提が全くなくなります。

不特定ページを扱うには、汎用的に JavaScript を扱う必要が生じ、一工夫で済むものではなくなるため対象から除外します。別の技術を使う必要があるでしょう。

基本的な考え方

JavaScript がしてくれるはずの処理を Mechanize がしてくれないのならば、 代わりに自分で同じことをすれば良いというのが基本的な考え方です。

Web ページで JavaScript がする基本的なことは、

  • フォームの操作
  • HTML の操作

です。

Ajax と呼ばれる技術では、さらに

  • HTTP 通信

も入ってきます。

Ajax では HTTP 通信と HTML の操作を組み合わせて、

  • 動的にコンテンツ生成

をすることもあります。

しかし、JavaScript が無くても Ruby でそれぞれを簡単に実現できます。

以下に対応方法をまとめます。

操作対応方法
フォームの操作Mechanize でフォームに値を設定したり、操作したりすれば良い。
HTML の操作Mechanize で読み込んだ HTML を Nokogiri で編集すれば良い。
HTTP 通信Mechanize は HTTP/HTML のアクセスをエミュレートするライブラリだということを思い出すだけ。
動的コンテンツ生成HTML の操作と HTTP 通信ができるのだからできるに決まってる。ただし、+αが必要になる場合がある。

特定のページにまとを絞れば JavaScript の代わりを Ruby がするのは簡単です。

JavaScript の処理を Ruby で代わりに行うのですから、 操作対象ページの JavaScript を読み解く力は最低限必要です。

それぞれの操作について

フォームの操作

例えばボタンの onClick イベントで JavaScript がフォームの操作をしているとしましょう。

よくあるのは以下のようなシナリオです。

  • 押されたボタンによって
    • hidden 項目に特定の値を設定してから submit する。
    • form の action を変更し、アクセス先を変更してから submit する。

などです。

まずはエージェントの以下のメソッド群を使って操作対象のフォームを取得してください。

  • form_with
  • forms
  • forms_with

form_with で取得することが多いと思います。

以下、事例です。

# アクションからフォームを取得する例
form = page.form_with(:action => /login.php/)

# name からフォームを取得する例
form = page.form_with(:name => 'login')

フォームの各種 input 要素に値を設定するには以下のように書けば良いだけです。

form.要素名 = '値'

action の変更も同様に以下のように書けば良いだけです。

form.action = 'アクセス URL'

submit メソッドを呼び出せば、アクションを実行出来ます。

next_page = form.submit

簡単です。

HTML の操作

Mechanize は読み込んだ HTML を Nokogiri という HTML/XML 処理ライブラリによって管理しています。

Nokogiri を使うと HTML/XML 要素の

  • 取得
  • 追加
  • 更新
  • 削除

が簡単にできます。

何をするにもまずは取得です。

エージェントの search メソッド*1を使います。

# <div class="target_parts"> という要素を取得する事例
nodeset = page.search('div.target_parts')

search メソッドの引き数は、取得する HTML 要素を選択するためのセレクタと呼ばれるものです。XPath や CSS セレクタというものが使えます*2

search メソッドの戻り値は Nokogiri::XML::NodeSet です。以下のような各種メソッドを使い HTML を操作できます。

メソッド名操作内容
each選択した各要素に対して何か処理をする。
children子要素を取得する。
push要素を追加する。
replace要素を置き換える。
remove要素を削除する。
to_htmlHTML 文字列を取得する(デバッグ目的で使うと思う)。

他にもたくさんメソッドがあります。

これらを使ってページの HTML を書き換えます。これらを使った書き換えはページにすぐ反映されます。

デバッグ目的で書き換え後のページを見る場合、注意することがあります。

# 書き換え前の HTML が表示される(body は解析前の文字列なので変更が反映されない)
puts page.body

# 書き換え後の HTML を見たい場合はこちら
puts page.search("/").to_html

HTTP 通信

説明は不要なはずです。Mechanize は、HTTP/HTML のアクセスをエミュレートしてくれるライブラリです。

そのまま使ってください。

注意点は、

  1. 現在使っているエージェントをそのまま使うか。
  2. エージェントを新たに作るか。

ぐらいかと思います。

クッキーをそのまま引き継いでアクセスしたい場合がほとんどだと思うので 1 の方法が普通だと思います。

動的コンテンツ生成

を理解していれば、することは簡単です。

Ajax の基本的なパターンは、HTTP 通信で埋め込みたい HTML を動的に取得し、その HTML を特定の場所に埋め込むというものです。

HTTP 通信で取得した内容が HTML であれば、

  1. 読み込んだページの search メソッドで埋め込み対象を取得する。
  2. 埋め込み先の箇所を search メソッドで取得する。
  3. Nokogiri を使って 2 を 1 で置き換える(replace メソッドで簡単にできる)。

というパターンになると思います。

以下、事例です。

# now_page という現在のページが既にあるとする。
# now_page の <div class="mount_position"> に parts_page の内容を埋め込みたい。
parts_page = agent.get("埋め込みたい内容があるページのURL")
mount_position = now_page.search("div.mount_position")
mount_position.replace(parts_page.search("/"))

変化形として Ajax の通信では JSON で情報の受け渡しを行う場合があります。

Ruby 1.9 には標準で JSON ライブラリがあり、これを使えば簡単です。

# now_page という現在のページが既にあるとする。
# 埋め込みたい内容は JSON でハッシュとして送られてくる。
# ハッシュの content に埋め込む内容が入っている。
# now_page の <div class="mount_position"> に parts_page の内容を埋め込みたい。
parts_json = JSON::Parser.new(agent.get("埋め込みたい内容があるページのURL").body)
parts_page = Nokogiri::HTML.parse(parts_json["content"])
mount_position = now_page.search("div.mount_position")
mount_position.replace(parts_page)

JavaScript を読み解くコツ

Ruby で JavaScript の代わりの操作をするのは簡単です。

その他に必要となるのは、解析対象となる JavaScript を見つけ出し、読み解く力です。

まずは JavaScript が起動する入り口を見つけましょう。

  • onLoad や onClick イベントが使われている場合
    • イベントを発見すれば良いだけです。イベントで関数を呼び出しているはずなので、その関数を探して読んでいけば良いだけです。
  • 目ぼしいイベントの設定が無いのに何か行われていそうな場合
    • jQuery などによりイベントがフックされている可能性があります。jQuery の事例だと以下のような記述が見つかる可能性があります。
$(document).ready(function(){
  # ページが読み込まれたときにしたい処理
});
$('フォームを探す文字列').submit(function() {
  # submit したときの処理
});

$('ボタンを探す文字列').click(function() {
  # ボタンをクリックしたときの処理
}

次に HTTP 通信を行っている場所についてですが、jQuery の場合は以下のような記述が見つかるはずです。

$.ajax({
 type: "get",
 url : "アクセスURL",
 data: "パラメータ",
 success: function(data) {
   # 成功した場合の処理
 },
 error  : function() {
   # エラーが発生した場合の処理
 }
});

XMLHttpRequest を直接利用している事例はもうあまり見掛けないのではないと思われますが、見掛けたらそれも HTTP 通信を行っている箇所です。

HTML の操作は、要素名に着目するのが重要です。form/input/div/span などについている name/id/class を探せば読み解けると思います。

コメント

本ページの内容に関して何かコメントがある方は、以下に記入してください。

最新の10件を表示しています。 コメントページを参照

  • 有益な情報ありがとうございます。rails4でmechanizeを使ってみたところjavascriptに対応していないということでこちらに辿り着きました。次のようなリンクをクリックする方法をご教示頂きたくコメントしました。<a href="#" onclick="javascript:submitNext(); return false">こちら</a> 色々試してみたり、検索してみたりしたのですがわからず質問させていただきました。ご回答いただければ幸いです。 -- newbie 2015-05-24 (日) 15:44:12
  • そのリンクは、クリック時に submitNext という JavaScript の関数を呼び出すということを示しています。submitNext という関数は標準には存在しないので独自に定義されたものと思って良いでしょう。submitNext がどこかに定義されているのでまずは探してください。 -- トゥイー 2015-05-27 (水) 10:17:05
  • submitNext を見つけたら具体的な遷移方法が書いてあるはずです。JavaScript は当然 Mechanize では動かないので、そこに書いてある内容を Mechanize で代わりに実行してやれば動くはずです。 -- トゥイー 2015-05-27 (水) 10:20:10
お名前: