zoukankan      html  css  js  c++  java
  • [Selenium] 自动侦测浏览器版本并下载对应的浏览器驱动

    昨天在群里聊天时,有同学说 Appium 官方支持自动下载兼容的浏览器驱动,想来Selenium也有类似的方法,于是在网上搜索一番。参考了Medium上一篇文章的方法,对步骤进行改进,增加了对多浏览器的支持。

    首先,先想好大致上的几个步骤

    1. 识别本地浏览器版本
    2. 下载对应浏览器版本的驱动
    3. 解压到对应文件夹
    4. 记录到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 = "https://chromedriver.storage.googleapis.com"
    EDGE_DRIVER_BASE_URL = "https://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
    https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{version}Download Chrome Driver API
    https://chromedriver.storage.googleapis.com/{version}/chromedriver_win32.zip
    https://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)GoogleChromeApplicationchrome.exe"
        _EDGE_PATH = r"C:Program Files (x86)MicrosoftEdgeApplicationmsedge.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("https://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()
    

    实测,成功打开浏览器!

    详细代码:https://github.com/felixzfq/AutomaticDiscoverBrowserDriver

    参考资料

  • 相关阅读:
    1305: Substring
    HDU1272 小希的迷宫 并查集
    1213 How Many Tables 简单的并查集问题
    POJ 3090 Visible Lattice Points 欧拉函数
    9.7——模拟赛
    洛谷——P2657 低头一族
    洛谷—— P1204 [USACO1.2]挤牛奶Milking Cows
    9.6——模拟赛
    洛谷—— P1126 机器人搬重物
    前端 自定义format函数
  • 原文地址:https://www.cnblogs.com/felixqiang/p/12891395.html
Copyright © 2011-2022 走看看