📖

[m1Mac]Seleniumのドライバーエラー解決策ValueError: There is no such driver by url

2022/10/19に公開

seleniumを使用していて出会ったエラーの対処法です。

【環境】

  • MacBook Air (M1, 2020)
  • python 3.9.9
  • selenium == 4.5.0
  • chromedriver-binary == 105.0.5195.52
  • webdriver_manager == 3.8.3

エラー内容

ValueError: There is no such driver by url https://chromedriver.storage.googleapis.com/106.0.5249.61/chromedriver_mac64_m1.zip

こんなドライバーないよーとエラーが出てますね。コードは下記のとおりです。

sample.py
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

options = Options()
options.add_argument('--headless')

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
# 106.0.5249.61/chromedriver_mac64_m1.zip が自動で入力

driver.get("https://www.selenium.dev/selenium/web/web-form.html")
title = driver.title

driver.implicitly_wait(0.5)

text_box = driver.find_element(by=By.NAME, value="my-text")
submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button")

print(title, text_box)

driver.quit()

エラー内容の全文を見てみましょう。

Traceback (most recent call last):
  File "/Users/myname/my_dir/selenium_test.py", line 15, in <module>
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
  File "/Users/myname/.pyenv/versions/3.9.9/lib/python3.9/site-packages/webdriver_manager/chrome.py", line 39, in install
    driver_path = self._get_driver_path(self.driver)
  File "/Users/my_name/.pyenv/versions/3.9.9/lib/python3.9/site-packages/webdriver_manager/core/manager.py", line 30, in _get_driver_path
    file = self._download_manager.download_file(driver.get_url())
  File "/Users/myname/.pyenv/versions/3.9.9/lib/python3.9/site-packages/webdriver_manager/core/download_manager.py", line 28, in download_file
    response = self._http_client.get(url)
  File "/Users/myname/.pyenv/versions/3.9.9/lib/python3.9/site-packages/webdriver_manager/core/http.py", line 33, in get
    self.validate_response(resp)
  File "/Users/myname/.pyenv/versions/3.9.9/lib/python3.9/site-packages/webdriver_manager/core/http.py", line 16, in validate_response
    raise ValueError(f"There is no such driver by url {resp.url}")
ValueError: There is no such driver by url https://chromedriver.storage.googleapis.com/106.0.5249.61/chromedriver_mac64_m1.zip

どうやら、ChromeDriverManagerのcore/http.pyというファイルでエラーが出ていそうです。

原因

まずはネットで同様のエラーがている人がいないか探してみます。

どうやらchromedriverのURLが
"https://chromedriver.storage.googleapis.com/106.0.5249.61/chromedriver_mac64_m1.zip"
から
"https://chromedriver.storage.googleapis.com/106.0.5249.61/chromedriver_mac_arm64.zip"
に変更されたみたいですね。

ちなみに105.0.5195.52の場合には"https://chromedriver.storage.googleapis.com/105.0.5195.52/chromedriver_mac64_m1.zip"で良いそうです。こちらのリンクから確認することができます。

その他のバージョンに関してはこちらから確認できます。

https://groups.google.com/g/chromedriver-users/c/JRuQzH3qr2c
https://github.com/SergeyPirogov/webdriver_manager/issues/443

対処法

対処法1. パッケージの修正で強引に修正

まずは強引に治す方法です。

pipでwebdriver-managerをインストールした後、site-packages/webdriver_manager/core/http.pyを下記のように直します。

core/http.py
import requests
from requests import Response
​
from webdriver_manager.core.config import ssl_verify
from webdriver_manager.core.utils import show_download_progress
​
​
class HttpClient:
    def get(self, url, params=None, **kwargs) -> Response:
        raise NotImplementedError
​
    @staticmethod
    def validate_response(resp: requests.Response):
        status_code = resp.status_code
        if status_code == 404:
            raise ValueError(f"There is no such driver by url {resp.url}")
        elif status_code == 401:
            raise ValueError(f"API Rate limit exceeded. You have to add GH_TOKEN!!!")
        elif resp.status_code != 200:
            raise ValueError(
                f"response body:\n{resp.text}\n"
                f"request url:\n{resp.request.url}\n"
                f"response headers:\n{dict(resp.headers)}\n"
            )
​
​
class WDMHttpClient(HttpClient):
    def __init__(self):
        self._ssl_verify = ssl_verify()def get(self, url, **kwargs) -> Response:
        ##### URLが変更されたらしいので対応 ######
        if 'chromedriver_mac64_m1.zip' in url:
            url = url.replace('chromedriver_mac64_m1.zip', 'chromedriver_mac_arm64.zip')
        ##### URLが変更されたらしいので対応 ######
        resp = requests.get(url=url, verify=self._ssl_verify, stream=True, **kwargs)
        self.validate_response(resp)
        show_download_progress(resp)
        return resp

ですがこれではDocker化するときなどの管理が面倒になります。なので、ドライバーの中身を変更する以外の対処法を考えてみます。

対処法2. 予めchromedriverをインストールしておく方法

pipを用いない場合では、下記のリンクからm1用のzipファイルをインストールすることでエラーなく実行することが可能です。
https://chromedriver.storage.googleapis.com/index.html?path=106.0.5249.61/
※それぞれの環境に合わせたZipファイルをこちらのリンクよりお探しください。

その場合下記のようにプログラムを修正します。

sample.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

options = Options()
options.add_argument('--headless')

# ダウンロード先のpath情報を指定
executable_path = '/Users/myname/Downloads/chromedriver'

driver = webdriver.Chrome(executable_path, options=options)

driver.get("https://www.selenium.dev/selenium/web/web-form.html")
title = driver.title

driver.implicitly_wait(0.5)

text_box = driver.find_element(by=By.NAME, value="my-text")
submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button")

print(title, text_box)

driver.quit()

この方法であれば他の人も同様に実行できる様はなりますが、それぞれの環境に合わせてpathを指定しなくてはならないので、誰がどんな環境で実行してもうまくいくように修正する必要があります。

対処法2-2. Chromedriverをインストールしpathはコマンドで通す方法

私は再現性の観点から、今回はこの対処法で対応しました。

まずはpipで「chromedriver-binary」をインストールします。その後、実行するディレクトリにて

export PATH=$PATH:`chromedriver-path`

を実行することでpathをコードに記載することなく実行できます。

コードは下記のようになります。

sample.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)

driver.get("https://www.selenium.dev/selenium/web/web-form.html")
title = driver.title

driver.implicitly_wait(0.5)

text_box = driver.find_element(by=By.NAME, value="my-text")
submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button")

print(title, text_box)

driver.quit()

この方法であればDocker化も問題なくできます。

Discussion