EC2 で Selenium WebDriver を動かしたときのメモ

Ruby + Selenium WebDriver を使ったプログラムを EC2 でを動かせるようにしたときのメモです。

Amazon Linux AMI では firefox や X 関係がいろいろと面倒だったので、 今回は Debian のオフィシャル AMI を使いました。 他のディストリビューションの方がパッケージが充実してて良いかもしれません。 AMI もオフィシャルのものがそれぞれリリースされているようです。

Debian オフィシャル AMI

下記に AMI の ID が記載されているので、EC2のインスタンス作成のときに指定します。

Cloud/AmazonEC2Image - Debian Wiki

ユーザー名は "admin" です。

初期設定

タイムゾーンの設定と tmux のインストール

$ sudo apt-get update
$ sudo dpkg-reconfigure tzdata
$ sudo apt-get install tmux

VNC サーバーのインストール

VNC サーバーとウィンドウマネージャをインストールします。

$ sudo apt-get install tightvncserver openbox

VNC で接続するときのパスワードの設定が事前に必要です。

$ tightvncpasswd

~/.profile に下記の設定を追加します。

export DISPLAY=":0"

Xorg とかをインストールしなくても、VNCサーバーがXサーバーの役割を果たしてくれるようです。

firefox のインストール

firefox-esr の本体と、日本語フォント(IPAフォント)を入れます

$ sudo apt-get install firefox-esr-l10n-ja fonts-ipafont

いつのまにか Debian にも firefox が。

ローカルマシンから VNC で接続

VNC サーバーを起動します。(openbox も勝手に起動します)

$ tightvncserver ":0"

ローカルマシンから ssh のポートフォワーディング(ローカルの 5900 からリモートの 5900 へ)を開始します。

$ ssh -L 5900:localhost:5900 admin@XXXXXX cat

この状態で、 Mac であれば Finder > 移動 > サーバーへ接続 を選択したあと、 サーバーアドレスに

vnc://localhost:5900

を入れてやれば VNC による接続ができます。

Ruby と gem

ruby と必要な gem をインストールします。 gem のネイティブ部分のコンパイルで怒られるのでいくつか追加でライブラリ類をインストールしています。

$ sudo apt-get install ruby ruby-dev zlib1g-dev
$ sudo gem install selenium-webdriver nokogiri pry

参考にさせていただいたページ

いろんなOSのAMIについてまとめられています

OSごとのオフィシャルなAMIをまとめてみた | mooapp

タイムゾーンの設定

Debianでタイムゾーンを変更する方法 -- ぺけみさお

emacs の markdown-mode と pandoc でちょっとだけ快適にブログを書く

Markdown形式でブログを書いていますが、なかなかスムーズにできません。

  1. emacs(fundamental-mode) で書いて
  2. はてなブログの編集画面にコピペして
  3. プレビューして見直し。おかしかったら1に戻る…
  4. 投稿

みたいな事をやっていたのですが、もうちょっとなんとかならんのかという事で、 改善方法を調べてみました。

emacsmarkdown-mode を入れる

まずは Markdown 用のモードを入れます。

M-x list-packages とすると、 「markdown-mode」というものが見付かったので インストールします。

これで特に何もしなくても *.md ファイルを開くと markdown-mode になります。 gfm-mode (Github Flavored Markdown) というモードも使えます。 はてなブログの場合はこちらの方があってるように思いました。

gfm-mode を関連付ける場合は下記の設定を init.el に追加します。

(add-to-list 'auto-mode-alist '("\\.md\\'" . gfm-mode))

pandoc でプレビューできるようにする

ブラウザでプレビューするために html に変換するツールを入れます。

の3つを試して、今回は pandoc を使う事にしました。 (他は github 風のコードブロックをうまく処理できませんでした)

brew でインストールできます。

brew install pandoc

emacs からのプレビューで pandoc を使うように init.el に下記を追加します。

(setq markdown-command "pandoc")

C-c C-c p で markdown-mode からプレビューできるようになりました。

CSS で見やすくする

そのままでは少し見にくいと感じたので、 CSS で見やすくします。

ここ から github 風の CSS をもらいました。 (そのままでは pandoc の出力する html にうまく適用されないので編集しました。 探せばそのまま使えるものがたくさんありました…)

