2016年2月1日月曜日

SeleniumBasicでExcelVBAからFirefoxを自動操作するための覚書

2016.02.06 一度開いたFirefoxのウインドウを使い回すコードを修正。

 Excel VBAでFirefoxを操れる素晴らしいツール。それがSeleniumbasic
以前はSeleniumVBAという名称だったが、昨年秋ごろにSeleniumBasicに変わった。今後SeleniumVBAの方はメンテされないそうなので注意。
インストール
ダウンロードページからインストーラをダウンロードして実行する。2016年2月2日現在はSeleniumBasic-2.0.7.0.exe。
インストール先は%LOCALAPPDATA%\SeleniumBasic。
インストーラの最後に、Firefoxアドオンを入れるかどうか聞かれる。これを入れておくと、Firefoxの操作を記録してコードをエクスポートすることができる(Excelのマクロ記録に近い感じ)。だけど、私は今のところ使っていないなあ。
Excelを起動し、ツールの参照設定でアドオンを有効にする。
Fig.1 参照設定でSelenium Type Libraryを有効化
これで準備完了。後はVBAエディタでコードをガリガリ書いていく。
オブジェクトを宣言する
以下を標準モジュールのどっかに書いておけばOK。
Public driver As New WebDriver
Public keys As New keys
Public by As by
SeleniumBasicには他にも色々なクラスがあるが、Firefox自動操作には取り急ぎ上記3種類があれば十分ではないかと思う。
Firefoxを起動する
まず真っ先にやることは、Firefoxを起動すること。
(起動済みのFirefoxを使い回す方法もある。後ほど。)
driver.Start "firefox", "https://dsp74118.blogspot.jp/"
driver.Get "/"
要素にアクセスする
WebDriverクラスにはFindElement(s)ByXXXXというメソッドが用意されてる。これは、JavaScriptやInternetExplorerオブジェクトにおけるdocument.getElement(s)ByXXXに相当する。 大体メソッド名で使い方は想像できると思う。
Fig.2 WebDriverクラスのメンバー
Dim elm as Object
set elm = driver.FindElementById("id1")
という感じで、DOMオブジェクトを変数に格納できる。
要素内のテキストを取得する
以下の様な感じで、id="id1"の要素のinnerTextを取得できる。
str = driver.FindElementById("id1").Text
innerHTMLを取得する方法は無いっぽい?誰か知ってたら教えて下さい。
テキストボックス等に文字を入力する
SendKeysメソッドを使う。
driver.FindElementByName("text1").SendKeys "みょみょみょ"
既に値が入っている場合は、1回clearをする。clearしないと、元から入っていた値の後ろに追記されてしまう。
InternetExplorerオブジェクトを使ってIEを自動操作する際には、DOMオブジェクトのvalueプロパティに値を入れるだけで、元の値を上書きしてくれるのだけど。ここは対照的。
driver.FindElementByName("text1").Clear
driver.FindElementByName("text1").SendKeys "abc"
keysクラスを使えば特殊キーも送れる。下記は一例。
driver.FindElementById("text2").SendKeys keys.Control, "a"
driver.FindElementById("text2").SendKeys keys.Delete
要素の存否確認
要素が存在しない時に、FindElementByXXXシリーズで要素にアクセスするとエラーで落ちちゃう。 例えば、name="name1"の要素が存在しない時に
driver.FindElementByName("name1")
ってやるとエラーになる。そのため、要素があるかどうか不確定の時は、事前に存否確認をする必要がある。
で、IsElementPresentというメソッドで、DOM内に特定の要素があるかどうか調べることができる。
第一引数には、by.Idとか、by.Nameとかがある。
…はずだったが、私の環境では動いたり動かなかったりする! なんで?
If driver.IsElementPresent(by.name("name2")) Then 'expect True of False
Fig.3 IsElementPresentが動かない...
謎エラー。
仕方がないので
if driver.FindElementsByName("name3").Count > 0 Then
とやってる。かっこわる。
あ、FindElementsByXXXシリーズ(sが入ってる)は、要素がなくてもエラーになりません。
x番目の要素を取り出す
HTML内のx番目のtableのy行目のz列目のテキストを取り出すなら、
xyzText = driver.FindElemntsByTag("table")(x).FindElementsByTag("tr")(y).FindElementsByTag("td")(z).Text
とやる。
同様に、ページ内の特定のボタンやラジオボタン、チェックボックスをクリックしたい時は、
driver.FindElementsByTag("input")(x).Click
とできる。
全input要素にidがついていれば、FindElementByIdで要素を特定すればよいが、世の中そうでない場合も多々あるので…。

全部の要素にアクセスするなら、forでループさせる。
Dim tags As Object, tag As Object
Set tags = driver.FindElementsByTag("a")
For Each tag In tags
    MsgBox tag.Text
Next
Countプロパティで数を調べてループする方法も使える。
Dim tags As Object, i as Integer
Set tags = driver.FindElementsByTag("a")
For i = 1 To tags.Count
    MsgBox tags(i).Text
