Python Selenium.WebDriverWait 最强详解页面加载策略

Posted XianZhe_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python Selenium.WebDriverWait 最强详解页面加载策略相关的知识,希望对你有一定的参考价值。

Python Selenium.WebDriverWait 网页加载策略『详细』


一、网页加载策略🍗

在通过Selenium加载一个网页时,Selenium都会等待页面加载完了才会运行下面的代码,这是因为 webdriver.get 方法会阻塞直到网页全部加载完成。


通常如果当页面加载花费大量时间时,可能是加载了很多外部资源「如:图像、css」,又或则是浏览的是国外网站,使用的网络环境差等问题,这些都是造成页面加载慢的原因。

但对于某些网页来说,需要加载的元素往往比不需要使用到的网页更快的加载出来,那么如何才能在加载到需要的数据之后就停止阻塞并且执行 webdriver.get 方法下的代码是本文章要探究的问题。


二、加载策略种类

Selenium支持的加载策略有三种,分别是noneeagernormal,且提供自带的方法让我们去设置启动策略。

关键字加载策略状态描述
none没有等待html下载完成,不等待解析完成就开始执行操作,selenium 会直接返回
eager渴望等待整个dom树加载完成,即DOMContentLoaded这个事件完成。
只要 HTML 完全加载和解析完毕就开始执行操作,忽略加载样式表、图像和子框架
normal正常(默认)等待整个页面加载完毕再开始执行操作

在浏览器选项的基类源码中能够看到,Selenium已经写好了设置加载策略的方法,同时也限制了加载策略的种类『源码第13行』

class Options(object):

    def __init__(self):
        self._page_load_strategy = "normal"
        self._caps = DesiredCapabilities.EDGE.copy()
   
    @property
    def page_load_strategy(self):
        return self._page_load_strategy

    @page_load_strategy.setter
    def page_load_strategy(self, value):
        if value not in ['normal', 'eager', 'none']:
            raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.")
        self._page_load_strategy = value
    ......

三、配置加载策略

每种类型的 webdriver 都有一个对应的配置文件放在特定的类 DesiredCapabilities 里面,这个配置文件的数据结构为字典格式,通过定义键为 pageLoadStrategy 的键值对,可以使webdriver的页面加载策略发生改变。

模块位置 👇

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

1)、源码中是这么去使用加载策略的👇

可以看到,源码实质上就是在 DesiredCapabilities 配置文件中增加了一个pageLoadStrategy属性值『源码第32行』,而 page_load_strategy 则是 Selenium 提供设置加载策略的方法。

对于DesiredCapabilities类,在博客 Python Selenium.WebDriverWait 浏览器启动参数设置『Edge如何使用启动参数』 中也有提到过,不管是在浏览器启动参数类 Options 源码中,还是自定义的 Options 启动参数类中,都会用的上。本篇文章相当于对 Python Selenium.WebDriverWait 浏览器启动参数设置『Edge如何使用启动参数』 的扩展。

Options选项类部分源码

class Options(object):

    def __init__(self):
        self._page_load_strategy = "normal"
        self._caps = DesiredCapabilities.EDGE.copy()

	......

    @property
    def page_load_strategy(self):
        return self._page_load_strategy

    @page_load_strategy.setter
    def page_load_strategy(self, value):
        if value not in ['normal', 'eager', 'none']:
            raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.")
        self._page_load_strategy = value
        
    @property
    def capabilities(self):
        return self._caps
        
	......
    def to_capabilities(self):
        """
            Creates a capabilities with all the options that have been set and

            returns a dictionary with everything
        """
        caps = self._caps
        caps['pageLoadStrategy'] = self._page_load_strategy

        return caps

DesiredCapabilities 部分源码

class DesiredCapabilities(object):
	......
	FIREFOX = 
		"browserName": "firefox",
		"marionette": True,
		"acceptInsecureCerts": True,
	

    INTERNETEXPLORER = 
        "browserName": "internet explorer",
        "version": "",
        "platform": "WINDOWS",
    

    EDGE = 
        "browserName": "MicrosoftEdge",
        "version": "",
        "platform": "WINDOWS"
    

    CHROME = 
        "browserName": "chrome",
        "version": "",
        "platform": "ANY",
    
    ......

2)、更改使用加载策略:👇

  • 使用 page_load_strategy 方法更改加载策略:

    from selenium import webdriver
    from selenium.webdriver.edge.options import Options
    
    
    def timer(func):
        """计时装饰器,用于计算程序运行时间"""
        def inner(*args, **kwargs):
            start_time = time.time()
            res = func(*args, **kwargs)
            print(f"加载时间:time.time() - start_time")
            return res
        return inner
    
    
    @timer
    def get_url(browser_, url_):
        """对页面发起请求"""
        browser_.get(url_)
    
    
    if __name__ == '__main__':
        url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
        op = Options()
        # 设置页面加载策略为none
        # op.page_load_strategy = "none"
        # 设置页面加载策略为normal
        # op.page_load_strategy = "eager"
        browser = webdriver.Edge("msedgedriver 97.exe", capabilities=op.to_capabilities())
        get_url(browser, url)
    

    程序流程:
    使用 Edge 浏览器对目标网址发起请求,timer 为装饰器方法进行计时,对发起请求的 get_url 函数进行计时,分别计算各浏览器加载策略的耗时
    normal加载时间: 3.539372682571411
    eager加载时间: 2.0514888763427734
    none加载时间: 0.003000974655151367

  • 使用原生方法手动配置加载策略:

    可以看到其实就是先将选项类的配置转换为字典,再为这个字典添加键为 pageLoadStrategy 的键值对,再将这个字典传入浏览器的实例化参数中。

    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    
    
    def timer(func):
        """计时装饰器,用于计算程序运行时间"""
        def inner(*args, **kwargs):
            start_time = time.time()
            res = func(*args, **kwargs)
            print(f"加载时间:time.time() - start_time")
            return res
        return inner
    
    
    @timer
    def get_url(browser_, url_):
        """对页面发起请求"""
        browser_.get(url_)
    
    
    if __name__ == '__main__':
        url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
        op = Options()
        capabilities = op.to_capabilities()
        # 设置页面加载策略为none
        capabilities["pageLoadStrategy"] = "none"
        # 设置页面加载策略为normal
        # capabilities["pageLoadStrategy"] = "eager"
        browser = webdriver.Chrome("chromedriver 97.exe", desired_capabilities=capabilities)
        get_url(browser, url)
    

    程序流程:
    使用 Chrome 浏览器对目标网址发起请求,timer 为装饰器方法进行计时,对发起请求的 get_url 函数进行计时,分别计算各浏览器加载策略的耗时
    normal加载时间: 3.847010850906372
    eager加载时间: 2.529080390930176
    none加载时间: 0.002000093460083008

DesiredCapabilities类,其本质就是存放着关于浏览器启动的配置文件。
在Selenium3中,不同浏览器的Options选项类源码存在部分差异,为方便展示,上述参考源码使用的是Edge浏览器的选项类。
对于每次运行的计时结果,都会有差异。


四、对加载策略进行封装🎍

在上述 page_load_strategy 方法示例中,使用到 Edge 浏览器进行示例,不用 Chrome 浏览器是因为在 Chrome 的选项类中并没有自带加载策略方法供我们去使用,需要手动配置加载策略或则直接对这个方法进行自定义封装。

将 Edge 和 Chrome 加载策略配置好之后,在写法一样的情况下分别调用 to_capabilities 方法输出配置策略对其进行对比,会发现 Chrome 选项类的配置中少了 pageLoadStrategy 值,主要差异体现在有没有配置 pageLoadStrategy 值的加载策略

1)、Edge浏览器加载配置
配置部分代码