pandoc にオプションを追加して css へのリンクを埋め込むようにします。

(setq markdown-command "pandoc -c ~/.pandoc/github-markdown.css")

ちょっとだけ快適になりました

ちょっとだけですが。

  • C-c C-c p 一発(3発?)でプレビューできます
  • Tab, Shift-Tab 本文の表示/非表示(見出しのみ)ができます
  • 選択範囲だけプレビューできます。(私は草稿を1つのファイルにまとめているので重宝)

このへんが特に気にいってます。

Markdown の書き方ですがこちらのページにいつもお世話になっています。

igcn.hateblo.jp

Selenium WebDriver + Ruby でWeb関係の自動化

RubyからSelenium WebDriverというのを最近知りました。 Webの自動化がとても簡単にできるようになり、感動したのでメモしておきます。

Selenium WebDriver がないとき

Webの自動化というと、ほぼ「Webブラウザの真似をするという事」になると思います。 操作したときにサーバーとどんなやりとりをしてるのか、ブラウザの開発ツール等を使って調べて、 同じ事をするようにコードを組んで…という流れになると思います。 これがなかなか思ったようにできません。

また、Webブラウザが、cookiejavascriptといった得体の知れないものも面倒見ている、 という事も考慮しなければなりません。「ページの内容によって処理が変わるけど、 見るべき要素は実はjavascriptの実行結果として生成されていた」なんてことになるともうお手上げ状態です。 普通にやってる人もいると思いますが、私の技倆ではちょっと…。

Selenium WebDriver があるとき

Selenium WebDriver は本物のWebブラウザを制御できます。 「このボタンを押すとこのURLにこんなものをPOSTするのかーふむふむ」とか考えなくてよくて、 単に「このボタンをクリックして!」と命令すればよいわけです。

「じゃあここにカーソルのっけて〜、ポップアップしてきたここをクリックして〜、うん、すごくいい、すごくかわいいよ〜」 みたいなあんな事やこんな事がいとも簡単(当社比)にできてしまいます。

Selenium というのが何かは実はよくわかっていませんが、 「selenium-webdriver という gem をインストールすると ruby からブラウザを制御できる!」 というのはおそらく間違いなさそうです。

インストール

gem install selenium-webdriver

これだけでいろいろできます。 ページの内容をごにょごにょしたいときは nokogiri も入れておくとさらに便利です。

操作いろいろ

私の場合は以下のものだけで充分事足りてます。

開始

require "selenium-webdriver"
require "nokogiri"

webdriver = Selenium::WebDriver.for(:firefox)

終了

webdriver.quit

URLを指定して取得

webdriver.get(url)

ページのソースからNokogiriオブジェクト

doc = Nokogiri::HTML(webdriver.page_source)

要素の取得(XPathを使う場合)

element = webdriver.find_element(:xpath, xpath)
  • 要素が見付からない場合は例外 Selenium::WebDriver::Error::NoSuchElementError を投げます。
  • クリックやキー入力といった操作はここで取得したオブジェクトに対して行います。
  • find_elements を使うと要素を配列で返します。複数取得可。見付からない場合は空の配列になります。
  • find_element(s) は要素オブジェクトに対しても使えます。

テキストエリア等に文字列入力

element.send_keys(str)

ドロップダウンメニューの選択(文字列指定の場合)

Selenium::WebDriver::Support::Select.new(element).select_by(:text, str)

チェックボックスの状態取得

element["checked"] # => true or false

マウスカーソルをのせる

webdriver.action.move_to(element).perform

shift押しながらクリック

webdriver.action.key_down(:shift).click(element).key_up(:shift).perform

ウインドウの切り替え

webdriver.switch_to.window(handle)

handle(ウインドウを識別する文字列)の取得は下記のとおり

current_handle = webdriver.window_handle
all_handles = webdriver.window_handles

ウインドウを閉じる

webdriver.close
  • 最後のウインドウを閉じるとブラウザが終了します

注意点

javascriptの実行タイミング

ページの取得(getやリンクのclickなど)は、メソッド呼び出し後、ページの読み込みが終わると制御が戻って、次の処理に進みます。そのため、ページ読み込み後に動き始めるjavascriptの実行後に生成される要素などを読み込み直後に取得しようとすると失敗する場合があります。

