Skip to content

Instantly share code, notes, and snippets.

@jslay88
Last active April 30, 2019 18:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jslay88/67c0578d95bc02e739eeba6299474a60 to your computer and use it in GitHub Desktop.
Save jslay88/67c0578d95bc02e739eeba6299474a60 to your computer and use it in GitHub Desktop.
Download Latest chromedriver. Win/Linux/Mac. 32/64bit
import os
import re
import zipfile
import logging
import requests
import xml.etree.ElementTree as ET
from io import BytesIO
logger = logging.getLogger(__name__)
def discover_system():
import sys
return {
'linux': 'linux',
'linux2': 'linux',
'darwin': 'mac',
'win32': 'win',
'cygwin': 'win'
}[sys.platform.lower()]
def discover_arch():
import platform
return '64' if platform.machine().endswith('64') else '32'
def discover_chrome_version():
import subprocess
system = discover_system()
version = None
if system == 'win':
cmd_args = [
'wmic',
'datafile', 'where',
'name="C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe"',
'get', 'Version', '/value'
]
process = subprocess.run(cmd_args, capture_output=True)
pattern = re.compile(r'Version=(\d+\.\d+\.\d+)')
version_match = pattern.search(process.stdout.decode('utf-8'))
if version_match:
version = version_match.group(1)
elif args.system == 'linux':
pass
elif args.system == 'mac':
pass
if version is not None:
version = tuple((int(version_number) for version_number in version.split('.')[:3]))
return version
def discover_latest_chromedriver(system, arch='64'):
if system == 'win':
arch = '32'
download_filename = 'chromedriver_{system}{arch}.zip'.format(system=system, arch=arch)
logger.debug('Download Filename: {filename}'.format(filename=download_filename))
url = 'https://chromedriver.storage.googleapis.com/'
resp = requests.get(url)
root = ET.fromstring(resp.content)
namespace = {'default': re.match(r'^{([^}]*)}.*$', root.tag)[1]}
latest = None
version_pattern = re.compile(r'^(\d+)\.?(\d*)\.?(\d*)\.?(\d*)/' + download_filename + '$',
re.IGNORECASE)
for content in root.findall('default:Contents', namespace):
matches = version_pattern.match(content.find('default:Key', namespace).text)
if matches:
matches = (int(matches.group(1)) if matches.group(1) else 0,
int(matches.group(2)) if matches.group(2) else 0,
int(matches.group(3)) if matches.group(3) else 0,
int(matches.group(4)) if matches.group(4) else 0)
logger.debug('Discovered v{version}'.format(version='.'.join(str(match) for match in matches)))
if latest is None:
latest = (content, matches)
else:
if matches[0] < latest[1][0]:
continue
if matches[0] > latest[1][0]:
latest = (content, matches)
elif matches[1] < latest[1][1]:
continue
elif matches[1] > latest[1][1]:
latest = (content, matches)
elif matches[2] < latest[1][2]:
continue
elif matches[2] > latest[1][2]:
latest = (content, matches)
elif matches[3] < latest[1][3]:
continue
elif matches[3] > latest[1][3]:
latest = (content, matches)
return url + latest[0].find('default:Key', namespace).text, latest[1]
def download_chromedriver(system, arch='64', latest_version_version=None, extract_name=None, extract_path=None):
"""
Download the latest chromedriver binary for the specified system
:param system: win, linux, or mac
:type system: str
:param arch: 32 or 64
:type arch: str
:param latest_version_version: Grab a particular latest version (74,0,3729)
(Use first 3 version numbers of a Chrome build v70+)
:type latest_version_version: tuple of int
:param extract_name: Override binary name on extraction
:type extract_name: str
:param extract_path: Override extraction path. Default is current working directory.
:type extract_path: str
:return: Extracted binary path
:rtype: str
"""
if system == 'win':
arch = '32'
if extract_path is None:
extract_path = os.path.realpath(os.getcwd())
logger.info('Updating chromedriver...')
if latest_version_version is None:
download_url, download_version = discover_latest_chromedriver(system, arch)
else:
url = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_'
resp = requests.get(url + '.'.join(str(v for v in latest_version_version[:3])))
if resp.status_code == 404:
resp = requests.get(url + str(latest_version_version[0]))
if resp.status_code == 404:
raise FileNotFoundError('Unable to find a suitable chromedriver for v{version}!'.format(
version='.'.join(str(v for v in latest_version_version))))
download_url = 'https://chromedriver.storage.googleapis.com/{version}/chromedriver_{system}{arch}.zip'.format(
version=resp.content.decode('utf-8'),
system=system,
arch=arch
)
download_version = tuple(int(v) for v in resp.content.decode('utf-8').split('.'))
logger.info('Downloading chromedriver v{version}...'.format(version=".".join((str(x) for x in download_version))))
resp = requests.get(download_url)
data = BytesIO(resp.content)
logger.info('Download Complete!')
logger.info('Extracting Archive...')
with zipfile.ZipFile(data) as chromedriver_zip:
file_info = chromedriver_zip.infolist()[0]
logger.info('Found {filename} in archive.'.format(filename=file_info.filename))
data = chromedriver_zip.read(file_info.filename)
if extract_name is None:
extract_path = os.path.join(extract_path, file_info.filename)
else:
extract_path = os.path.join(extract_path, extract_name)
logger.info('Extracting {filename} to {extract_path}'.format(filename=file_info.filename,
extract_path=extract_path))
with open(extract_path, 'wb') as extracted_file:
extracted_file.write(data)
logger.info('chromedriver v{version} downloaded and extracted successfully!'
.format(version=".".join((str(x) for x in download_version))))
return extract_path
if __name__ == '__main__':
import sys
import argparse
arg_parser = argparse.ArgumentParser('Chromedriver Downloader')
arg_parser.add_argument('-s', '--system', help='System build to download for', choices=['linux', 'mac', 'win'],
default=None)
arg_parser.add_argument('-a', '--arch', help='Arch build to download for (win is always 32).', choices=['32', '64'],
default=None, required=False)
arg_parser.add_argument('-v', '--version', help='Chrome version (First 3 version numbers) to download for. (v70+)',
default=None)
arg_parser.add_argument('-f', '--filename', help='Name for the extracted chromedriver file',
default=None, type=str, required=False)
arg_parser.add_argument('-p', '--path', help='Directory to extract chromedriver to.',
default=None, type=str, required=False)
arg_parser.add_argument('-l', '--log-file', help='Log File Path', default=None, required=False)
arg_parser.add_argument('-L', '--log-level', help='Log Level', choices=['DEBUG', 'INFO', 'WARNING', 'CRITICAL'],
default='INFO', required=False)
args = arg_parser.parse_args()
log_format = logging.Formatter('%(asctime)s :: %(levelname)s :: %(name)s : %(message)s',
datefmt='%m/%d/%Y %H:%M:%S')
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_format)
logger.addHandler(console_handler)
if args.system is None:
args.system = discover_system()
if args.arch is None:
args.arch = discover_arch()
if args.version is None:
args.version = discover_chrome_version()
if args.log_file:
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler(args.log_file, maxBytes=5 * (1024 ** 2), backupCount=5)
file_handler.setFormatter(log_format)
logger.addHandler(file_handler)
logger.setLevel(args.log_level)
download_chromedriver(args.system, args.arch, args.version, args.filename, args.path)
@jslay88
Copy link
Author

