SeleniumのChromeDriverを自動でダウンロードする方法(VBA編)

Seleniumを組み込んだツールを使っていると、ブラウザ(Chrome)とWebドライバー(ChromeDriver)のバージョン不一致によりエラーとなることがある。感覚的には2か月に一度程度の頻度で発生する。Chromeが自動更新されるためだ。

これまでは手作業でChromeDriverをダウンロードして所定の場所に配置してきたが、地味に面倒なので、自動化することを思い立った。

Chromeを起動できればオーソドックスなSeleniumの操作法でバージョンを取得できると思われるが、そもそもバージョン不一致でChromeを操作できない。プログラムでChromeを起動する前にバージョンチェックして、ChromeDriverを自動で最新化する必要がある。

以下の流れで処理を行う。

  1. Chromeのバージョンを取得
  2. Chromeのバージョンに一致するChromeDriverをダウンロード
  3. ダウンロードしたZipファイルを解凍
  4. ChromeDriverを所定の場所に格納
  5. メイン処理(1〜4の実行)

Chromeのバージョンを取得

Chromeのインストールフォルダ名がバージョン番号になっているので、そのフォルダ名を取得する。また、Chromeの更新の関係で新旧フォルダが混在することがあるらしいので、最新のバージョン番号に対応するフォルダ名を取得する。

図1: 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) &gt; 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)が目的のバージョンのダウンロードページとなる。

図2:ダウンロードページ。一番下の赤枠は、chromedriver_win32.zipにマウスカーソルをあてて表示されたもの。

③(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版は間もなく別のディレクトリにインストールされるようになる

関連ページ