SeleniumのChromeDriverを自動でダウンロードする方法(VBA編)
Seleniumを組み込んだツールを使っていると、ブラウザ(Chrome)とWebドライバー(ChromeDriver)のバージョン不一致によりエラーとなることがある。感覚的には2か月に一度程度の頻度で発生する。Chromeが自動更新されるためだ。
これまでは手作業でChromeDriverをダウンロードして所定の場所に配置してきたが、地味に面倒なので、自動化することを思い立った。
Chromeを起動できればオーソドックスなSeleniumの操作法でバージョンを取得できると思われるが、そもそもバージョン不一致でChromeを操作できない。プログラムでChromeを起動する前にバージョンチェックして、ChromeDriverを自動で最新化する必要がある。
以下の流れで処理を行う。
- Chromeのバージョンを取得
- Chromeのバージョンに一致するChromeDriverをダウンロード
- ダウンロードしたZipファイルを解凍
- ChromeDriverを所定の場所に格納
- メイン処理(1〜4の実行)
Chromeのバージョンを取得
Chromeのインストールフォルダ名がバージョン番号になっているので、そのフォルダ名を取得する。また、Chromeの更新の関係で新旧フォルダが混在することがあるらしいので、最新のバージョン番号に対応するフォルダ名を取得する。
サンプルコードを示す。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| Function getChromVersion(errStr)
On Error GoTo errH
Dim FSO, f
Dim regEx, matches, match
Dim mv, latest
' 正規表現オブジェクトの生成とパターンのセット
Set regEx = CreateObject("VBScript.RegExp")
With regEx
.Pattern = "[0-9\.]+" '数値またはピリオド
.Global = True
End With
' Chromeのメジャーバージョンを取得
Set FSO = CreateObject("Scripting.FileSystemObject")
latest = 0
For Each f In FSO.GetFolder("C:\Program Files (x86)\Google\Chrome\Application").SubFolders
If regEx.test(f.Name) Then
mv = Left(f.Name, InStr(f.Name, ".") - 1)
If latest < mv Then
latest = mv
End If
End If
Next
getChromVersion = latest
finally:
On Error Resume Next
Set FSO = Nothing
Set f = Nothing
Set regEx = Nothing
Set matches = Nothing
Set match = Nothing
Exit Function
errH:
errStr = "getChromVersion error! Err.Number=[" & err.Number & "], Err.Description=[" & err.Description & "]"
getChromVersion = -1
Resume finally
End Function
|
18〜25行目がインストールフォルダに対するループ。フォルダ名が数字とピリオドで構成されているものを正規表現を使って取得している。バージョンの形式は例えば「84.0.4147.89」となっており、この中からメジャーバージョン「84」を取得する。この関数はメジャーバージョンの最大値を返している。
注意すべきは、近いうちにChromeのインストールフォルダが変更されることだ※1。 変更されたら、プログラムを修正する必要がある。
Chromeのバージョンに一致するChromeDriverをダウンロード
以下の流れで処理を行う。
① ChromeDriverダウンロードサイトのトップページ情報を取得
② ダウンロードページのURLを取得
③ ダウンロード実行
サンプルコードを示す。
①は8〜24行目、②は26〜35行目、③は40行目〜44行目に対応している。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| ' ChromeDriverのダウンロード
Function downloadChromeDriver(latest, dest, errStr)
On Error GoTo errH
Dim objHtml, objDoc
Dim aTag, skey, targetUrl
' XMLHTTPオブジェクトを生成
Set objHtml = CreateObject("MSXML2.XMLHTTP")
' 指定URLのHTMLオブジェクトを取得
Call objHtml.Open("GET", "https://chromedriver.chromium.org/downloads", True)
Call objHtml.send
' ロード完了まで待つ
Do While objHtml.readyState <> 4
DoEvents
Loop
' HTMLドキュメントを作成
Set objDoc = New HTMLDocument
' HTMLドキュメントにHTMLテキストを書き込み
Call objDoc.Write(objHtml.responseText)
' Chromeのメジャーバージョンと一致するChromeDriverのダウンロードページを取得する
skey = "https://chromedriver.storage.googleapis.com/index.html?path="
For Each aTag In objDoc.getElementsByTagName("a")
targetUrl = aTag.getAttribute("href")
If InStr(targetUrl, skey) > 0 Then
If CInt(Mid(targetUrl, Len(skey) + 1, InStr(Len(skey) + 1, targetUrl, ".") - (Len(skey) + 1))) = latest Then
Exit For
End If
End If
Next
' ダウンロードファイルが見つからなかった場合は処理終了
If InStr(targetUrl, skey) = 0 And InStr(targetUrl, latest) = 0 Then GoTo notfound
' ダウンロードページよりWindows用のzipファイルをダウンロードする
targetUrl = Replace(targetUrl, "index.html?path=", "") & "chromedriver_win32.zip"
If URLDownloadToFile(0, targetUrl, dest, 0, 0) <> 0 Then
GoTo errH
End If
downloadChromeDriver = 1
finally:
On Error Resume Next
Set objHtml = Nothing
Set objDoc = Nothing
Exit Function
notfound:
errStr = "Download file is not found!"
downloadChromeDriver = 0
Resume finally
errH:
errStr = "downloadChromeDriver error! Err.Number=[" & err.Number & "], Err.Description=[" & err.Description & "]"
downloadChromeDriver = -1
Resume finally
End Function
|
②(26〜35行目)について。[ダウンロードサイト][7]のトップページには個別のバージョンのページに遷移するリンク(aタグ)が多数ある。aタグのhref属性は、例えば「https://chromedriver.storage.googleapis.com/index.html?path=85.0.4183.38/」のようになっていて、バージョン番号が含まれている。多数あるaタグをループしてhref属性からメジャーバージョンを取得し、「1. Chromeのバージョンを取得」で取得済みのメジャーバージョン番号に一致していれば、そのaタグのhref属性(ダウンロードページURL)が目的のバージョンのダウンロードページとなる。
③(40行目〜44行目)について。ダウンロードページにてWindows用のzipファイルのリンクが貼られており、例えば「https://chromedriver.storage.googleapis.com/85.0.4183.38/chromedriver_win32.zip」のようになっている(図2参照)。②で取得したURLから「index.html?path=」の部分を削れば、それがダウンロード元のURLとなる。実際のダウンロードはWin32APIを利用するが、それにはソースヘッダー部分に以下の宣言をしておく必要がある。
1
2
3
4
5
6
| Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" _
(ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long
|
ダウンロードしたZipファイルを解凍
サンプルコードを示す。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| 'zipファイルを解凍
Public Function unZip(src, dest, errStr)
On Error GoTo errH
Const FOF_SILENT = &H4 '進捗ダイアログを表示しない。
Const FOF_NOCONFIRMATION = &H10 '上書き確認ダイアログを表示しない([すべて上書き]と同じ)。
'指定のフォルダが存在するかチェック
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FolderExists(dest) Then
Else
MkDir dest
End If
Set FSO = Nothing
'解凍実行
With CreateObject("Shell.Application")
.Namespace(dest).CopyHere .Namespace(src).Items, FOF_NOCONFIRMATION + FOF_SILENT
End With
unZip = 1
finally:
On Error Resume Next
Set FSO = Nothing
Exit Function
errH:
errStr = "unZip error! Err.Number=[" & err.Number & "], Err.Description=[" & err.Description & "]"
unZip = -1
End Function
|
Windowsのシェルを使う。
Shell.Application.Namespace().Itemsで Zipファイルの中にあるファイル情報を取得して、Shell.Application.Namespace().CopyHereで指定した場所にファイルをコピーする。
この処理をオプション無しで実行すると、解凍フォルダがすでに存在している場合は、上書きするかどうかを確認するダイアログが表示される。自動化しようとしているので、こんなもの表示されるのはまずい。また、解凍処理の進捗ダイアログも表示されるが、これも特に要らない。
20行目の「FOF_NOCONFIRMATION + FOF_SILENT」は、これらのダイアログを表示させないためのものだ。
ChromeDriverを所定の場所に格納
サンプルコードを示す。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| ' 解凍したChromeDriverを所定のフォルダに配置
Function copyChromeDriver(src, dest, errStr)
On Error GoTo errH
Dim FSO
Set FSO = CreateObject("Scripting.FileSystemObject")
FSO.CopyFile src, dest, True
copyChromeDriver = 1
finally:
On Error Resume Next
Set FSO = Nothing
Exit Function
errH:
errStr = "copyChromeDriver error! Err.Number=[" & err.Number & "], Err.Description=[" & err.Description & "]"
copyChromeDriver = -1
Resume finally
End Function
|
関数にするほどのこともないかと思ったが、以下で示すメイン処理の記述の統一感を出したかったので、あえてこうしてみた。特別なことはしていない。
メイン処理
基本的に、これまで見てきた関数を呼び出すだけだ。サンプルコードを示す。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| Function autoDownloadChromedriver(errStr)
On Error GoTo errH
Dim latest, ret
Dim src, dest
Dim downloadFolder
downloadFolder = CreateObject("Shell.Application").Namespace("shell:Downloads").self.Path
' Chromeのバージョンを取得
latest = getChromVersion(errStr)
If latest < 0 Then GoTo errH
' Chromeバージョンに一致するChromeDriverをダウンロード
dest = downloadFolder & "\chromedriver_win32.zip"
ret = downloadChromeDriver(latest, dest, errStr)
If ret <= 0 Then GoTo errH ' 戻り値[0]: ダウンロードファイル無し、戻り値[-1]: エラー
' ダウンロードファイルの解凍
src = downloadFolder & "\chromedriver_win32.zip"
dest = downloadFolder & "\chromedriver_win32"
ret = unZip(src, dest, errStr)
If ret < 0 Then GoTo errH
' 所定の場所に格納
src = downloadFolder & "\chromedriver_win32\chromedriver.exe"
dest = Environ("LOCALAPPDATA") & "\SeleniumBasic\"
ret = copyChromeDriver(src, dest, errStr)
If ret < 0 Then GoTo errH
autoDownloadChromedriver = 1
finally:
On Error Resume Next
Exit Function
errH:
autoDownloadChromedriver = -1
Resume finally
End Function
Sub driver()
Dim ret, errStr
ret = 0: errStr = ""
ret = autoDownloadChromedriver(errStr)
End Sub
|
8行目は 、ダウンロードフォルダを取得している。
15行目は、ダウンロードするファイル名の設定。
17行目は、戻り値判定が不等号となっている。downloadChromeDriver
はnotfoundのステータスを返すことがあるため、不等号による判定としている。
20行目は ダウンロードするファイル名の設定、21行目は解凍時に作成されるフォルダ名の設定。
26行目は、解凍されたフォルダ内にあるChromeDriverのファイル名の設定。21行目のフォルダと一致している必要がある。
27行目は、ChromeDriverの格納先を取得している。Selenium導入時にパスを変えていたら、この27行目の処理も導入先のパスに変える必要がある。
40行目以下は、メイン処理を動かすためのドライバだ。
おわりに
あとは、Seleniumを使っているツールの初期処理などで、上記のメイン処理を呼び出せば良い。これで、面倒だった作業から解放される。
参考サイト
Google ChromeのWindows版は間もなく別のディレクトリにインストールされるようになる
関連ページ