......
    url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
    op = Options()
    # 设置页面加载策略为none
    op.page_load_strategy = "none"
    # 设置页面加载策略为normal
    # op.page_load_strategy = "eager"
    # 打印配置策略
    print(op.to_capabilities())
    browser = webdriver.Edge("msedgedriver 97.exe", capabilities=op.to_capabilities())
    get_url(browser, url)


2)、Chrome浏览器加载配置
配置部分代码

......
    url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
    op = Options()
    # 设置页面加载策略为none
    op.page_load_strategy = "none"
    # 设置页面加载策略为normal
    # op.page_load_strategy = "eager"
    # 打印配置策略
    print(op.to_capabilities())
    browser = webdriver.Chrome("chromedriver 97.exe", options=op)
    get_url(browser, url)


3)、Chrome加载策略封装

在继承原有 Chrome 选项类的基础上,自定义加载策略方法,通过这种形式就可以弥补某些浏览器启动参数类方法不全的问题,或编写提升效率的方法

Chrome封装加载策略类

class ChromeOptions(Options):
    def __init__(self):
        super(ChromeOptions, self).__init__()
        self._page_load_strategy = "normal"

    @property
    def page_load_strategy(self):
        return self._page_load_strategy

    @page_load_strategy.setter
    def page_load_strategy(self, value):
        """设置加载策略方法"""
        if value not in ['normal', 'eager', 'none']:
            raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.")
        self._page_load_strategy = value

    def to_capabilities(self):
        """使用已设置的所有启动参数,并返回包含所有内容的字典"""
        caps = self._caps
        chrome_options = self.experimental_options.copy()
        chrome_options["extensions"] = self.extensions
        if self.binary_location:
            chrome_options["binary"] = self.binary_location
        chrome_options["args"] = self.arguments
        if self.debugger_address:
            chrome_options["debuggerAddress"] = self.debugger_address
        # 添加加载策略键值对
        caps["pageLoadStrategy"] = self._page_load_strategy
        caps[self.KEY] = chrome_options

        return caps

调用执行
可以看到,需要的 pageLoadStrategy 值已被成功添加,且程序也能正常使用自定义的加载策略

url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
op = ChromeOptions()
# 设置页面加载策略为none
op.page_load_strategy = "none"
# 设置页面加载策略为normal
# op.page_load_strategy = "eager"
# 打印配置策略
print(op.to_capabilities())
browser = webdriver.Chrome("chromedriver 97.exe", options=op)
get_url(browser, url)


五、配合显示等待使用加载策略

通过更改加载策略,Selenium不再等待页面加载完毕后才会执行 webdriver.get 下面的代码, 但这也会造成一个问题,在 webdriver.get 下面的代码通常都是页面元素的获取代码,但在页面没有加载结束的情况下去获取会造成程序抛出异常。

1)、显示等待

为了解决这个问题,这时候就需要用上 Selenium 的显示等待。
使用 Selenium 时能用上的等待方式共有三种,分别是显示等待、隐性等待、强制等待,这里只稍微提及一下显示等待。显示等待的作用:程序每隔xx秒看一眼,如果页面元素存在了,则代码继续执行下一步,否则继续循环检查,直到超过设置的最长时间抛出 TimeoutException 异常。

2)、配合显示等待

现使用 Selenium 配置 none 加载策略并打开博客页面,点击页面点赞标签,观察显示等待效果。
显示等待与加载策略配合使用,能够在一定程度上提高程序的执行效率,对于使用Selenium来说这是一个常见的技巧。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec


class ChromeOptions(Options):
    def __init__(self):
        super(ChromeOptions, self).__init__()
        self._page_load_strategy = "normal"

    @property
    def page_load_strategy(self):
        return self._page_load_strategy

    @page_load_strategy.setter
    def page_load_strategy(self, value):
        """设置加载策略方法"""
        if value not in ['normal', 'eager', 'none']:
            raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.")
        self._page_load_strategy = value

    def to_capabilities(self):
        """使用已设置的所有启动参数,并返回包含所有内容的字典"""
        caps = self._caps
        chrome_options = self.experimental_options.copy()
        chrome_options["extensions"] = self.extensions
        if self.binary_location:
            chrome_options["binary"] = self.binary_location
        chrome_options["args"] = self.arguments
        if self.debugger_address:
            chrome_options["debuggerAddress"] = self.debugger_address
        # 添加加载策略键值对
        caps["pageLoadStrategy"] = self._page_load_strategy
        caps[self.KEY] = chrome_options

        return caps


