命名って難しい

変数、関数、クラスなどなど実装より命名に毎回悩むタイプの人間による技術についてのメモ。

Python+SeleniumでWebDriverを自動管理してくれるクラスを作ってみたら同じような機能を持つパッケージがあったのでソースコードを供養します。

タイトルで終わり!

ソースコード

メイン

"""Google ChromeのWebDriverを管理するクラス
"""

import os
import platform
import subprocess
from urllib.parse import urlparse
import requests
from urllib.request import urlretrieve
from bs4 import BeautifulSoup as bs
from tempfile import NamedTemporaryFile
from zipfile import ZipFile

class ChromeUpdater():
  """
  ChromeのWebDriver更新クラス

  Returns:
      Chrome: WebDriver更新クラス
  """

  SYS_TO_VER_GETTER_CMD: dict[str, list[str]] = {
    'Windows': [
      'powershell',
      '-command',
      '(Get-Item "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe").VersionInfo.ProductVersion'],
    'Darwin': [
      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
      '--version']
  }

  SYS_TO_WEBDRIVER_NAME: dict[str, str] = {
    'Windows': 'chromedriver.exe',
    'Darwin': 'chromedriver'
  }

  SYS_TO_ZIP_FILENAME: dict[str, str] = {
    'Windows': 'chromedriver_win32.zip',
    'Darwin': 'chromedriver_mac64.zip'
  }

  MAIN_PAGE_URL: str = 'https://chromedriver.chromium.org/downloads'

  def __init__(self, webdrivers_basedir:str=os.getcwd()) -> None:
    """
    初期化処理。実行しているOSを判別し、ファイル名、バージョン取得のコマンドを確定させる。

    Args:
        working_dir (str, optional): WebDriverを管理するディレクトリ. Defaults to os.getcwd().
    """
    self.basedir = os.path.join(webdrivers_basedir, 'webdrivers','chrome')
    pfsys = platform.system()
    self.filename = ChromeUpdater.SYS_TO_WEBDRIVER_NAME[pfsys]
    self.zipfilename = ChromeUpdater.SYS_TO_ZIP_FILENAME[pfsys]
    self.version_get_cmd = ChromeUpdater.SYS_TO_VER_GETTER_CMD[pfsys]
    self.major_version = self._get_version()
    self.webdriver_dir = os.path.join(self.basedir, self.major_version)
    self.webdriver_path = os.path.join(self.webdriver_dir, self.filename)

  def _get_version(self) -> str:
    """
    Chromeバージョンの取得

    Returns:
        str: バージョン番号(メジャー)
    """
    out = subprocess.run(
      self.version_get_cmd,
      capture_output=True,
      text=True,
      check=True).stdout
    return out.strip().split()[-1].split('.')[0]

  def update(self) -> None:
    """
    WebDriverの更新。該当するWebDriverが存在しない場合にのみダウンロードを行う。
    """    
    if os.path.exists(self.webdriver_path):
      return
    self.download()

  def get_webdriver_dl_url(self) -> str:
    """
    WebDriverダウンロードのためのURLの取得

    Returns:
        str: WebDriverのURL
    """    

    # ダウンロードページから表示されている
    res = requests.get(ChromeUpdater.MAIN_PAGE_URL)
    soup: bs = bs(res.text, 'html.parser')

    # WebDriverダウンロードサイトから環境と同じバージョンのWebDriverをダウンロードする。
    sel = f'p>span>a[href*="path={self.major_version}"]'
    urls = [a.attrs['href'] for a in soup.select(sel)]
    dlpage_url = urls[0]

    # 該当バージョンのダウンロードページURLから直接DLするURLを作成する。
    pr = urlparse(dlpage_url)
    return '{}://{}/{}{}'.format(
      pr.scheme,pr.netloc,pr.query.split('=')[1],self.zipfilename
    )

  def download(self):
    """
    WebDriverのダウンロード。
    """
    # 一時ファイルにダウンロードして解凍を行う。
    with NamedTemporaryFile() as ntf:
      urlretrieve(self.get_webdriver_dl_url(), ntf.name)
      ZipFile(ntf.name).extractall(self.webdriver_dir)

テストコード

今回はテストコードにもチャレンジ。pytestしてみました。

"""
ChromeUpdaterのテスト
"""
import pytest # pylint: disable=W0611

from os import getcwd
from os.path import exists,join
from tempfile import TemporaryDirectory
from twidence.webdriver.chrome import ChromeUpdater

def test_init():
  curdir = getcwd()
  crm = ChromeUpdater()
  assert crm.basedir == join(curdir,'webdrivers','chrome')
  assert crm.filename == 'chromedriver'
  assert crm.major_version == '95'
  assert crm.webdriver_dir == join(curdir,'webdrivers','chrome','95')
  assert crm.webdriver_path == join(curdir,'webdrivers','chrome','95','chromedriver')
  assert crm.zipfilename == 'chromedriver_mac64.zip'


def test_init_arg1():
  with TemporaryDirectory(prefix=join(getcwd(),'test_workdir')) as tmpdir:
    crm = ChromeUpdater(tmpdir)
    assert crm.basedir == join(tmpdir,'webdrivers','chrome')
    assert crm.filename == 'chromedriver'
    assert crm.major_version == '95'
    assert crm.webdriver_dir == join(tmpdir,'webdrivers','chrome','95')
    assert crm.webdriver_path == join(tmpdir,'webdrivers','chrome','95','chromedriver')
    assert crm.zipfilename == 'chromedriver_mac64.zip'

def test_get_webdriver_dl_url():
  crm = ChromeUpdater()
  url = crm.get_webdriver_dl_url()
  assert url=='https://chromedriver.storage.googleapis.com/95.0.4638.54/chromedriver_mac64.zip'


def test_update():
  with TemporaryDirectory(prefix=join(getcwd(),'test_workdir_')) as tmpdir:
    crm = ChromeUpdater(tmpdir)
    crm.update()
    assert crm.basedir == join(tmpdir, 'webdrivers', 'chrome')
    assert exists(join(tmpdir, 'webdrivers'))
    assert exists(join(tmpdir, 'webdrivers', 'chrome'))
    assert exists(join(tmpdir, 'webdrivers', 'chrome', '95'))
    assert exists(join(tmpdir, 'webdrivers', 'chrome', '95', 'chromedriver'))

同じような機能を持つパッケージ、webdriver_manager

参考リンク

github.com

qiita.com

以上!