Next
2番目以降の要素にだけアクセスしたいときは後者が便利(例えばtableの1行目がタイトル行になってて、2行目以降のデータだけ取り出したい時とか)
特定のリンク先をクリックする
XPathは個人的には嫌いだが使う。以下のような感じで、好きな要素(特定のattributeを持つ要素とか)を狙い撃ちできる。
href="../hogehoge.html"
driver.FindElementByXPath("//a[@href='" & href & "']").Click
どんな時に役立つかというと、私は上記のようにaタグを特定する時に使っている。特に、onClickで何かしてるリンクをクリックする時に便利だと思う。
多分
onClickString = "javascript:hoge()"
driver.FindElementByXPath("//a[@onclick='" & onClickString & "']").Click
とかもできる。試してないけど。
プルダウンメニューを選択する
AsSelect.SelectByXXX メソッドを使う。
<select name="select1">
 <option value="value1">text1</option>
 <option value="value2">text2</option>
</select>
こんなプルダウンがある時に、option内のtextでプルダウンを選択するなら
driver.FindElementByName("select1").AsSelect.SelectByText ("text1")
optionのvalueが分かっているなら、以下の方法も使える。
driver.FindElementByName("select2").AsSelect.SelectByValue ("value1")
プルダウンメニューの中身を取り出す
以下の様な感じで、プルダウンメニューのi番目のoptionのtextが取り出せる。
なぜかOptions(i)ではダメ。
optionText = driver.FindElementByName("select3").Options.Item(i).Text
属性(attribute)
以下の様な感じでattributeを取り出せる。
"getAttribute"と間違えがちなので注意。
href = driver.FindElementByName("a").Attribute("href")
一度開いたFirefoxのウインドウを使い回す
Seleniumというツールは、処理を走らせるたびにブラウザを開いて、処理が終わったら閉じる、という使い方を想定しているっぽい節がある。が、処理のオートメーションという要件からすると、ログインが必要なWebページで同じような処理を繰り返し行いたい場合だってある。そんなケースで、1回処理を流すたびにブラウザが開いて、ログインして、処理して、ログアウトして、ブラウザ閉じる…という動作をしていたら、まどろっこしくて仕方がない。ここは、一度開いたブラウザを、ログインしたままキープして使い回したいところ。

今のところ、下記のような関数で、この要件を実装している。
以下のコードはサンプルなので、私が実際に使っているものとは少し変えてある。
(IDとパスワードがベタ書きになってるとことか。)
Public driver As New WebDriver

' Firefoxで某システムを開く
' - Firefox未起動だったら起動し、某システムのログイン画面を表示して自動ログイン
' - Firefox起動済みで、どこか他所のドメインに移動してたら、某システムのトップページに戻る
' - Firefox起動済みでログイン済みだったら何もしない
' - Firefox起動済みでセッションが切れてたら自動再ログイン
' @return 失敗したらfalse
Public Function OpenExampleCom() As Boolean

    Dim winCount As Integer
    Dim errNum As Integer

    OpenExampleCom = False

    On Error Resume Next
    winCount = driver.Windows.Count
    errNum = Err.Number
    On Error GoTo 0
    ' ToDo この辺りの処理は精査必要?
    If errNum = 57 Or winCount = 0 Then
        ' Firefoxを起動して、ページを開く
        driver.Start "firefox", "http://example.com/"
        driver.Get "/login"
        ' ログイン
        If AutoLogin = False then
            GoTo finally
        End If
    Else
        If driver.baseUrl <> "http://example.com" Then
            ' baseUrlが変わってたらセットし直し、トップページに移動。
            driver.baseUrl = "http://example.com/"
            driver.Get "/topPage"
        End If
        ' セッション判定
        ' ログアウトorタイムアウト画面ならログイン画面に移動。
        ' 対象のページに合わせて適切に書き換えること。
        If driver.FindElementByTag("h1").Text = "Logout" Or _
         driver.FindElementByTag("h1").Text = "TimeOut" Then
            driver.Get "/login"
        End If
        ' 今いるのがログイン画面ならログインする。
        If AutoLogin = False then
            GoTo Finally
        End If
    End If

    OpenExampleCom = True

finally:
    ' 事後処理。何かあればここに書く。

End Function

' 自動ログイン
' @return 失敗したらfalse
Private Function AutoLogin() as Boolean

    AutoLogin = True

    ' 今いるのがログイン画面ならログイン処理を行う。
    ' 対象のページに合わせて適切に書き換えること。
    If driver.FindElementByTag("h1").Text = "Login" Then
        driver.FindElementById("userid").SendKeys "dsp74118"
        driver.FindElementById("passwd").SendKeys "Password"
        driver.FindElementByTag("input").submit
        ' ログイン成否判定。対象のページに合わせて適切に書き換えること。
        If driver.FindElementByTag("h1").Text = "Login Error" Then
            MsgBox "ログインに失敗しました。"
            AutoLogin = False
        End If
    End If

End Function
続きがあります。

2 件のコメント:

  1. すごいです。
    勉強になります。

    返信削除
  2. Casino Review and Bonus Code - JTHub
    Casino Bonus 전주 출장안마 Codes — All the 광주광역 출장안마 Casino players can choose 서산 출장마사지 to 경상북도 출장안마 play at JTRCasino. The game offers a generous welcome bonus of $5,000 and 고양 출장안마 a

    返信削除