Webブラウザの自動操作 (Selenium with Rubyの実例集)
[履歴] [最終更新] (2016/06/03 21:52:52)
1
作品
388
技術情報
最近の投稿
ここは
趣味の電子工作を楽しむ人のためのハードウェア情報共有サイト

技術情報や作品の投稿機能、リアルタイム遠隔操作 API をご利用いただけます。
新着作品

概要

人間がブラウザを用いて手作業でできること:

  • 業務でのWebシステムの操作
  • 巡回サイトでの定型処理
  • Webアプリケーションの自動テスト
  • etc.

は、プログラミングによって自動化できます。例えばPerlではWWW::Mechanizeというモジュールを用いて上記のような自動操作が可能です。同様にRubyにもMechanizeというライブラリがあります。有益なまとめ記事も多数書かれています。

しかしながら、これらのツールはFirefoxやIEといった各種ブラウザを完全にエミュレートできておらず、例えばJavaScriptが多用されているサイトだと自動化が簡単には達成できなかったりします。そこで、Seleniumという、ブラウザをエミュレートするのではなくブラウザを操作するツールを用いることで作業を自動実行することを試みます。

Seleniumについて

Seleniumはブラウザを自動操作するためのプロジェクト群です。簡単な自動化であればSelenium IDEというプロジェクトで開発されているFirefoxのアドオンを使うのがよさそうですが、ここではもっと複雑な自動化にも対応できるSelenium WebDriverの使用方法を紹介します。

自動化に用いる言語について

Java, C#, Python, Ruby, Perl, PHPがサポートされていますが、ここではRubyを用いた例を示すことにします。

公式ドキュメント

このページで紹介する内容を越えるものは、公式ドキュメントを参照してください。

インストール (Ruby)

ターミナルで以下のコマンドを実行します。

gem install selenium-webdriver

Seleniumを使用するためには、下記二行が必要です。ただし、ruby1.9以降では"require 'rubygems'"は不要です。

#!/usr/bin/ruby
require 'rubygems' # not required for ruby 1.9 or if you installed without gem
require 'selenium-webdriver'

操作するブラウザを指定

各種ブラウザを操作するためには、それらが事前にインストールされている必要があります。Googleのトップページを表示するだけのサンプルを示します。実行後にブラウザが自動で閉じるかどうかはドライバーに依存します (明示的に閉じるためには、quitメソッドを用います)。サポートされているブラウザのうち、Firefox、Internet Explorer、Google Chromeの使用例を示します。

Firefox

Windows、Mac、Linuxで動作が確認されています。

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://google.com"

Internet Explorer

現在、Internet ExplorerはWindows版しかありませんので、下記サンプルはWindowsでしか動作しません。ブラウザ本体とは別に、IEDriverServerという、SeleniumとブラウザをつなぐソフトをダウンロードしてPATHを通しておく必要があります。2013-8-21(Wed)現在、最新版はGoogle Codeにホスティングされています。こちらからダウンロード可能です。なお、ブラウザ表示倍率の設定を100%以外にしているとエラーが発生することがあります。その場合は100%に設定してからスクリプトを実行してみてください。

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :ie
driver.get "http://google.com"

Google Chrome

Linux、Mac、Windowsでの動作が保証されています。GoogleChromeブラウザ本体のインストールとは別に、Chrome Driverという、SeleniumとブラウザをつなぐソフトをダウンロードしてPATHを通しておく必要があります。2015-2-27 現在、最新版はこちらからダウンロード可能です。

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :chrome
driver.get "http://google.com"

プロキシ設定

IEとChromeの場合、システムのプロキシ設定を参照するため、そちらを手動で書き換えればよいです。Firefoxに関してはシステムのプロキシ設定を参照せず、独自の設定を持つため、プログラム上での設定が必要になります。

#!/usr/bin/ruby
require 'selenium-webdriver'

PROXY = 'proxy.example.com:8080'
profile = Selenium::WebDriver::Firefox::Profile.new
profile.proxy = Selenium::WebDriver::Proxy.new(
  :http     => PROXY,
  :ftp      => PROXY,
  :ssl      => PROXY
)

driver = Selenium::WebDriver.for :firefox, :profile => profile
driver.get "http://google.com"

http_proxyという環境変数が定義されている場合

