[Python][Selenium] webdriverの自動更新（webdriver_managerなし）

作ったプログラムの備忘録

# はじめに
* 社内LAN環境においてwebdriver_managerが上手く動かないので、webdriver_managerを使わないでも、webdriverの読込みエラーが出た際にレジストリからバージョン確認して、自動更新するようにした
* 需要はあまりないとは思うがメモとして記載
* 毎日実行しているが、気づいたら更新されているので、個人的には非常に便利に使っている
* またexe化して配布する際の初回起動時にwebdriverを同梱しなくても勝手にダウンロードしてくれるのでとても便利


## 動作テスト環境
OS: Windows 10 Pro 64bit
言語： Python 3.9.13
ライブラリ：Selenium 4.7.2

# ソースコード

[2023.04.11追記]importの例外読み込みと例外処理に`WebDriverException`を追加しました。

```Python:全体コード（Edgeの場合）
import os
import winreg
import shutil
import urllib.request
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.edge.service import Service
from selenium.common.exceptions import SessionNotCreatedException
from selenium.common.exceptions import WebDriverException

driver_path = 'edgedriver_win64/msedgedriver.exe'
edge_service = Service(executable_path=driver_path)

try:
    if not os.path.isfile(f'{os.getcwd()}/{driver_path}'):
        raise FileNotFoundError()
    driver = webdriver.Edge(service=edge_service)
except (FileNotFoundError, SessionNotCreatedException, WebDriverException):
    key = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, r'SOFTWARE\Microsoft\Edge\BLBeacon')
    ver = winreg.QueryValueEx(key, 'version')[0]
    site = r'https://msedgedriver.azureedge.net/'
    folder_name = 'edgedriver_win64'
    if os.path.exists(folder_name): shutil.rmtree(folder_name)
    webdriver_dl = urllib.request.urlopen(f'{site}{ver}/{folder_name}.zip').read()
    with open(f'{folder_name}.zip', mode="wb") as f:
        f.write(webdriver_dl)
    shutil.unpack_archive(f'{folder_name}.zip', folder_name)
    os.remove(f'{folder_name}.zip')
    driver = webdriver.Edge(service=edge_service)
```

## 解説

### 1. Webdriverのエラー検出
```Python:
try:
    if not os.path.isfile(f'{os.getcwd()}/{driver_path}'):
        raise FileNotFoundError()
    driver = webdriver.Edge(service=edge_service)
```
* そもそもwebdriverが存在しない場合を想定して、`FileNotFoundError`を定義
* ここの`driver = webdriver.Edge(service=edge_service)`でwebdriverのバージョンに起因するエラーが発生すると`SessionNotCreatedException`（または`WebDriverException`）の例外が発生するので、以下の`except`部でwebdriverを自動更新する

### 2. 例外の定義
```Python:
except (FileNotFoundError, SessionNotCreatedException, WebDriverException):
```
* webdriverが存在しない場合とバージョンが合わない場合に発生する3つの例外を想定
※`WebDriverException`は記事投稿後に発生したのですが、`SessionNotCreatedException`との発生条件の違いがいまいちわかっていません。ただどちらの例外もWebDriverを削除して新しいものに入れ替えないと動かなかったので、例外に追加しています。

### 3. ブラウザバージョンの確認
```Python:Edgeのバージョン確認方法
    key = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, r'SOFTWARE\Microsoft\Edge\BLBeacon')
    ver = winreg.QueryValueEx(key, 'version')[0]
```
```Python:chromeのバージョン確認方法
    key = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, r"Software\Google\Chrome\BLBeacon")
    ver = winreg.QueryValueEx(key, 'version')[0]
```
* レジストリからブラウザのバージョンを取得するコード<br>
* winregでレジストリの<br>`HKEY_CURRENT_USER\SOFTWARE\Microsoft\Edge\BLBeacon` `HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon`<br>にある`version`部分を読みに行っている
* レジストリはオブジェクトとしてtupleでまとめられているので、`QueryValueEX`以外にも`ver = winreg.EnumValue(key, 0)[1]`などでも取得可能
* winregの詳細：https://docs.python.org/ja/3.7/library/winreg.html

### 4. webdriverのダウンロード元情報の定義
```Python:edgeの場合のURLとファイル名
    site = r'https://msedgedriver.azureedge.net/'
    folder_name = 'edgedriver_win64'
```
```Python:chromeの場合のURLとファイル名
    site = r'https://chromedriver.storage.googleapis.com/'
    folder_name = 'chromedriver_win32'
```
* webdriverのダウンロード元のURLとファイル名を定義

### 5. 既存のwebdriverの削除
```Python:
    if os.path.exists(folder_name): shutil.rmtree(folder_name)
```
* 既存のwebdriverが存在したら削除

### 6. webdriverをダウンロード・保存・解凍・zip削除
```Python:
    webdriver_dl = urllib.request.urlopen(f'{site}{ver}/{folder_name}.zip').read()
    with open(f'{folder_name}.zip', mode="wb") as f:
        f.write(webdriver_dl)
    shutil.unpack_archive(f'{folder_name}.zip', folder_name)
    os.remove(f'{folder_name}.zip')
```
* webdriverをダウンロードして保存、解凍、zipの削除を実行

### 7. 新しいwebdriverの読み込み
```Python:
    driver = webdriver.Edge(service=edge_service)
```
* 最後に自動更新したwebdriverを読み出す

# 関数化・外部モジュール化

最近は関数化したり、外部モジュール化しておいて、driverを読み込むようにしています。

```Python:
def load_webdriver():
    driver_path = 'edgedriver_win64/msedgedriver.exe'
    service = Service(executable_path=driver_path)

    # optionsはなくてもOK
    options = EdgeOptions()
    options.add_experimental_option('excludeSwitches', ['enable-logging'])
    options.add_argument('--window-size=1000,1000')

    try:
        if not os.path.isfile(f'{os.getcwd()}/{driver_path}'):
            raise FileNotFoundError()
        driver = webdriver.Edge(service=service, options=options)
    except (FileNotFoundError, SessionNotCreatedException, WebDriverException):
        key = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, r'SOFTWARE\Microsoft\Edge\BLBeacon')
        ver = winreg.EnumValue(key, 0)[1]
        site = r'https://msedgedriver.azureedge.net/'
        folder_name = 'edgedriver_win64'
        if os.path.exists(folder_name):
            shutil.rmtree(folder_name)
        webdriver_dl = urllib.request.urlopen(f'{site}{ver}/{folder_name}.zip').read()
        with open(f'{folder_name}.zip', mode="wb") as f:
            f.write(webdriver_dl)
        shutil.unpack_archive(f'{folder_name}.zip', folder_name)
        os.remove(f'{folder_name}.zip')
        driver = webdriver.Edge(service=service, options=options)

    return driver

def main():
    driver = load_webdriver()

```