新・VBAでChromeDriverの自動更新する

以前のプログラムはコチラ

ちゃんと動くし悪くはないんですけどね、ソースが長いのとローカルにiniファイルを作る部分が都合悪い場合があるみたいなので発想を変えてシンプルな感じに作り直してみました

ソース

説明不要でソースだけあればいいって方のためにまずソース

Sub Sample()
Dim driver As Selenium.ChromeDriver
Call ChromeDriverCheck

Set driver = New Selenium.ChromeDriver
driver.start
driver.get "https://javeo.jp/"
'------------------------------
'メイン処理
'------------------------------
driver.Close
driver.Quit
set driver = Nothing
End Sub
Option Explicit
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

Private driver As New Selenium.ChromeDriver
Private errMsgArr As Variant
Private i As Long
Private VerDiffErr As Boolean

Private strUrl As String
Private ChromeVersion As String
Private ChromeDriverVersion As String
Private ChromeDriverPath As String
 
Private objFso As Object ' Scripting.FileSystemObject '--- 「Microsoft Scripting Runtime」の参照設定
Private objReg As Object ' VBScript_RegExp_55.RegExp '--- 「Microsoft VBScript Regular Expressions 5.5」の参照設定
Private objMat As Object 'MatchCollection '--- 「Microsoft VBScript Regular Expressions 5.5」の参照設定
Private xmlhttp As Object ' MSXML2.XMLHTTP60 '--- 「Microsoft XML v6.0」の参照設定
Private htmlDoc As Object 'HTMLDocument '--- 「Microsoft HTML Object Library」の参照設定
 
Private Const CheckFolder As String = "drivercheck"
Private Const zipFile As String = "driver.zip"
Public Function ChromeDriverCheck() As Boolean
'====================================================================================================
'ChromeDriverを自動更新する
'一度強制的にSeleniumをスタートさせてエラーになったらエラーメッセージを分解してバージョン情報を収集する
'====================================================================================================
'---SeleniumBasicのフォルダを探す
With CreateObject("Scripting.FileSystemObject")
    Select Case True
        Case .FolderExists(Environ("ProgramFiles(x86)") & "\SeleniumBasic")
            ChromeDriverPath = Environ("ProgramFiles(x86)") & "\SeleniumBasic"
        Case .FolderExists(Environ("ProgramFiles") & "\SeleniumBasic")
            ChromeDriverPath = Environ("ProgramFiles") & "\SeleniumBasic"
        Case .FolderExists(Environ("ProgramW6432") & "\SeleniumBasic")
            ChromeDriverPath = Environ("ProgramW6432") & "\SeleniumBasic"
        Case .FolderExists(Environ("LOCALAPPDATA") & "\SeleniumBasic")
            ChromeDriverPath = Environ("LOCALAPPDATA") & "\SeleniumBasic"
        Case Else
            MsgBox "'SeleniumBasic'のフォルダが見つかりません", vbCritical
            ChromeDriverCheck = False
            Exit Function
    End Select
End With

On Error Resume Next
'---ヘッドレス状態で実行して起動成功するか試してみる
With driver
    .AddArgument ("--headless")
    .Start
