その他

【VBAブラウザ操作】seleniumの導入前後コード比較(Before After)[No64]

投稿日:2020年10月12日 更新日:

スポンサーリンク

Internet Explorer対応のスクレイピングプログラムも、最近はEdge化の影響で正常に動作しなくなってきた。

seleniumbasicをインストールしたけど、プログラムをどのように旧型式のソースコードからselenium化すれば良いか、迷った人も多いのではないだろうか。

今回はそんな方々に向けた内容になると思う。

行いたいこと

今回のプログラムは次のことを行うプログラムとする。

yahooの「検索バー」上の「ウェブ」「画像」「動画」・・・の情報を取得しメッセージボックス出力する。

調べ方は該当サイトをデベロッパーツールで確認し操作する要素を確認するのは、どの手法を使っても同じ。

要素をコピーしてテキストファイルに貼ると、よりプログラムを作成しやすい。classで指定するクラス名を検索して色付けした。

className01 = "_33uErKrZG6jQv2aeVeGWGc"

className02 = "fQMqQTGJTbIMxjQwZA2zk _3tGRl6x9iIWRiFTkKl3kcR"

selenium:利用なし。のプログラム

プログラム01:従来のInternetExplorerオブジェクトを利用した場合


Sub RunTest_InternetExplorer()
    Dim objIE As InternetExplorer
    Set objIE = CreateObject("Internetexplorer.Application")
    
    objIE.Visible = True
    objIE.navigate "https://www.yahoo.co.jp/"
    
    Application.Wait Now() + TimeValue("00:00:03")
    
    Dim htmlDoc As HTMLDocument
    Set htmlDoc = objIE.document

    Dim className01 As String
    className01 = "_33uErKrZG6jQv2aeVeGWGc"
    
    Dim var As String
    var = htmlDoc.getElementsByClassName(className01)(0).innerHTML
'    var = htmlDoc.getElementsByClassName(className01)(0).innerText
'    var = htmlDoc.getElementsByClassName(className01)(0).outerHTML
'    var = htmlDoc.getElementsByClassName(className01)(0).outerText
    MsgBox var

    Dim listData As IHTMLElementCollection
    Set listData = htmlDoc.getElementsByClassName(className01)
    
    Dim varList01 As String
    Dim msgText01 As String
    For i = 0 To listData.Length - 1
        varList01 = listData(i).innerHTML
'        varList01 = listData(i).innerText
'        varList01 = listData(i).outerHTML
'        varList01 = listData(i).outerText
        msgText01 = msgText01 & "▼(i=" & i & ")" & varList01 & vbCrLf
    Next i
    MsgBox prompt:=msgText01, Title:="昇順(01)"

    Dim className02 As String
    className02 = "fQMqQTGJTbIMxjQwZA2zk _3tGRl6x9iIWRiFTkKl3kcR"
    
    Dim varList02 As String
    Dim msgText02 As String
    For i = 0 To listData.Length - 1
        varList02 = listData(i).getElementsByClassName(className02)(0).innerHTML
'        varList02 = listData(i).getElementsByClassName(className02)(0).innerText
'        varList02 = listData(i).getElementsByClassName(className02)(0).outerHTML
'        varList02 = listData(i).getElementsByClassName(className02)(0).outerText
        msgText02 = msgText02 & "▼(i=" & i & ")" & varList02 & vbCrLf
    Next i
    MsgBox prompt:=msgText02, Title:="昇順(02)"
    
    msgText02 = htmlDoc.getElementsByClassName(className01)(0).getElementsByClassName(className02)(0).innerHTML
''''    msgText02 = htmlDoc.getElementsByClassName(className01)(0).getElementsByClassName(className02)(0).innerText
''''    msgText02 = htmlDoc.getElementsByClassName(className01)(0).getElementsByClassName(className02)(0).outerHTML
''''    msgText02 = htmlDoc.getElementsByClassName(className01)(0).getElementsByClassName(className02)(0).outerText
    MsgBox prompt:=msgText02, Title:="昇順(02)-test"

End Sub

selenium:利用あり。

プログラム02:従来のプログラムを崩さないように努めた場合


Sub RunTest_seleniumWebDriver01()
    Dim driver As New EdgeDriver
    driver.Get "https://www.yahoo.co.jp/"
    driver.Wait 1000
    
    Dim htmlDoc As HTMLDocument
    Set htmlDoc = New HTMLDocument
    htmlDoc.body.innerHTML = driver.PageSource
    'htmlDoc -> all -> Item(*)内のinnerHTMLに値が入る。

    Dim className01 As String
    className01 = "_33uErKrZG6jQv2aeVeGWGc"

    Dim var As String
    var = htmlDoc.getElementsByClassName(className01)(0).innerHTML
'    var = htmlDoc.getElementsByClassName(className01)(0).innerText
'    var = htmlDoc.getElementsByClassName(className01)(0).outerHTML
'    var = htmlDoc.getElementsByClassName(className01)(0).outerText
    MsgBox var

    Dim listData As IHTMLElementCollection
    Set listData = htmlDoc.getElementsByClassName(className01)

    Dim varList01 As String
    Dim msgText01 As String
    
    'For Each iList In listData
    'Next
    For i = 0 To listData.Length - 1
        varList01 = listData(i).innerHTML
'        varList01 = listData(i).innerText
'        varList01 = listData(i).outerHTML
'        varList01 = listData(i).outerText
        msgText01 = msgText01 & "▼(i=" & i & ")" & varList01 & vbCrLf
    Next i
    MsgBox prompt:=msgText01, Title:="昇順(01)"

    Dim className02 As String
    className02 = "fQMqQTGJTbIMxjQwZA2zk _3tGRl6x9iIWRiFTkKl3kcR"

'''    '---[▼▼▼A]
'''    '期待していたコードでエラーになる。
'''    Dim varList02 As String
'''    Dim msgText02 As String
'''    For i = 0 To listData.Length - 1
'''        varList02 = listData(i).getElementsByClassName(className02)(0).innerHTML
''''        varList02 = listData(i).getElementsByClassName(className02)(0).innerText
''''        varList02 = listData(i).getElementsByClassName(className02)(0).outerHTML
''''        varList02 = listData(i).getElementsByClassName(className02)(0).outerText
'''
'''        msgText02 = msgText02 & "▼(i=" & i & ")" & varList02 & vbCrLf
'''    Next i
'''    msgText02 = listData(0).getElementsByClassName(className02)(0).innerHTML
'''    msgText02 = htmlDoc.getElementsByClassName(className01)(0).getElementsByClassName(className02)(0).innerHTML
'''    MsgBox prompt:=msgText02, Title:="昇順(02)"

'''    '---[▼▼▼B]
'''    '意図した結果は得られない。
    Set listData = Nothing
    Set listData = htmlDoc.getElementsByClassName(className02)

    Dim varList02 As String
    Dim msgText02 As String
    For i = 0 To listData.Length - 1
        varList02 = listData(i).innerHTML
'        varList02 = listData(i).innerText
'        varList02 = listData(i).outerHTML
'        varList02 = listData(i).outerText
        msgText02 = msgText02 & "▼(i=" & i & ")" & varList02 & vbCrLf
    Next i
'        msgText02 = htmlDoc.getElementsByClassName(className02)(0).innerHTML
    MsgBox prompt:=msgText02, Title:="昇順(02)"
    
End Sub

スポンサーリンク

プログラム03:selenium標準の書き方で書いた場合


Sub RunTest_seleniumWebDriver02()
    Dim driver As New EdgeDriver
    driver.Get "https://www.yahoo.co.jp/"
    driver.Wait 1000

    Dim className01 As String
    className01 = "_33uErKrZG6jQv2aeVeGWGc"

    Dim var As String
    var = driver.FindElementByClass(className01).Attribute("innerHTML")
'    var = driver.FindElementByClass(className01).Attribute("innerText")
'    var = driver.FindElementByClass(className01).Attribute("outerHTML")
'    var = driver.FindElementByClass(className01).Attribute("outerText")
'    var = driver.FindElementByClass(className01).Text
    MsgBox var
    
    Dim elm01 As Selenium.WebElements
    Set elm01 = driver.FindElementsByClass(className01)
 
    Dim varElm01 As String
    Dim msgText01 As String
    For i = 1 To elm01.Count
        varElm01 = elm01(i).Attribute("innerHTML")
'        varElm01 = elm01(i).Attribute("innerText")
'        varElm01 = elm01(i).Attribute("outerHTML")
'        varElm01 = elm01(i).Attribute("outerText")
'        varElm01 = elm01(i).Text
        msgText01 = msgText01 & "▼(i=" & i & ")" & varElm01 & vbCrLf
    Next i
    MsgBox prompt:=msgText01, Title:="昇順(01)"

    Dim className02 As String
    className02 = ".fQMqQTGJTbIMxjQwZA2zk._3tGRl6x9iIWRiFTkKl3kcR"
    
    Dim varElm02 As String
    Dim msgText02 As String
    For i = 1 To elm01.Count
        varElm02 = elm01(i).FindElementByCss(className02).Attribute("innerHTML")
'        varElm02 = elm01(i).FindElementByCss(className02).Attribute("innerText")
'        varElm02 = elm01(i).FindElementByCss(className02).Attribute("outerHTML")
'        varElm02 = elm01(i).FindElementByCss(className02).Attribute("innerText")
'        varElm02 = elm01(i).FindElementByCss(className02).Text
        msgText02 = msgText02 & "▼(i=" & i & ")" & varElm02 & vbCrLf
    Next i
    MsgBox prompt:=msgText02, Title:="昇順(02)"
End Sub

わかったこと

従来形式のプログラム(プログラム01)をselenium化は簡単なプログラムの置き換え作業だけで、できるかと期待していたが、それはできないことがわかった。

従来の「Dim htmlDoc As HTMLDocument」「Set htmlDoc = objIE.document」の形式は利用しないほうが良い

従来のコードでは次のようにしたhtmlDocが大活躍したコードが書けた。


    Dim htmlDoc As HTMLDocument
    Set htmlDoc = objIE.document

しかし、seleniumでは「webdriver.document」は用意されていない。

次のようにすれば再現できると期待したが完全ではないことが調査後判明した。


	Dim htmlDoc As HTMLDocument
    Set htmlDoc = New HTMLDocument
    htmlDoc.body.innerHTML = driver.PageSource

このコードの意味は「HTMLDocument」のオブジェクトで「htmlDoc」を宣言し(この時点ではhtmlDocはただの箱)、Newすることで「HTMLDocument」の構造を「htmlDoc」にセットしインスタント化。

その後、「htmlDoc」内の「body」内の「innerHTML」に「driver.PageSource」を貼り付けるコード。

VBA開発画面のウォッチを使うことでより流れを理解できる。ちなみにこのソース実行で、「htmlDoc -> all -> Item(*)内のinnerHTML」に値が入ることをウォッチで確認。

ここまで聞くと上手く行くように思える。

「htmlDoc」に「getElementbyID」「getElementsbyTagName」「getElementsbyName」「getElementsbyClassName」を1回する分にはエラーにならない。取得できる。

しかし、それぞれのgetを複数回(2回以上)行うと、「Microsoft Visual Basic」「実行時エラー '438':オブジェクトは、このプロパティまたはメソッドをサポートしていません。」と表示される(プログラム02だけでは)。

プログラム01では「htmlDoc」に2回Getを行ってもエラーにはならなかった。

恐らく「Set htmlDoc = objIE.document」の「htmlDoc」を扱う時は「objIE.document」を参照してみていたが、「htmlDoc.body.innerHTML = driver.PageSource」では用意したhtmlDocという箱に「driver.PageSource」を代入したため全ての情報を参照できない。ので複数回GETできないのではと考える。

そのため、IHTMLElementCollectionを使った従来のコードをそのまま移行し実行するとエラーになることが多いと思う。(コレクションにはGETした値が入ることが多い。それに対してもう一度GETするとトータルで複数回GETしたことになりエラーになる。)

どうしても複数回GETしたい場合、いきなり1回目のGETで2回目のGETを使う方法もある。ただし、1回目→2回目のように絞り込んでいけない為、目的の要素以外をGETする可能性あり。

半角スペースの入った複合クラス(複数クラス)は使えなくなる

例えば、「className01 className02」のクラスをGETする場合は「.className01.className02」とドットで繋げれば抽出できる。

最後までお付き合いいただきありがとうございます!

この情報が誰かの役にたてれば幸いです。

スポンサーリンク

タグ

-その他

© 2021 BookALittle