下記のようにすると、要素が現れるまでの待ち時間を設定できます(秒)

webdriver.manage.timeouts.implicit_wait = 1

page_sourceで取得するソースもjavascriptの実行結果の反映前後で変わってくる場合があるので同様の注意が必要です。

※私はうまくいかないところにひたすらsleepをつっこんでますけど…

見えない要素はさわれない

マウスカーソルを乗せるとポップアップする要素などは、見えてない状態で操作する事はできません。 ブラウザで操作するときと同じ手順を踏む必要があります。

リファレンス

http://www.rubydoc.info/gems/selenium-webdriver/Selenium/WebDriver/

他にもgoogleで色んなページ見て参考にさせていただいたのですが、控えてませんでした…。

知らなかった Enumerable#reduce

Enumerable#reduce というメソッドの存在を最近知りました。

http://docs.ruby-lang.org/ja/2.2.0/class/Enumerable.html#I_INJECT

ruby使ってる人からしたら当たり前の事とは思いますが、 使ってみるとすごくきもちよかったのでメモしておきます。

例1

例えば下記のような配列があり、平均値を求めたい場合

nums = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

eachを使うと次のようになりますが、 (私は最初for文を使おうとしましたよ…?)

sum = 0
nums.each{|num| sum += num}
average = sum / nums.size

p average # => 8

reduceを使うと、次のようになります。

average = nums.reduce(0){|sum, num| sum + num} / nums.size
  • 前のブロックの実行結果を渡してくれるので、ブロック外で一時変数を作らなくてよいし、
  • 最終的な結果を値として返してくれるので、式の中に組み込めるのが、

よいですね。

下記のような書き方もできます。

average = nums.reduce(:+) / nums.size

初期値に最初の要素を使う場合は省略する事ができ、 計算の内容が「メソッドを呼ぶだけ」という場合は引数でメソッド名をシンボルで渡す事でブロックは省略できます。

例2

ブロックの結果に色々仕込むと、便利に使えますね。 次の例は、前の数との差分の合計という無意味な計算です。

diff, = nums[1...nums.size].reduce([0, nums[0]]){|(result, prev), num| [result + (num - prev), num]}

p diff # => 34

別名 inject

Enumerable#inject というのもありますが、名前が違うだけなんですね。 reduceは「一つにする」みたいなイメージですが、 「注入!」的な使い方をする場合はこちらを使うとコードのニュアンスが伝わりやすくなるという事でしょうかね。

次の例は、なんか劇的にわかりにくいですが、前の数との差分を空の配列に注入するという無意味な処理です。

diffs, = nums[1...nums.size].reduce([[], nums[0]]){|(result, prev), num| [result << (num - prev), num]}

p diffs # => [1, 0, 1, 1, 2, 3, 5, 8, 13]

Enumerable

eachを使った素敵メソッドをたくさん提供してくれるモジュールですね。 私には思いが及ばないものがたくさんありました。ちゃんと見ておこうと思います。

読み方 http://ejje.weblio.jp/content/enumerable

無職なのでブログ始めてみました

初投稿です。ntkgと申します。どうぞよろしくお願いします。

きっかけ

無職なので時間があるから…というのもありますが。

英語のレッスンに通い始めた

言ってる事はなんとか理解できるが、英語でのアウトプットができない…はぁ

じゃあ英語以外は大丈夫なんでしょうねぇ?

日本語もダメしたぁ\(^o^)/

じゃあ練習しようか

という感じです。

書いていくこと

最近の関心事は、

  • プログラミングの学習(ruby, あとlispもいつかやってみたい)
  • 英語の学習
  • 投資(短期)
  • 健康
  • これからどうなってしまうんだろう…とかw

なのでそのあたりに偏ってくるかなと思います。 ですがネタの内容にはこだわらず、日々のできごととか思った事とかしょうもない事を、 できるだけたくさん書きたいと思ってます。

あと、「人様に読んでいただくための文章」を書く練習として、そのへんも意識しつつ…。

自己紹介

30半ばの独身のおっちゃん(初級)です。 以前ソフト開発の仕事をしていましたが、日々蓄積するウンザリ感に敗北し、 魔の詰みルートへ…

どうぞよろしくお願いします。