def timer(func):
    """计时装饰器,用于计算程序运行时间"""
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        print(f"加载时间:time.time() - start_time")
        return res
    return inner


@timer
def get_url(browser_, url_):
    """对页面发起请求"""
    browser_.get(url_)


def like(browser_: webdriver, sec=3, poll_frequency=0.5):
    """CSDN博客点赞方法

    Args:
        browser_: 浏览器对象
        sec: 显示等待时长
        poll_frequency: 显示等待轮询频率
    """
    xpaths = "//li[@id='is-like']"
    element = WebDriverWait(browser_, sec, poll_frequency=poll_frequency).until(
        ec.presence_of_element_located((By.XPATH, xpaths)))
    element.click()


if __name__ == '__main__':
    url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
    op = ChromeOptions()
    # 设置页面加载策略为none
    op.page_load_strategy = "none"
    # 设置页面加载策略为normal
    # op.page_load_strategy = "eager"
    browser = webdriver.Chrome("chromedriver 97.exe", options=op)
    get_url(browser, url)
    # 执行点赞
    like(browser)


六、Selenium4对加载策略的改动💣

在Selenium4中对页面的选项类进行了改动,每个浏览器的 Options选项类不再是单个存在的方式,在 Selenium4 每个选项类都继承来自了 BaseOptions 类。
这是一个很重要的改进,对于各个浏览器来讲,在原本没有定义加载策略方法浏览器选项类中也能用上加载策略了,不需要自行另外再去自定义方法,我们只需直接调用自带方法即可配置加载策略。

BaseOptions类部分源码 🐾

class BaseOptions(metaclass=ABCMeta):
    """
    Base class for individual browser options
    """
    ......
        @platform_name.setter
    def platform_name(self, platform: str) -> NoReturn:
        """
        Requires the platform to match the provided value: https://w3c.github.io/webdriver/#dfn-platform-name

        :param platform: the required name of the platform
        """
        self.set_capability("platformName", platform)

    @property
    def page_load_strategy(self) -> str:
        """
        :returns: page load strategy if set, the default is "normal"
        """
        return self._caps["pageLoadStrategy
    ......

Chrome浏览器启动选项直接使用🐔

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options


def get_url(browser_, url_):
    """对页面发起请求"""
    browser_.get(url_)


if __name__ == '__main__':
    url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
    op = Options()
    # 设置页面加载策略为none
    op.page_load_strategy = "none"
    # 设置页面加载策略为normal
    # op.page_load_strategy = "eager"
    driver = Service("chromedriver 97.exe")
    browser = webdriver.Chrome(service=driver, options=op)
    get_url(browser, url)

Edge浏览器启动选项直接使用🐔

from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.edge.options import Options


def get_url(browser_, url_):
    """对页面发起请求"""
    browser_.get(url_)


if __name__ == '__main__':
    url = "https://blog.csdn.net/XianZhe_/article/details/120929106?spm=1001.2014.3001.5501"
    op = Options()
    # 设置页面加载策略为none
    op.page_load_strategy = "none"
    # 设置页面加载策略为normal
    # op.page_load_strategy = "eager"
    driver = Service("msedgedriver 97.exe")
    browser = webdriver.Edge(service=driver, options=op)
    get_url(browser, url)

参考文献💗


相关博客🤩

以上是关于Python Selenium.WebDriverWait 最强详解页面加载策略的主要内容,如果未能解决你的问题,请参考以下文章

Selenium WebDriver(Python)API

[python] python+selenium+webdriver

[python] python+selenium+webdriver

selenium webdriver (python)大全

python+selenium—webdriver入门

python+selenium—webdriver入门