gemなどのプロキシ設定のためにhttp_proxyという環境変数を定義してしまっている場合、その環境変数を削除するかno_proxyという環境変数を別途用意して127.0.0.1を値として代入しておく必要があります。下記のStack Overflowの解決策で記述されているように、ローカルホストで動作しているWebdriverへのアクセスではproxyサーバにアクセスしないようにする必要があるのです。

Acess denied to /hub/session when launching Firefox webdriver

The issue was not directly to do with permissions but with an environment
variable which was not read when we ran as root. We have the http_proxy
environment variable set with no exclusions for localhost. This meant that
the Python client was attempting to connect to the WebDriver via the proxy.
This was not an issue when run as root because http_proxy was not set.
This issue can be fixed by setting the no_proxy environment variable for localhost.

Waitをかける

環境によってはページの読み込みが完了する前に次の処理を開始してしまう場合があります。その場合、セレクターを用いてID指定などで検索しても、JavaScriptで動的に生成される要素などを考慮すると、検索対象が存在しないことになり自動化がうまくいきません。これを回避するために、適宜waitを挟みながら処理を進めるようにプログラミングするとよいです。下記サンプルでは、some-dynamic-elementというIDを持つ要素が (JavaScriptで生成されて) 10秒以内にページ内に出現しない限り例外を投げて、driver.quitで処理を中断するように記述されています。Googleのトップページにはそのような要素はありませんので、10秒後処理が中断されYahooのページは表示されません。

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://google.com"

wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
begin
  element = wait.until { driver.find_element(:id => "some-dynamic-element") }
ensure
  driver.quit
end

# 実行されない
driver.get "http://www.yahoo.co.jp"
sleep 10

すべてのfind_element(s) で上記timeout構文が必要となる場合、下記のようにdriverにwaitを設定することで一括して設定できます。

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.manage.timeouts.implicit_wait = 10 # seconds

driver.get "http://google.com"
element = driver.find_element(:id => "some-dynamic-element")

なお、単純なwaitであれば、rubyのsleepメソッドで事足ります。これは目視でページの確認をしながら先に進めたい場合に有効です。

スクリーンショットを撮影

ドライバーによってはサポートされていませんが、スクリーンショットを撮影できます。

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://google.com"
driver.save_screenshot "/tmp/google.png"

ページ情報 (URL、タイトル、HTML) を取得

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://www.example.com/"

p driver.current_url
p driver.title
p driver.page_source

要素セレクタ

セレクタメソッドは二種類あります。

  • find_element: 該当要素がなければ例外を投げます
  • find_elements: 結果を配列で返します。該当要素がなければ空の配列を返します

上記セレクタメソッドに与える式としては、XPathやjQueryでもお馴染のCSSセレクタ等があります。

サンプルコード:

#!/usr/bin/ruby
require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://www.example.com/"

# 一般のセレクタ
elements = driver.find_elements(:id => "myid") # ID <div id="myid"></div>
elements = driver.find_elements(:class_name => "myclass") # クラス <div class="myclass"></div>
elements = driver.find_elements(:tag_name => "div") # タグの種類 <div class="myclass1"></div>, <div class="myclass2"></div>,...
elements = driver.find_elements(:name => "myname") # 名前 <div name="myname"></div>
elements = driver.find_elements(:xpath => "address") # XPath
elements = driver.find_elements(:css => "ul#smaple-id li") # CSSセレクタ

# <a>タグに特化したセレクタ
elements = driver.find_elements(:link_text => "click") # <a href="">click</a>
elements = driver.find_elements(:partial_link_text => "click") # <a href="">click here</a>

# メソッドチェーン
elements = driver.find_element(:tag_name => "body").find_elements(:xpath => 'div/p/a')

# イテレート
elements.each do |element|
  puts element.text.encode('UTF-8')
end

CSSセレクタおよびXPathの簡単な設定 (Firefox)

複雑なサイトの自動化を試みる場合、CSSセレクタおよびXPathの設定が困難です。Firefoxのアドオンを利用することで設定の手間が削減できます。

  • Firebug: 要素インスペクタで選択した後、右クリックでCSSセレクタまたはXPathを取得可能
  • Firefinder for Firebug: CSSセレクタまたはXPathで実際に要素を選択できるかテスト可能

要素情報を出力

# 指定した要素内のテキストを取得 (jQueryと似ている)
p element.text
p element.attribute("id")

リンクを押す

element.click

戻る/進む

