跳到内容

如何在 Selenium 中等待页面加载?专家指南

让我猜一下——您已经开始使用 Selenium 抓取网站,突然您面临着可怕的超时错误、过时的元素异常和不稳定的定位器。听起来有点熟?

我们很多人都去过那里!在当今的动态网络中,在交互之前正确等待页面完全加载对于可靠的自动化至关重要。

在这份超过 3200 字的综合指南中,我将利用我作为专业网络抓取专家 5 年多的经验来探索 Selenium 中优雅等待的各种方法和最佳实践。

无论您是新手还是经验丰富的专业人士,强大的等待逻辑都是确保稳定性的必备工具。让我们深入了解吧!

为什么你不能冲进去

在网络的早期,页面大多是按顺序呈现的简单 HTML。抓取工具可以在页面加载后立即开始提取。

但今天的网络是高度动态的。根据 谷歌研究,首次绘制的中位时间为 1.7 秒,但完全交互的中位时间是惊人的 15秒。加载内容需要很长时间。

作为一名爬虫,如果你冲得太快,你会面临以下一些常见问题:

  • 由于元素尚未呈现而导致按钮单击错误
  • 尝试从尚未加载服务器内容的表中读取数据
  • 将文本发送到屏幕上不可见的输入
  • 抓取页面加载后将填充的空元素

这些类型的异常是您在交互之前需要等待更长的时间才能使页面准备就绪的症状。

从数字来看:页面加载时间

为了了解我们可能需要等待多长时间,让我们看一下有关页面加载性能的一些实际指标 2020 年网络状况报告 通过阿卡迈:

  • 中位互动时间: 15s
  • 平均页面重量: 2744KB
  • 平均请求数: 105
  • 每页平均图像数: 53
  • 每页 JavaScript 字节数: 453KB

如今的页面更大、更复杂,在最初的响应之后需要做更多的工作。对于刮刀来说,等待交互性而不仅仅是第一次绘制非常重要。

不等待导致的常见异常

以下是元素尚未准备就绪时可能发生的一些特定异常:

  • 陈旧元素引用异常 – 获取后从 DOM 中删除的元素
  • 元素不可交互异常 – 尝试点击看不见的元素
  • 没有这样的元素异常 – 查找超时,因为元素尚不存在

每一个都表明爬虫需要更多的等待。

明确的等待是你的朋友

为了避免这些错误,我们需要等待页面完全渲染后再进行交互。 Selenium 有两种主要方法:

隐式等待 – 设置驱动程序的全局等待时间

显式等待 – 等待特定条件发生

在大多数情况下,显式等待比隐式等待更受欢迎。让我们了解一下原因。

隐式等待:大锤方法

隐式等待在驱动程序上设置一个超时,以便在查找元素时轮询 DOM。这意味着您任何时候致电:

driver.find_element_by_id(‘some-id‘)

驱动程序将重试隐式等待持续时间,以在抛出 NoSuchElementException 之前找到该元素。

你可能会这样使用它:

driver = webdriver.Chrome()
driver.implicitly_wait(10) 

现在,如果元素不立即存在,所有查找将重试长达 10 秒以查找元素。

缺点是它会等待每个定位器,甚至是不需要确定页面准备情况的定位器。这确实会减慢你的抓取速度。

隐式等待就像为每个元素获取添加 5 秒睡眠。加起来!

显式等待的精确度

显式等待使我们能够在继续操作之前精确等待表明准备就绪的特定条件。

关键想法是:

  • 仅在需要时等待 – 避免与页面准备情况无关的不必要的等待
  • 精确的条件 – 等待确切的元素或状态,而不仅仅是总时间
  • 高度灵活 – 自定义每个页面不同条件的等待逻辑
  • 可读 – 重新访问旧代码时易于理解意图

这是等待元素出现的典型示例:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait 

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "myDynamicElement"))
)

这将暂停执行,直到加载 ID 为“myDynamicElement”的元素,或者 10 秒过去。

其他有用的预期条件 由硒提供 包括:

  • title_contains() – 等待页面标题更新
  • staleness_of() – 等待元素不再附加到 DOM
  • element_to_be_clickable() – 等待元素可见并启用

显式胜过隐式:一个现实世界的例子

让我们用一个真实的例子来比较一下这两种等待。

假设我正在抓取一个具有导航栏、左侧面板和主要内容的网站。

我需要等待的关键元素是渲染我的数据的 ID“#main-content”。

使用隐式等待:

  • 即使不需要,每个元素查找也会增加 10 秒
  • 如果速度太快,仍然容易出现过时的元素错误

显式等待:

  • 仅在需要 #main-content 选择器时等待
  • 避免导航和侧面板不必要的等待
  • 专门等待数据加载后再继续

通过有选择地等待单个就绪条件(例如元素),我可以避免不必要的延迟。

有效显式等待的模式

既然您确信显式等待是可行的方法,那么让我们探讨一些有效使用它们的最佳实践。

页面加载等待

等待文档就绪状态是确定加载何时完成的常用技术:

WebDriverWait(driver, 10).until(
   lambda d: d.execute_script(‘return document.readyState‘) == ‘complete‘
)

这会轮询浏览器,直到就绪状态“完成”,表示所有资源均已加载。

更轻量级的模式是监视特定的高级元素:

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "main-content"))
) 