jslay88 commented Apr 29, 2019

Python 3

Figured this might be useful, since LATEST_RELEASE file apparently no longer contains the actual latest release, and has caused issues for me (specifically when Chrome updated to 74, the version listed in LATEST_RELEASE no longer worked).

According to the new info provided by Google. https://sites.google.com/a/chromium.org/chromedriver/downloads/version-selection

Version Selection

We maintain multiple versions of ChromeDriver. Which version to select depends on the version of Chrome you are using it with. Specifically:

  • ChromeDriver uses the same version number scheme as Chrome. See https://www.chromium.org/developers/version-numbers for more details.
  • Each version of ChromeDriver supports Chrome with matching major, minor, and build version numbers. For example, ChromeDriver 73.0.3683.20 supports all Chrome versions that start with 73.0.3683.
  • Before a new major version of Chrome goes to Beta, a matching version of ChromeDriver will be released.
  • After the initial release of a new major version, we will release patches as needed. These patches may or may not coincide with updates to Chrome.

Here are the steps to select the version of ChromeDriver to download:

We always provide ChromeDriver for the current Stable and Beta versions of Chrome. However, if you use Chrome from Dev or Canary channel, or build your own custom version of Chrome, It is possible that there is no available ChromeDriver that officially supports it. In this case, please try the following:

Please don't rely on the LATEST_RELEASE file without a version suffix. It exists for backward compatibility only, and will be removed in the near future.

This script skips the step of discovering the download link, as it is always static per version, system, and arch. The discovery process of the exact version available to download is what is important. Will add support for discovering installed Chrome version for macOS and Linux at a later time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment