0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

update chromedriver via python script - macOS

Last updated at Posted at 2025-10-09

macOS上でchromedriverを自動更新するスクリプトについて考えてみました。
silicon mac なのでchromedriverはmac-arm64を使います。

スクリプトの制御内容

・実行環境のChromeのメジャーバージョンを取得します。
・ ~/.zshrc(/Users/xxx/.zshrc)をみて、 $PATHにchromedriverの記述があれば、そのパスの既存chromedriverのバージョンとChromeのバージョンを比較します。記述がなければ、/usr/local/bin/chromedriver/に新規インストールします。
・バージョンを比較して一致する場合は更新しません。chromedriverが古い場合は \$PATHに設定されたchromedriverを更新します。

Chrome for Testingから対応するchromedriverのzipをダウンロードします。ダウンロードしたzipと展開ディレクトリは更新後に削除します。
・新規インストールする場合は、~/.zshrcにchromedriverのパスを追記します。
・最後にChromeとchromedriverのメジャーバージョンが一致していること確認します。

新規インストール先を変更する場合はINSTALL_DIRのパスを修正してください。

python code

import subprocess
import requests
import zipfile
import os
import shutil

# ユーザーの .zshrc ファイルの絶対パスを取得
ZSHRC_PATH = os.path.expanduser("~/.zshrc")

# chromedriver のインストール先ディレクトリ
INSTALL_DIR = os.path.expanduser("/usr/local/bin/chromedriver")

# インストール後の chromedriver 実行ファイルのパス
CHROMEDRIVER_BIN = os.path.join(INSTALL_DIR, "chromedriver")

# Chrome のメジャーバージョンを取得
def get_chrome_version():
    try:
        result = subprocess.run(
            ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--version"],
            stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
        )
        version = result.stdout.strip().split()[-1]  # 例: "Google Chrome 117.0.5938.92" → "117.0.5938.92"
        print(f"[Chrome] バージョン: {version}")
        return version.split('.')[0]  # メジャーバージョンのみ返す → "117"
    except (subprocess.SubprocessError, FileNotFoundError) as e:
        print(f"[Chrome] バージョン取得失敗: {e}")
        return None