当主要内容部分加载时,此操作就会成功,而无需等待其他所有内容。

每个操作等待

您还可以在采取操作之前等待,例如单击某个元素:

menu = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "top-menu"))
)

submenu = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, "submenu"))
)

submenu.click()

这可确保顶部菜单和子菜单在单击之前都已准备好。

并行等待

等待多个条件可以确认页面已准备好:

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "header")),
    EC.presence_of_element_located((By.ID, "footer")), 
    EC.presence_of_element_located((By.ID, "main"))
)

要求加载页眉、页脚和主要内容可以减少误报。

链式和嵌套式等待

对于高级场景,还可以嵌套等待:

element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "dropdown"))
)

menu = WebDriverWait(element, 10).until(
    EC.presence_of_element_located((By.ID, "menu"))  
)

它首先等待父元素,然后等待其中的子元素。

AJAX 轮询等待

有些网站通过连续的 AJAX 请求加载。您可以循环等待更改:

while True:

    current_count = driver.find_element_by_id(‘result-count‘).text

    # If count changed since last check, page is still loading
    if current_count != previous_count:
        previous_count = current_count
        continue 

    break # Page loaded!

这会轮询一个元素以查找更改以检测加载。

异步等待

在像 asyncio 这样的异步框架中,您可以等待承诺:

await page.waitForSelector(‘#content‘)

语法有点不同,但提供异步等待。

隐式+显式组合

您甚至可以结合隐式和显式等待:

driver.implicitly_wait(10) 

my_element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "my-element"))
)

这样,您就可以同时进行全局等待和特定等待。只要确保他们使用合理的持续时间即可。

选择表明准备就绪的定位器

选择要等待的定位器时,您需要符合以下条件的元素:

  • 在加载过程中出现较晚
  • 拥有不会改变的唯一 ID 或类
  • 位于首屏以进行快速检查
  • 不太可能因地点变更而搬迁
  • 不要从 DOM 中删除并变得过时

一些常见的例子是:

  • 主标题或导航在资产之后加载
  • 主要内容容器或小部件
  • 页脚
  • 小型动态 UI 元素,例如按钮

像旋转器这样的负载指示器在消失时也是很好的等待触发器。

调整超时以实现最佳等待

将超时设置得太长确实会减慢抓取工具的速度,但太短可能会导致片状故障。

以下是调整持续时间的一些最佳实践:

  • 将页面加载超时设置更长,大约 10-20 秒。
  • 对单个元素使用较短的超时,例如 3-5 秒。
  • 考虑浏览器性能、移动设备与桌面设备。
  • 网络延迟因素、宽带与 3G。
  • 监视超时错误并根据需要调整得更高。
  • 分析页面加载瀑布的典型加载时间。
  • 额外预算 1-2 秒作为缓冲。
  • 在代码库中标准化类似的等待。

当您抓取更多页面时,您将对可靠性的最佳等待有更好的直觉。

处理等待和超时失败

即使等待时间较长,您仍然可能会偶尔遇到超时。以下是处理它们的一些方法:

  • 记录调试详细信息 – 添加打印有助于诊断等待失败的位置。
  • 超时重试 – 失败时重试短显式等待最多 3 次。
  • 增加超时时间 – 如果发生多次超时,请逐渐增加等待时间。
  • 使用尝试/例外 – 捕获特定异常,例如 StaleElementReference。
  • 失败时禁用 – 您可以在重复失败后跳过等待,让测试继续进行。

凭借内置的弹性,这些零星的问题不会破坏您的抓取工具。

用其他语言等待

到目前为止,示例都是用 Python 编写的,但显式等待可以跨语言使用:

  • 爪哇岛WebDriverWaitExpectedConditions
  • C#WebDriverWaitExpectedConditions
  • 红宝石WebDriver::WaitExpectedConditions
  • JavaScript的browser.wait() 和实用方法

这些概念非常相似——只是语法略有不同。

超越 Selenium:更多等待工具

除了 Selenium 之外,还有一些其他有用的等待库:

  • 时间time.sleep() 很简单,但暂停所有执行。
  • Retry 重试 ——由数百家创建、维护和提供物联网(IoT)全球开放标准的公司所组成的 重试包 使重试和等待变得容易。
  • 艾奥httpawait response.text() 等待网络调用完成。
  • 美丽的汤BeautifulSoup(page.content, features="lxml") 将等待完整解析。
  • Scrapyyield scrapy.Request(url, callback=self.parse) 是异步的。

将它们与 Selenium 混合可以在代码中提供强大的等待。

总结:好好等待并可靠地报废

最后,这里有五个要点:

  1. 使用显式等待 – 他们避免不必要的超时并针对特定条件。

  2. 等待多个信号 – 合并等待页眉、正文、页脚等以确认页面准备就绪。

  3. 明智地调整超时 – 根据实际页面加载数据设置值以优化延迟。

  4. 标准化等待 – 在代码库中重用一致的模式。

  5. 增加弹性 – 实施重试和失败处理以考虑动态页面。

起初,等待似乎很乏味。但是,投资强大的等待逻辑将为您带来为现代网络准备的可靠、有弹性的抓取工具。

希望这些从我作为专业网络抓取专家多年中总结出来的模式和技巧将帮助您成功等待。废了!

标签:

加入谈话

您的电邮地址不会被公开。 必填带 *