[Selenium] 自動偵測瀏覽器版本並下載對應的瀏覽器驅動
昨天在群里聊天時,有同學說 Appium 官方支援自動下載兼容的瀏覽器驅動,想來Selenium也有類似的方法,於是在網上搜索一番。參考了Medium上一篇文章的方法,對步驟進行改進,增加了對多瀏覽器的支援。
首先,先想好大致上的幾個步驟
- 識別本地瀏覽器版本
- 下載對應瀏覽器版本的驅動
- 解壓到對應文件夾
- 記錄到
mapping.json
文件中
接下來就是擼起袖子開干
定義好目錄結構
|— config
|— mapping.json: 瀏覽器驅動配置資訊
|— driver: 存放瀏覽器驅動
|— utils
|— driver_util.py: 封裝的工具包
|— test_search.py: 測試腳本
數據準備
導入第三方庫,定義好路徑名稱等常量
import json
import os
import zipfile
import shutil
import requests
import pathlib
from win32com import client as win_client
# 工作目錄(當前路徑調試時需加上.parent)
BASE_DIR = str(pathlib.Path.cwd())
# BASE_DIR = str(pathlib.Path.cwd().parent)
CHROME_DRIVER_BASE_URL = "//chromedriver.storage.googleapis.com"
EDGE_DRIVER_BASE_URL = "//msedgedriver.azureedge.net"
CHROME_DRIVER_ZIP = "chromedriver_win32.zip"
EDGE_DRIVER_ZIP = "edgedriver_win64.zip"
CHROME_DRIVER = "chromedriver.exe"
EDGE_DRIVER = "msedgedriver.exe"
BROWSER_DRIVER_DIR = str(pathlib.PurePath(BASE_DIR, "driver"))
DRIVER_MAPPING_FILE = os.path.join(BASE_DIR, "config", "mapping.json")
第一步,獲取瀏覽器的版本
Chrome 瀏覽器有些小版本沒有對應版本號的瀏覽器驅動,需要藉助 Query API 查詢對應大版本LATEST RELEASE版本,再根據查詢對應的瀏覽器驅動
新版Edge 瀏覽器每個版本號官網都有對應的驅動下載
Latest Version API
//chromedriver.storage.googleapis.com/LATEST_RELEASE_{version}Download Chrome Driver API
//chromedriver.storage.googleapis.com/{version}/chromedriver_win32.zip
//msedgedriver.azureedge.net/{version}/edgedriver_win64.zip
程式碼如下
def get_browser_version(file_path):
"""
獲取瀏覽器版本
:param file_path: 瀏覽器文件路徑
:return: 瀏覽器大版本號
"""
# 判斷路徑文件是否存在
if not os.path.isfile(file_path):
raise FileNotFoundError(f"{file_path} is not found.")
win_obj = win_client.Dispatch('Scripting.FileSystemObject')
version = win_obj.GetFileVersion(file_path)
return version.strip()
def get_browser_major_version(file_path):
"""
獲取瀏覽器大版本號
:param file_path: 瀏覽器文件路徑
:return: 瀏覽器大版本號
"""
browser_ver = get_browser_version(file_path)
browser_major_ver = browser_ver.split(".")[0]
return browser_major_ver
def get_latest_browser_version(browser_major_ver):
"""
獲取匹配大版本的最新release版本
:param browser_major_ver: 瀏覽器大版本號
:return: 最新release版本號
"""
latest_api = f"{CHROME_DRIVER_BASE_URL}/LATEST_RELEASE_{browser_major_ver}"
resp = requests.get(latest_api)
latest_driver_version = resp.text.strip()
return latest_driver_version
第二步,下載瀏覽器驅動
def download_browser_driver(latest_driver_version, browser_name):
"""
下載瀏覽器驅動壓縮包
:param browser_name: 瀏覽器名稱
:param latest_driver_version: 瀏覽器的版本號
"""
download_api = None
if browser_name == "Chrome":
download_api = f"{CHROME_DRIVER_BASE_URL}/{latest_driver_version}/{CHROME_DRIVER_ZIP}"
elif browser_name == "Edge":
download_api = f"{EDGE_DRIVER_BASE_URL}/{latest_driver_version}/{EDGE_DRIVER_ZIP}"
download_dir = os.path.join(str(BROWSER_DRIVER_DIR), os.path.basename(download_api))
# 下載,設置超時時間20s
resp = requests.get(download_api, stream=True, timeout=20)
if resp.status_code == 200:
with open(download_dir, 'wb') as fo:
fo.write(resp.content)
else:
raise Exception("Download chrome driver failed")
第三步,解驅動壓縮包
解壓後將原壓縮包刪除
def unzip_driver(browser_major_ver, browser_name):
"""
解壓驅動壓縮包
:param browser_name: 瀏覽器名稱
:param browser_major_ver: 瀏覽器大版本號
:return: 驅動文件路徑
"""
file_path = None
driver_path = None
if browser_name == "Chrome":
file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(CHROME_DRIVER_ZIP))
driver_path = os.path.join(BROWSER_DRIVER_DIR, browser_major_ver, CHROME_DRIVER)
elif browser_name == "Edge":
file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(EDGE_DRIVER_ZIP))
driver_path = os.path.join(BROWSER_DRIVER_DIR, browser_major_ver, EDGE_DRIVER)
browser_driver_dir = os.path.join(BROWSER_DRIVER_DIR, browser_major_ver)
# 解壓到指定目錄
with zipfile.ZipFile(file_path, 'r') as zip_ref:
zip_ref.extractall(browser_driver_dir)
return driver_path
def remove_driver_zip(browser_name):
"""
刪除下載的驅動壓縮包
:param browser_name: 瀏覽器名稱
"""
file_path = None
if browser_name == "Chrome":
file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(CHROME_DRIVER_ZIP))
elif browser_name == "Edge":
file_path = os.path.join(BROWSER_DRIVER_DIR, os.path.basename(EDGE_DRIVER_ZIP))
os.remove(file_path)
第四步,讀寫配置文件資訊
def read_driver_mapping_json():
"""
讀取 mapping_json
:return: 字典格式
"""
if os.path.exists(DRIVER_MAPPING_FILE):
with open(DRIVER_MAPPING_FILE) as fo:
try:
driver_mapping_dict = json.load(fo)
# mapping.json內容為空時,返回空字典
except json.decoder.JSONDecodeError:
driver_mapping_dict = {}
else:
raise FileNotFoundError(f"{DRIVER_MAPPING_FILE} is not found")
return driver_mapping_dict
def write_driver_mapping_json(browser_major_ver, latest_driver_version, driver_path, browser_name):
"""
寫入 mapping_json
:param browser_major_ver: 瀏覽器大版本號
:param latest_driver_version: 瀏覽器驅動版本號
:param driver_path: 驅動存放路徑
:param browser_name: 瀏覽器名稱
"""
mapping_dict = read_driver_mapping_json()
# 版本號在dict中(瀏覽器名不在dict中)
if browser_major_ver in mapping_dict:
mapping_dict[browser_major_ver][browser_name] = {
"driver_path": driver_path,
"driver_version": latest_driver_version
}
# 大版本號不在dict中,且字典不為空
elif browser_major_ver not in mapping_dict and mapping_dict:
mapping_dict[browser_major_ver] = {
browser_name:
{
"driver_path": driver_path,
"driver_version": latest_driver_version
}
}
# 字典為空
else:
mapping_dict = {
browser_major_ver:
{
browser_name:
{
"driver_path": driver_path,
"driver_version": latest_driver_version
}
}
}
mapping_dict.update(mapping_dict)
with open(DRIVER_MAPPING_FILE, 'w') as fo:
json.dump(mapping_dict, fo)
綜合
將以上步驟整合到automatic_discover_driver
函數中,通過調用該函數返回瀏覽器驅動路徑
def automatic_discover_driver(browser_path, browser_name="Chrome"):
"""
偵測瀏覽器驅動是否在mapping.json有記錄,否則下載該驅動
:param browser_path: 瀏覽器路徑
:param browser_name: 瀏覽器名稱
"""
browser_maj_ver = get_browser_major_version(browser_path)
# Chrome需要獲取大版本號對應的latest release version
# Edge 可直接用當前瀏覽器版本號
if browser_name == "Chrome":
latest_browser_ver = get_latest_browser_version(browser_maj_ver)
elif browser_name == "Edge":
latest_browser_ver = get_browser_version(browser_path)
else:
raise Exception(f"{browser_name} is not found")
# 讀取mapping.json內容
mapping_dict = read_driver_mapping_json()
# json為空 或版本號不在mapping_dict中 或瀏覽器名不在mapping_dict中
if not mapping_dict or \
browser_maj_ver not in mapping_dict or \
browser_name not in mapping_dict[browser_maj_ver]:
# 下載瀏覽器驅動壓縮包
download_browser_driver(latest_browser_ver, browser_name)
# 解壓瀏覽器驅動壓縮包,並返回驅動路徑
driver_path = unzip_driver(browser_maj_ver, browser_name)
# 將瀏覽器大版本號、瀏覽器名、驅動路徑、對應的瀏覽器版本號資訊寫入到mapping.json中
write_driver_mapping_json(browser_maj_ver, latest_browser_ver, driver_path, browser_name)
# 刪除瀏覽器驅動壓縮包
remove_driver_zip(browser_name)
# 返回瀏覽器驅動的路徑
mapping_dict = read_driver_mapping_json()
return mapping_dict[browser_maj_ver][browser_name]["driver_path"]
測試
創建一個test_search.py文件驗證是否可以自動下載對應的瀏覽器驅動
import pytest
from time import sleep
from selenium import webdriver
from utils.driver_util import automatic_discover_driver as automatic
class TestSearch:
_CHROME_PATH = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
_EDGE_PATH = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
_browser = "Edge"
def setup(self):
driver_path = automatic(self._EDGE_PATH, self._browser)
if self._browser == "Chrome":
self.driver = webdriver.Chrome(driver_path)
elif self._browser == "Edge":
self.driver = webdriver.Edge(driver_path)
def teardown(self):
self.driver.close()
self.driver.quit()
def test_search_bing(self):
self.driver.get("//cn.bing.com/")
self.driver.find_element_by_id("sb_form_q").send_keys("selenium")
self.driver.find_element_by_id("sb_go_par").click()
sleep(3)
if __name__ == '__main__':
pytest.main()
實測,成功打開瀏覽器!
詳細程式碼://github.com/felixzfq/AutomaticDiscoverBrowserDriver
參考資料
- Automatic discovery of compatible Chromedriver
- [Python][Selenium] 人生苦短,把麻煩的 Chrome Browser Driver Version Mapping 自動化