Last active
April 30, 2019 18:28
-
-
Save jslay88/67c0578d95bc02e739eeba6299474a60 to your computer and use it in GitHub Desktop.
Download Latest chromedriver. Win/Linux/Mac. 32/64bit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 inLATEST_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:
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.