スポンサーリンク
Internet Explorer対応のスクレイピングプログラムも、最近はEdge化の影響で正常に動作しなくなってきました。
selenium basicをインストールしたけど、プログラムをどのように旧型式のソースコードからselenium化すれば良いか、迷った方も多いのではないでしょうか。
今回はそんな方々向けに、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
このコードは以下2つの意味を表しています。
- 「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」とドットで繋げれば抽出することが可能です。
最後までお付き合いいただきありがとうございます!
この情報が誰かの役にたてれば幸いです。