06 DriverManager源码解析+问题解决方案

1,725 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

「selenium实战专栏」将记录selenium实战(Python版)过程,以及各类问题的解决方案。

大致规划如下:

  • 利用Element UI组件库联系对各种元素的操作
  • 利用一个真实网站进行部分页面UI自动化实战

使用版本如下:

  • Python 3.10.6
  • selenium 4.0.5

《01 selenium环境搭建》中说到,selenium运行的原理就是通过chromedriver操作浏览器(Chrome浏览器为例,其他浏览器也是如此),所以在运行脚本前,需要先安装浏览器驱动。但是官方提供的DriverManager驱动管理工具存在些问题,比如我的Chrome版本是106.0.5249.103,使用DriverManager会报错“找不到对应版本的浏览器驱动”,如下图:

但是在chromedriver官网可以看到,其实大版本对应上就可以使用。

为了解决这个问题,同时明白DriverManager的实现原理,今天带大家一起了解下DriverManager的源代码,然后给出这个问题的解决方案。

DriverManager源码解析

ChromeDriverManager().install()

首先来看下官方提供的Demo,可以看到入口方法为ChromeDriverManager().install()

service = ChromeService(executable_path=ChromeDriverManager().install())
options = ChromeOptions()
driver = webdriver.Chrome(options=options, service=service)

因为DriverManager支持多种浏览器,所以不同的浏览器都实现了自己的install方法,这里以Chrome浏览器为为例,查看ChromeDriverManager().install()方法。

ChromeDriverManager构造方法很简单,在这里可以看到chrome驱动的下载地址,后续对应版本的驱动就是使用这个下载地址拼接版本号进行下载的。

install方法也很简单,主要是获取浏览器驱动的地址,通过命令chmod 755 /path/to/driver将浏览器驱动设置为可执行文件,然后返回,也就是我们之前手动设置浏览器可执行文件的步骤。

_get_driver_path

通过上面的分析知道这个方法是用来获取浏览器驱动地址的,先判断缓存中是否已经存在想要的驱动,已经存在就直接返回,没有的话,就要去下载驱动,然后保存到缓存中。这个缓存其实是保存在本地的一个文件,以{os_type}_{driver_name}_{driver_version}_{browser_version}来区分。

get_url

这个方法就是上面提到的通过版本路径拼接获取驱动的完整下载地址:

get_version最主要的就是获取到浏览器版本,主要逻辑在get_browser_version_from_os方法。

get_browser_version_from_os

这个方法定义了各种操作系统/浏览器获取版本的命令行,拿MAC下的Chrome浏览举例,可以看到是固定拿应用程序中Google Chrome的版本信息,所以如果你的应用程序改名了,或者没有放在应用程序中,都将会导致这个命令无法查询到浏览器版本。

获取对应的命令行之后通过subprocess执行命令获取浏览器版本。

总结下来其实思路很简单,主要就是两步:

  • 获取当前系统安装的浏览器的版本
  • 根据浏览器版本下载对应的浏览器驱动,缓存起来

Chrome浏览器找不到驱动包的问题

回到最开始的问题,根据Chrome的完整版本号找不到对应的驱动包。

经过上面的源码分析,首先可以通过手动设置版本号解决这个问题:

  • 在浏览器设置中找到完整的版本号,例如106.0.5249.103 ,其中106就是大版本号。(这一步也可以直接使用DriverManager提供的工具类get_browser_version_from_os来获取。)
  • 访问chromedriver.storage.googleapis.com/LATEST_RELE…获取大版本号对应的最新驱动版本。
  • 将驱动版本拷贝作为ChromeDriverManager参数,手动设置版本号后就不会再查询实际安装的浏览器版本号了。
service = Service(executable_path=ChromeDriverManager(version="106.0.5249").install())
options = Options()
driver = webdriver.Chrome(options=options, service=service)

另外一个方案是新增一个类继承ChromeDriver,重写get_browser_version方法,获取到实际浏览器版本号后,仅返回大版本号,后面的动作依然交给ChromeDriver来完成,这样就不需要手动查询大版本号对应的驱动版本了。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.core.utils import get_browser_version_from_os
from webdriver_manager.drivers.chrome import ChromeDriver


class MajorChromeDriver(ChromeDriver):
    def __init__(self, driver):
        super(MajorChromeDriver, self).__init__(
            name=driver._name,
            version=driver._version,
            os_type=driver._os_type,
            url=driver._url,
            latest_release_url=driver._latest_release_url,
            chrome_type=driver._chrome_type,
            http_client=driver._http_client,
        )

    def get_browser_version(self):
        version = get_browser_version_from_os(self.get_browser_type())
        return version.split('.')[0]


class MajorChromeDriverManager(ChromeDriverManager):
    def __init__(self):
        super(MajorChromeDriverManager, self).__init__()
        self.driver = MajorChromeDriver(self.driver)


service = Service(executable_path=MajorChromeDriverManager().install())
options = Options()
driver = webdriver.Chrome(options=options, service=service)

写在最后

在专栏第一节中不建议大家使用这个包DriverManager,因为刚开始搭建环境,一方面不需要在第三方包上花费过多的时间(比如跟我一样无法自动下载对应版本的浏览器驱动),一方面也是便于了解底层实现逻辑是什么。

今天了解源码之后,大家就清楚是怎么回事,出现问题也知道如何解决啦,从本节开始还是推荐大家使用这个包DriverManager(根据实际情况做些定制化,比如这个Chrome小版本号匹配不到的问题),这样就可以写一份代码就在不同操作系统,不同浏览器自动下载驱动运行啦。

说句题外话,之前这个包还没有出来的时候,都是自己写的python脚本来下载对应浏览器版本依赖的。现在看了官方给出的源代码,感觉自己之前的脚本写的好简陋,哈哈哈,感兴趣的伙伴也可以自己尝试实现下。