# 指定された chromedriver のバージョンを取得
def get_chromedriver_version(path):
    try:
        result = subprocess.run([path, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        output = result.stdout.strip()
        error = result.stderr.strip()
        if error:
            print(f"[Chromedriver ERROR] {error}")
        print(f"[Chromedriver] {path}{output}")
        parts = output.split()
        if len(parts) >= 2 and parts[1][0].isdigit():
            return parts[1].split('.')[0]  # メジャーバージョンのみ抽出
    except (subprocess.SubprocessError, FileNotFoundError) as e:
        print(f"[Chromedriver] バージョン取得失敗: {e}")
    return None

# Apple Silicon 用の chromedriver ダウンロード URL を取得
def fetch_arm64_download_url():
    url = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json"
    try:
        response = requests.get(url)
        response.raise_for_status()
        data = response.json()
        for entry in data.get("channels", {}).get("Stable", {}).get("downloads", {}).get("chromedriver", []):
            if entry.get("platform") == "mac-arm64":
                print(f"[取得] ダウンロードURL: {entry['url']}")
                return entry["url"]
    except requests.RequestException as e:
        print(f"[取得失敗] {e}")
    return None

# macOS の Quarantine 属性を削除(Gatekeeper 対策)
def remove_quarantine(path):
    try:
        result = subprocess.run(["xattr", path], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
        if "com.apple.quarantine" in result.stdout:
            subprocess.run(["xattr", "-d", "com.apple.quarantine", path], check=False)
    except subprocess.SubprocessError as e:
        print(f"[Quarantine] xattr 実行失敗: {e}")
    except FileNotFoundError:
        print(f"[Quarantine] ファイルが見つかりません: {path}")

# chromedriver をダウンロード・展開・配置・クリーンアップ
def update_chromedriver(download_url, install_dir):
    zip_path = os.path.join(install_dir, "chromedriver.zip")
    try:
        print(f"[ダウンロード] {download_url}")
        response = requests.get(download_url)
        response.raise_for_status()
        with open(zip_path, "wb") as f:
            f.write(response.content)

        with zipfile.ZipFile(zip_path, "r") as zip_ref:
            zip_ref.extractall(install_dir)

        # 展開された chromedriver 実行ファイルのパスを探索
        extracted = next((os.path.join(install_dir, d, "chromedriver")
                          for d in os.listdir(install_dir)
                          if d.startswith("chromedriver-mac")), None)

        if not extracted or not os.path.exists(extracted):
            print("[エラー] 展開された chromedriver が見つかりません")
            return False

        # 実行ファイルを install_dir に移動し、実行権限を付与
        dst = os.path.join(install_dir, "chromedriver")
        shutil.move(extracted, dst)
        os.chmod(dst, 0o755)
        remove_quarantine(dst)
        print(f"[完了] chromedriver 更新: {dst}")
        return True
    except (requests.RequestException, zipfile.BadZipFile, shutil.Error, OSError) as e:
        print(f"[更新失敗] {e}")
        return False
    finally:
        # ZIP ファイルと展開ディレクトリを削除
        try:
            if os.path.exists(zip_path):
                os.remove(zip_path)
            for d in os.listdir(install_dir):
                if d.startswith("chromedriver-mac"):
                    shutil.rmtree(os.path.join(install_dir, d), ignore_errors=True)
        except (FileNotFoundError, PermissionError) as e:
            print(f"[クリーンアップ失敗] {e}")

# .zshrc から PATH 定義を抽出
def extract_zshrc_paths():
    if not os.path.exists(ZSHRC_PATH):
        return []
    try:
        with open(ZSHRC_PATH, "r") as f:
            lines = f.readlines()

        all_paths = []
        for line in lines:
            line = line.strip()
            if line.startswith("export PATH="):
                rhs = line.split("=", 1)[-1].strip()
                if rhs.startswith('"') and rhs.endswith('"'):
                    rhs = rhs[1:-1]
                elif rhs.startswith("'") and rhs.endswith("'"):
                    rhs = rhs[1:-1]
                rhs = rhs.replace("$PATH", "").replace("$PATH:", "").replace(":$PATH", "")
                for p in rhs.split(":"):
                    p = p.strip()
                    if p:
                        all_paths.append(os.path.expanduser(p))
        return all_paths
    except OSError as e:
        print(f"[zshrc] 読み取り失敗: {e}")
        return []

# .zshrc に指定パスがすでに含まれているか確認
def zshrc_contains_chromedriver_path(path):
    try:
        with open(ZSHRC_PATH, "r") as f:
            content = f.read()
        return path in content
    except OSError as e:
        print(f"[zshrc] チェック失敗: {e}")
        return False

# .zshrc に chromedriver のパスを追記
def append_path_to_zshrc(path):
    if zshrc_contains_chromedriver_path(path):
        return
    try:
        with open(ZSHRC_PATH, "a") as f:
            f.write(f'\nexport PATH="{path}:$PATH"\n')
        print(f"[zshrc] PATH 追記: {path}")
    except OSError as e:
        print(f"[zshrc] PATH 追記失敗: {e}")

# メイン処理:バージョン整合性チェックとインストール・更新
def main():
    chrome_major = get_chrome_version()
    if not chrome_major:
        return

    path_dirs = extract_zshrc_paths()

    # 各 PATH ディレクトリを走査して chromedriver を探す
    for p in path_dirs:
        candidate = os.path.join(p, "chromedriver")
        if os.path.isfile(candidate):
            version = get_chromedriver_version(candidate)

            if version is None:
                print(f"[再インストール] {candidate} に chromedriver があるがバージョン取得できないため再インストールします")
                url = fetch_arm64_download_url()
                if url and update_chromedriver(url, p):
                    version = get_chromedriver_version(candidate)
                    if version == chrome_major:
                        print(f"[バージョン比較] 再インストール後の chromedriver が Chrome と一致しています ({version})")
                    else:
                        print(f"[警告] 再インストール後もバージョン不一致: Chrome={chrome_major}, Driver={version}")
                return

            elif version == chrome_major:
                print(f"[バージョン比較] {candidate} は Chrome と一致しています ({version})")
                return

            else:
                print(f"[不一致] {candidate} → Chrome={chrome_major}, Driver={version}")
                print(f"[更新] {candidate} を更新します")
                url = fetch_arm64_download_url()
                if url and update_chromedriver(url, p):
                    version = get_chromedriver_version(candidate)
                    if version == chrome_major:
                        print(f"[バージョン比較] 更新後の chromedriver が Chrome と一致しています ({version})")
                    else:
                        print(f"[警告] インストール後もバージョン不一致: Chrome={chrome_major}, Driver={version}")
                return

    print("[インストール] chromedriver が PATH に見つからないため新規インストールします")
    os.makedirs(INSTALL_DIR, exist_ok=True)
    url = fetch_arm64_download_url()
    if url and update_chromedriver(url, INSTALL_DIR):
        if not zshrc_contains_chromedriver_path(INSTALL_DIR):
            append_path_to_zshrc(INSTALL_DIR)

        version = get_chromedriver_version(CHROMEDRIVER_BIN)
        if version == chrome_major:
            print(f"[バージョン比較] インストール後の chromedriver が Chrome と一致しています ({version})")
        else:
            print(f"[警告] インストール後もバージョン不一致: Chrome={chrome_major}, Driver={version}")

if __name__ == "__main__":
    main()

実行例 (Pycharmターミナルより)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?