End With
'---エラーになったらドラーバーを入れ替える
Select Case Err.Number
    Case 0 '---エラーにならなければSeleniumを落として終わり
        With driver
            .Close
            .Quit
        End With
    Case 33 '---SessionNotCreatedErrorを検知した時
        VerDiffErr = False
        '---バージョン情報を抽出するために正規表現を使う
        Set objReg = CreateObject("VBScript.RegExp")
        objReg.Pattern = "\d+\.\d+\.\d+" '---数字だけのマッチだからパターンだけで十分
        '---エラーメッセージを分解してバージョン情報を取得する
        errMsgArr = Split(Err.Description, vbLf)
        For i = LBound(errMsgArr) To UBound(errMsgArr)
            Select Case True
                Case Trim(errMsgArr(i)) = "SessionNotCreatedError" '---バージョン違いエラーであることを確認
                    VerDiffErr = True
                Case Trim(errMsgArr(i)) Like "Current browser version is *" '---Chromeのバージョンとパスが表示されている場合
                    Set objMat = objReg.Execute(errMsgArr(i))
                    ChromeVersion = objMat.Item(0)
                Case Trim(errMsgArr(i)) Like "(Driver info:*" '---ChromeDriverのバージョンが表示されている場合
                    Set objMat = objReg.Execute(errMsgArr(i))
                    ChromeDriverVersion = objMat.Item(0)
            End Select
        Next
        Set objReg = Nothing
        '---ErrNumberは33なのにメッセージが違った場合
        If VerDiffErr = False Then
            MsgBox Err.Description, vbCritical
            ChromeDriverCheck = False
            Exit Function
        End If
        '---ChromeDriverを入れ替える前に一度落とす
        With driver
            .Close
            .Quit
        End With
        '---入れ替えが失敗することがあるから念のため分岐しておく
        If ChromeDriverUpdate(ChromeVersion) = False Then
            MsgBox "ChromeDriverの更新に失敗しました" & vbCrLf & "Excelを全て終了して再実行してください", vbCritical
            ChromeDriverCheck = False
            Exit Function
        End If
        '---念のためテスト運転
        On Error GoTo ErrTest
        With driver
            .AddArgument ("--headless")
            .Start
            .Get ("https://javeo.jp")
            .Close
            .Quit
        End With
        On Error GoTo 0
        
    Case Else '---その他エラーはメッセージをそのまま出す
        MsgBox Err.Description, vbCritical
        ChromeDriverCheck = False
        Exit Function
End Select
On Error GoTo 0

ChromeDriverCheck = True
Exit Function
ErrTest:
    MsgBox "ChromeDriverの起動に失敗しました", vbCritical
    ChromeDriverCheck = False
