Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for downloading Chromedriver versions 115+ #1478

Merged
merged 6 commits into from
Aug 25, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 74 additions & 24 deletions undetected_chromedriver/patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

from distutils.version import LooseVersion
import io
import json
import logging
import os
import pathlib
import platform
import random
import re
import shutil
Expand All @@ -24,21 +26,9 @@

class Patcher(object):
lock = Lock()
url_repo = "https://chromedriver.storage.googleapis.com"
zip_name = "chromedriver_%s.zip"
exe_name = "chromedriver%s"

platform = sys.platform
if platform.endswith("win32"):
zip_name %= "win32"
exe_name %= ".exe"
if platform.endswith(("linux", "linux2")):
zip_name %= "linux64"
exe_name %= ""
if platform.endswith("darwin"):
zip_name %= "mac64"
exe_name %= ""

if platform.endswith("win32"):
d = "~/appdata/roaming/undetected_chromedriver"
elif "LAMBDA_TASK_ROOT" in os.environ:
Expand Down Expand Up @@ -72,6 +62,10 @@ def __init__(
prefix = "undetected"
self.user_multi_procs = user_multi_procs

self.is_old_chromedriver = version_main and version_main <= 114

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ultrafunkamsterdam Did you get this error?

  File "C:\hostedtoolcache\windows\Python\3.7.9\x64\lib\site-packages\undetected_chromedriver\__init__.py", line 255, in __init__
    user_multi_procs=user_multi_procs,
  File "C:\hostedtoolcache\windows\Python\3.7.9\x64\lib\site-packages\undetected_chromedriver\patcher.py", line 65, in __init__
    self.is_old_chromedriver = version_main and version_main <= 114
TypeError: '<=' not supported between instances of 'str' and 'int'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you passing version_main in as a string? It is supposed to be None or an int

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdholtz Thanks for your quick reply. I didn't notice the type at the beginning.

# Needs to be called before self.exe_name is accessed
self._set_platform_name()

if not os.path.exists(self.data_path):
os.makedirs(self.data_path, exist_ok=True)

Expand All @@ -97,9 +91,33 @@ def __init__(
self._custom_exe_path = True
self.executable_path = executable_path

# Set the correct repository to download the Chromedriver from
if self.is_old_chromedriver:
self.url_repo = "https://chromedriver.storage.googleapis.com"
else:
self.url_repo = "https://googlechromelabs.github.io/chrome-for-testing"

self.version_main = version_main
self.version_full = None

def _set_platform_name(self):
"""
Set the platform and exe name based on the platform undetected_chromedriver is running on
in order to download the correct chromedriver.
"""
if self.platform.endswith("win32"):
self.platform_name = "win32"
self.exe_name %= ".exe"
if self.platform.endswith(("linux", "linux2")):
self.platform_name = "linux64"
self.exe_name %= ""
if self.platform.endswith("darwin"):
if self.is_old_chromedriver:
self.platform_name = "mac64"
else:
self.platform_name = "mac-x64"
self.exe_name %= ""

def auto(self, executable_path=None, force=False, version_main=None, _=None):
"""

Expand Down Expand Up @@ -217,12 +235,32 @@ def fetch_release_number(self):
:return: version string
:rtype: LooseVersion
"""
path = "/latest_release"
if self.version_main:
path += f"_{self.version_main}"
path = path.upper()
# Endpoint for old versions of Chromedriver (114 and below)
if self.is_old_chromedriver:
path = f"/latest_release_{self.version_main}"
path = path.upper()
logger.debug("getting release number from %s" % path)
return LooseVersion(urlopen(self.url_repo + path).read().decode())

# Endpoint for new versions of Chromedriver (115+)
if not self.version_main:
# Fetch the latest version
path = "/last-known-good-versions-with-downloads.json"
logger.debug("getting release number from %s" % path)
with urlopen(self.url_repo + path) as conn:
response = conn.read().decode()

last_versions = json.loads(response)
return LooseVersion(last_versions["channels"]["Stable"]["version"])

# Fetch the latest minor version of the major version provided
path = "/latest-versions-per-milestone-with-downloads.json"
logger.debug("getting release number from %s" % path)
return LooseVersion(urlopen(self.url_repo + path).read().decode())
with urlopen(self.url_repo + path) as conn:
response = conn.read().decode()

major_versions = json.loads(response)
return LooseVersion(major_versions["milestones"][str(self.version_main)]["version"])

def parse_exe_version(self):
with io.open(self.executable_path, "rb") as f:
Expand All @@ -237,17 +275,29 @@ def fetch_package(self):

:return: path to downloaded file
"""
u = "%s/%s/%s" % (self.url_repo, self.version_full.vstring, self.zip_name)
logger.debug("downloading from %s" % u)
# return urlretrieve(u, filename=self.data_path)[0]
return urlretrieve(u)[0]
zip_name = f"chromedriver_{self.platform_name}.zip"
if self.is_old_chromedriver:
download_url = "%s/%s/%s" % (self.url_repo, self.version_full.vstring, zip_name)
else:
zip_name = zip_name.replace("_", "-", 1)
download_url = "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/%s/%s/%s"
download_url %= (self.version_full.vstring, self.platform_name, zip_name)

logger.debug("downloading from %s" % download_url)
return urlretrieve(download_url)[0]

def unzip_package(self, fp):
"""
Does what it says

:return: path to unpacked executable
"""
exe_path = self.exe_name
if not self.is_old_chromedriver:
# The new chromedriver unzips into its own folder
zip_name = f"chromedriver-{self.platform_name}"
exe_path = os.path.join(zip_name, self.exe_name)

logger.debug("unzipping %s" % fp)
try:
os.unlink(self.zip_path)
Expand All @@ -256,10 +306,10 @@ def unzip_package(self, fp):

os.makedirs(self.zip_path, mode=0o755, exist_ok=True)
with zipfile.ZipFile(fp, mode="r") as zf:
zf.extract(self.exe_name, self.zip_path)
os.rename(os.path.join(self.zip_path, self.exe_name), self.executable_path)
zf.extractall(self.zip_path)
os.rename(os.path.join(self.zip_path, exe_path), self.executable_path)
os.remove(fp)
os.rmdir(self.zip_path)
shutil.rmtree(self.zip_path)
os.chmod(self.executable_path, 0o755)
return self.executable_path

Expand Down