driver.navigate.back
driver.navigate.forward

フォームへの情報入力およびサブミット

# フォームの内容をクリア
element.clear

# inputまたはtextarea要素への値の代入
element.send_keys("my name");

# ラジオボタン
elements[3].click

# ドロップダウンリスト
select = driver.find_element(:tag_name => "select")
all_options = select.find_elements(:tag_name => "option")
all_options.each do |option|
  option.click if (true) # 何かしらの条件を指定
end

# ドロップダウンリストの別の記述方法
select = Selenium::WebDriver::Support::Select.new(driver.find_element(:tag_name => "select"))
select.select_by(:index, 0) # 0,1,2,...
# select.select_by(:text,  "mytext") # 表示されるテキストによる選択
# select.select_by(:value, "myvalue") # valueによる選択

# サブミット (elementが所属するformのサブミットが行われる)
element.submit

ウィンドウを複数開く

driver = Selenium::WebDriver.for :firefox
driver.get "http://www.example.com/"

driver2 = Selenium::WebDriver.for :firefox
driver2.get "http://www.example.com/"

ポップアップボックスを処理する

JavaScriptのalert(), confirm(), prompt()関数によって、ポップアップボックスが表示されるページがあります。Seleniumでそれらを制御するためには、まず

alert = driver.switch_to.alert

によって制御対象をアクティブなポップアップボックスに移します。そして、下記4つのメソッドによって制御ができます。ポップアップボックスが消えた後は自動的に制御はもとのブラウザに戻ります。

  • alert.text によってボックス内のテキストを取得 (alert,confirm,prompt)
  • alert.accept によって「はい」「OK」ボタンをクリック (alert,confirm,prompt)
  • alert.dismiss によって「いいえ」「キャンセル」ボタンをクリック (confirm,prompt限定)
  • alert.send_keys "some text input" によって入力ボックスへの値の入力 (prompt限定)

なお、これらのメソッドでは基本認証あるいはベーシック認証と呼ばれるものへの入力はできませんのでご注意ください。

ダウンロードファイルを確認なしで自動的に保存する (Firefox)

CSVやzipファイルをダウンロードする場合、既定では「アプリケーションで開くかダウンロードして保存するか」を確認するポップアップボックスが表示されます。このポップアップボックスを表示されてしまうと以降の自動化ができないため、Firefoxのプロフィール (URLバーにabout:configと入力して確認および変更ができる、Firefoxの挙動の設定群) を設定して指定のディレクトリに確認なしで保存するようにします。

profile = Selenium::WebDriver::Firefox::Profile.new

# ファイルをダウンロードする既定のフォルダ
# - 0: デスクトップ
# - 1: システム既定のダウンロードフォルダ
# - 2: ユーザ定義フォルダ (browser.download.dir で指定)
profile['browser.download.folderList'] = 2

# ダウンロード先を指定 (バックスラッシュが二つ必要。環境によっては1つだけでいいかも)
profile['browser.download.dir'] = 'C:\Users\YourName\Desktop'

# ファイルをポップアップボックスなしで自動的に保存 (CSVファイルの例)
profile['browser.download.useDownloadDir'] = true
profile['browser.helperApps.neverAsk.saveToDisk'] = "text/plain,
  application/vnd.ms-excel,text/csv,text/comma-separated-values,application/octet-stream"

# ドライバーを生成
driver = Selenium::WebDriver.for :firefox, :profile => profile

こちらのページを参考にしました。

ドラッグ・アンド・ドロップ

element = driver.find_element(:name => 'source')
target = driver.find_element(:name => 'target')

driver.action.drag_and_drop(element, target).perform

具体例

関連ページ
    概要 こちらのページで紹介されているSeleniumを用いて、OAuthを用いずにTwitterに自動投稿するRubyスクリプトを記述してみます。連続で複数回実行すると、ボット判定としてキャプチャ認証が発生します。その認証までは通過できませんので悪しからず。また、Twitterの仕様変更次第ではDOMの構造が変化するため、下記サンプルは機能しなくなる恐れが有ります。
    概要 様々な局面でHTMLの解析が必要になります。例えば、こちらのページでも紹介したSeleniumで複雑なことをしようとすると、driver.page_sourceで取得したHTMLを解析したくなります。このような用途のために、RubyにはNokogoriというツールがあります。 公式ドキュメント 本ページの内容を越えるものは、