End Function
Private Function ChromeDriverUpdate(ByVal TargetChromeDriverVersion As String) As String
'====================================================================================================
'https://chromedriver.chromium.org/ に用意されているAPIを使ってChrome.exeのバージョンに合わせたChromeDriverをダウンロードして保存する
'====================================================================================================
ChromeDriverUpdate = False
Set xmlhttp = CreateObject("MSXML2.XMLHTTP")
Set htmlDoc = CreateObject("HTMLFile")
'Set htmlDoc = New HTMLDocument
Dim strReleaseVersion As String
With xmlhttp
    .Open "GET", "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_" & TargetChromeDriverVersion '---Chromeドライバーが公開されているサイトからソースを取得
    .send
    Do While .readyState < 4
        DoEvents
    Loop
    strReleaseVersion = .responseText
    strUrl = "https://chromedriver.storage.googleapis.com/" & strReleaseVersion & "/chromedriver_win32.zip"
    URLDownloadToFile 0, strUrl, ChromeDriverPath & "\" & CheckFolder & "\" & zipFile, 0, 0 '---ファイルをダウンロードするWinAPI
    
    On Error Resume Next
    CreateObject("Scripting.FileSystemObject").DeleteFile ChromeDriverPath & "\" & "chromedriver.exe", True '---今のドライバーを削除する
    If Err.Number <> 0 Then
        ChromeDriverUpdate = False
        Exit Function
    End If
    On Error GoTo 0
    With CreateObject("Shell.Application") '---zipを既定のフォルダに向けて解凍する
        .Namespace((ChromeDriverPath)).CopyHere .Namespace((ChromeDriverPath & "\" & CheckFolder & "\" & zipFile)).Items
    End With
End With

ChromeDriverUpdate = True
Set htmlDoc = Nothing
Set xmlhttp = Nothing
End Function

ソースの説明

プログラムの方針

理想はChrome.exeのバージョンとChromeDriverのバージョンを取得して比較するだけの簡単なお仕事なんですが、ChromeDriverの取得方法が無い(多分)ので手っ取り早く一度Seleniumを実行してエラーになったらメッセージから取ればいいって結論に至りました

まずはバージョン不一致になった時のエラーがコチラ

SessionNotCreatedError
session not created: This version of ChromeDriver only supports Chrome version <ChromeDriverのメジャーバージョン>
Current browser version is <chrome.exeのバージョン> with binary path <chrome.exeのフルパス>
  (Driver info: chromedriver=<ChromeDriverのバージョン> (273bf7ac8c909cde36982d27f66f3c70846a3718-refs/branch-heads/4758@{#1151}),platform=Windows NT 10.0.19044 x86_64)

3行目にchrome.exeのバージョンがあるのでここを取ればいいわけです

SeleniumBasicのフォルダを探す

With CreateObject("Scripting.FileSystemObject")
    Select Case True
        Case .FolderExists(Environ("ProgramFiles(x86)") & "\SeleniumBasic")
            ChromeDriverPath = Environ("ProgramFiles(x86)") & "\SeleniumBasic"
        Case .FolderExists(Environ("ProgramFiles") & "\SeleniumBasic")
            ChromeDriverPath = Environ("ProgramFiles") & "\SeleniumBasic"
        Case .FolderExists(Environ("ProgramW6432") & "\SeleniumBasic")
            ChromeDriverPath = Environ("ProgramW6432") & "\SeleniumBasic"
        Case .FolderExists(Environ("LOCALAPPDATA") & "\SeleniumBasic")
            ChromeDriverPath = Environ("LOCALAPPDATA") & "\SeleniumBasic"
        Case Else
            MsgBox "'SeleniumBasic'のフォルダが見つかりません", vbCritical
            ChromeDriverCheck = False
            Exit Function
    End Select
End With

SeleniumBasicがインストールされているかの確認を含めてSeleniumBasicフォルダを探します

インストールを管理者権限で行うかどうか、管理者権限の場合はOSが32bitか64bitかでフォルダが変わるので総当たりでチェックする

エラー無視を入れてChromeDriverを起動してみる

On Error Resume Next
'---ヘッドレス状態で実行して起動成功するか試してみる
With driver
    .AddArgument ("--headless")
    .Start
End With
'---エラーになったらドラーバーを入れ替える
Select Case Err.Number
    Case 0 '---エラーにならなければSeleniumを落として終わり
        With driver
            .Close
            .Quit
        End With
    Case 33 '---SessionNotCreatedErrorを検知した時
        '------------------------------
        '中略
        '------------------------------
    Case Else '---その他エラーはメッセージをそのまま出す
        MsgBox Err.Description, vbCritical
        ChromeDriverCheck = False
        Exit Function
End Select
On Error GoTo 0

エラー発生する前提なのでOn Error Resume Nextを入れてエラー無視しながらヘッドレスモードで起動して様子を見る

エラーになるならErr.Numberは33しか有り得ないと思いつつ念のためCase Elseも入れてカバーしておく

エラーメッセージからchrome.exeのバージョンを取得する

VerDiffErr = False
'---バージョン情報を抽出するために正規表現を使う
Set objReg = CreateObject("VBScript.RegExp")
objReg.Pattern = "\d+\.\d+\.\d+" '---数字だけのマッチだからパターンだけで十分
'---エラーメッセージを分解してバージョン情報を取得する
errMsgArr = Split(Err.Description, vbLf)
For i = LBound(errMsgArr) To UBound(errMsgArr)
    Select Case True
        Case Trim(errMsgArr(i)) = "SessionNotCreatedError" '---バージョン違いエラーであることを確認
            VerDiffErr = True
        Case Trim(errMsgArr(i)) Like "Current browser version is *" '---Chromeのバージョンとパスが表示されている場合
            Set objMat = objReg.Execute(errMsgArr(i))
            ChromeVersion = objMat.Item(0)
        Case Trim(errMsgArr(i)) Like "(Driver info:*" '---ChromeDriverのバージョンが表示されている場合
            Set objMat = objReg.Execute(errMsgArr(i))
            ChromeDriverVersion = objMat.Item(0)
    End Select
Next
Set objReg = Nothing
'---ErrNumberは33なのにメッセージが違った場合
If VerDiffErr = False Then
    MsgBox Err.Description, vbCritical
    ChromeDriverCheck = False
    Exit Function
End If

まずVerDiffErr絡みは念のため書いてるけど正直不要だと思う

次に正規表現でchrome.exeのバージョン情報から「メジャー・マイナー・ビルド」部分を抜き取る(なんでリビジョンは不要と言うと後でわかる)

一応ChromeDriverのバージョンも抜き取ってはいるけど前述の通り使い道は無い、、、

ChromeDriverを置き換える

'---ChromeDriverを入れ替える前に一度落とす
With driver
    .Close
    .Quit
End With
'---入れ替えが失敗することがあるから念のため分岐しておく
If ChromeDriverUpdate(ChromeVersion) = False Then
    MsgBox "ChromeDriverの更新に失敗しました" & vbCrLf & "Excelを全て終了して再実行してください", vbCritical
    ChromeDriverCheck = False
    Exit Function
End If
'---念のためテスト運転
On Error GoTo ErrTest
With driver
    .AddArgument ("--headless")
    .Start
    .Get ("https://javeo.jp")
    .Close
    .Quit
End With
On Error GoTo 0

最初にテスト起動していたChromeDriverを落としておかないと入れ替え失敗するのでこのタイミングで

ちなみにテスト起動直後に落とすとErr.Numberがリセットされてしまうのでココがベスト(多分)

ChromeDriverUpdateは次で説明しているのでそちら参照

ChromeDriverを実際に入れ替える※ChromeDriverUpdate部分

Private Function ChromeDriverUpdate(ByVal TargetChromeDriverVersion As String) As String
'====================================================================================================
'https://chromedriver.chromium.org/ に用意されているAPIを使ってChrome.exeのバージョンに合わせたChromeDriverをダウンロードして保存する
'====================================================================================================
ChromeDriverUpdate = False
Set xmlhttp = CreateObject("MSXML2.XMLHTTP")
Set htmlDoc = CreateObject("HTMLFile")
'Set htmlDoc = New HTMLDocument
Dim strReleaseVersion As String
With xmlhttp
    .Open "GET", "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_" & TargetChromeDriverVersion '---Chromeドライバーが公開されているサイトからソースを取得
    .send
    Do While .readyState < 4
        DoEvents
    Loop
    strReleaseVersion = .responseText
    strUrl = "https://chromedriver.storage.googleapis.com/" & strReleaseVersion & "/chromedriver_win32.zip"
    URLDownloadToFile 0, strUrl, ChromeDriverPath & "\" & CheckFolder & "\" & zipFile, 0, 0 '---ファイルをダウンロードするWinAPI
    
    On Error Resume Next
    CreateObject("Scripting.FileSystemObject").DeleteFile ChromeDriverPath & "\" & "chromedriver.exe", True '---今のドライバーを削除する
    If Err.Number <> 0 Then
        ChromeDriverUpdate = False
        Exit Function
    End If
    On Error GoTo 0
    With CreateObject("Shell.Application") '---zipを既定のフォルダに向けて解凍する
        .Namespace((ChromeDriverPath)).CopyHere .Namespace((ChromeDriverPath & "\" & CheckFolder & "\" & zipFile)).Items
    End With
End With

ChromeDriverUpdate = True
Set htmlDoc = Nothing
Set xmlhttp = Nothing
End Function

実際に途中で出現するURLを叩いてもらえれば一目瞭然

ChromeDriverを管理している https://chromedriver.chromium.org/ で準備されているAPIで公開されている指定したChromeDriverの最新バージョンが取得できる

このAPIで渡すLATEST_RELEASE_の後ろに続く値は「メジャー」だけか「メジャー・マイナー・ビルド」のどちらかなので事前取得でリビジョンは抜いていたんですね

で、このバージョン情報があればのURLでChromeDriverをダウンロードできる

余談

ここで思い返してほしいのがChrome.exeのバージョンはリビジョンまで取れていたのにわざわざビルドまでにしてAPIで最新のバージョンを取得したのか・・・

ここが少しややこしくなる話でChromeDriverは全てのChrome.exeのバージョンが準備されているわけではないってことと、リビジョンが違うだけならエラーにならないので”なるべく新しい”レベルで更新すれば動くのでこの手順でやっています

あとがき

この時点で前回よりソースが短くなっていてもう少し削れる部分があるので結構スッキリできたかなと

エラー発生ありきなのが気に入らない点ではあるものの個人的には今回の方がオススメですね

コメント

タイトルとURLをコピーしました