バージョンに合ったChromeDriverをダウンロードする処理のメモ

# 主な話
ChromeとChromeDriverのバージョンが合わなくて困り、毎回手動でバージョン合わせるのが面倒になったので自動化したときのメモ。
VBScript→Pythonの順で実装したがPython版も同じ方法を使っているので両方Windows限定。
zipの展開処理はVBScriptだと非推奨の方法かPowerShellが必要になって面倒なので記事内では省略。

## 環境情報
- Windows10 バージョン 1903
- Python 3.9.0

## 基本的な処理の流れ
VBScriptもPythonもやってることは同じで以下の手順でダウンロードを行っている。
1. レジストリを読んでインストールされているChromeのバージョンを取得
2. 「chromedriver.storage.googleapis.com/LATEST_RELEASE_x」にアクセスして最新のドライバのバージョンを取得
3. 「chromedriver.storage.googleapis.com/x.x.x.x/chromedriver_win32.zip」からダウンロード、ファイルとして保存

## VBScriptのコード
- 変数の宣言は省略。変数の宣言を強制している場合は適宜追加。
- ファイルの保存パス部分を変更すればVBAでも動く。

```VisualBasic:保存パスを作成している部分
With CreateObject("Scripting.FileSystemObject")
'    SaveRoot = .GetFile(WScript.ScriptFullName).ParentFolder.Path
    SaveRoot = ThisWorkbook.Path
    SavePath = .BuildPath(SaveRoot, "chromedriver_win32.zip")
End With
```

```VisualBasic:DriverDownload.vbs
Function GetChromeVersion()
    Set WshShell = CreateObject("WScript.Shell")
    GetChromeVersion = WshShell.RegRead("HKCU\Software\Google\Chrome\BLBeacon\version")
End Function

Function GetDriverVersion(ChromeVersion)
    Const LATEST_RELESE = "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_"
    MajorVersion = Split(ChromeVersion, ".")(0)
    VersionUrl = LATEST_RELESE & MajorVersion
    With CreateObject("MSXML2.XMLHTTP")
        .Open "GET", VersionUrl
        .Send
        Do
        Loop Until .readyState = 4
        GetDriverVersion = .responseText
    End With
End Function
Function DownloadDriver(DriverVersion)
    DriverUrl = "https://chromedriver.storage.googleapis.com/" & DriverVersion & "/chromedriver_win32.zip"
    With CreateObject("MSXML2.XMLHTTP")
        .Open "GET", DriverUrl
        .Send
        Do
        Loop Until .readyState = 4
        Driver = .responseBody
    End With
    With CreateObject("Scripting.FileSystemObject")
        SaveRoot = .GetFile(WScript.ScriptFullName).ParentFolder.Path
        SavePath = .BuildPath(SaveRoot, "chromedriver_win32.zip")
    End With
    Const adTypeBinary = 1
    Const adSaveCreateOverWrite = 2
    With CreateObject("ADODB.Stream")
        .Type = adTypeBinary
        .Open
        .Write Driver
        .SaveToFile SavePath, adSaveCreateOverWrite
        .Close
    End With
    DownloadDriver = SavePath
End Function

ChromeVersion = GetChromeVersion()
DriverVersion = GetDriverVersion(ChromeVersion)
WScript.Echo DownloadDriver(DriverVersion)
```
## Pythonのコード
- ファイルの保存先は実行したときの作業フォルダになる。

```Python:driver_download.py
import urllib.request
import winreg
from pathlib import Path

def get_chrome_version() -> str:
    with winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER,
                           r"Software\Google\Chrome\BLBeacon") as key:
        value = winreg.QueryValueEx(key, "version")
    return value[0]

def get_driver_version(chrome_version: str) -> str:
    LATEST_RELESE = "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_"
    major_version = chrome_version.split(".")[0]
    version_url = LATEST_RELESE + major_version
    with urllib.request.urlopen(version_url) as res:
        return res.read().decode("utf-8")

def download_driver(driver_version: str) -> Path:
    driver_url = f"https://chromedriver.storage.googleapis.com/{driver_version}/chromedriver_win32.zip"
    with urllib.request.urlopen(driver_url) as res:
        driver = res.read()
    driver_file = Path("chromedriver_win32.zip").absolute()
    with driver_file.open(mode="bw") as f:
        f.write(driver)
    return driver_file

chrome_version = get_chrome_version()
driver_version = get_driver_version(chrome_version)
print(download_driver(driver_version))
```
## おまけ：ローカルのドライババージョンを確認する方法
今回はローカルのバージョンは確認せずにDL処理をやらせているが既に最新版ならDLさせないという分岐をしたい場合は

```bat:chromedriver.exeを展開したフォルダでコマンドプロンプト
> chromedriver -version
rem ChromeDriver 87.0.4280.88 (ゴチャッとした文字列)
```
でローカルのドライババージョン情報を表示できるのでVBならWshShell.ExecやPythonならsubprocess.runなどで出力を拾ってくれば確認に使える。
ただしバージョン以外の文字列が含まれているのでSplitや正規表現などで必要な部分だけを取り出さないといけないっぽい。

## その他
- 内容自体はn番煎じ感があるが実装にあたってレジストリの読み込み、httpでのデータ取得、バイナリデータからファイルを作る方法などを両方の言語で確認できたのでヨシ！
- Pythonでの更新に使う場合はドライババージョンを取得したらダウンロード処理はpipenvに任せる手もあり？
- その場合はpipenvの作業フォルダを意識する必要がありそう。
- VSCodeのデバッグの「Python: CurrentFile」を使って走らせる分には特に指定がなくてもいい感じに動いてくれた。
- pipenvは環境変数に「PIPENV_VENV_IN_PROJECT」を入れて作ったフォルダをワークスペースとして利用している。

```Python
subprocess.run(["pipenv", "install", f"chromedriver-binary=={driver_version}"])
```
## 修正
winreg.OpenKeyExがwith文に対応していたので修正