使用Selenium WebDriver等待页面加载完成(Python)

345 投票
16 回答
788798 浏览
提问于 2025-04-29 15:58

我想要抓取一个使用无限滚动的页面上的所有数据。下面的Python代码可以实现这个功能。

for i in range(100):
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(5)

这段代码的意思是,每次我滚动到页面底部时,需要等5秒钟,这通常足够让页面加载新生成的内容。但是,这样可能效率不高,因为页面可能在5秒内就加载完了新内容。我该如何判断每次滚动到底部时,页面是否已经加载完新内容呢?如果我能检测到这一点,就可以在页面加载完成后再继续滚动,这样会更省时间。

暂无标签

16 个回答

23

来自 selenium/webdriver/support/wait.py

driver = ...
from selenium.webdriver.support.wait import WebDriverWait
element = WebDriverWait(driver, 10).until(
    lambda x: x.find_element_by_id("someId"))
51

正如David Cullen的回答中提到的,我一直看到推荐使用类似下面这样的代码:

element_present = EC.presence_of_element_located((By.ID, 'element_id'))
WebDriverWait(driver, timeout).until(element_present)

我发现很难找到所有可以与By一起使用的定位器,所以我觉得把这个列表放在这里会很有用。根据Ryan Mitchell的《Python网络爬虫》

ID

在示例中使用;通过HTML的id属性找到元素。

CLASS_NAME

通过HTML的class属性找到元素。为什么这个函数叫CLASS_NAME而不是简单的CLASS呢?因为如果用object.CLASS的形式,会在Selenium的Java库中产生问题,因为.class是一个保留的方法。为了保持不同语言之间Selenium语法的一致性,所以使用了CLASS_NAME

CSS_SELECTOR

通过元素的class、id或标签名来找到元素,使用#idName.classNametagName的格式。

LINK_TEXT

通过HTML标签中包含的文本找到标签。例如,可以使用(By.LINK_TEXT, "Next")来选择一个显示“下一步”的链接。

PARTIAL_LINK_TEXT

LINK_TEXT类似,但匹配部分字符串。

NAME

通过name属性找到HTML标签。这在处理HTML表单时很方便。

TAG_NAME

通过标签名找到HTML标签。

XPATH

使用XPath表达式来选择匹配的元素。

74

下面介绍三种方法:

readyState

检查页面的准备状态(不太可靠):

def page_has_loaded(self):
    self.log.info("Checking if {} page is loaded.".format(self.driver.current_url))
    page_state = self.driver.execute_script('return document.readyState;')
    return page_state == 'complete'

wait_for这个辅助函数很好,但不幸的是,click_through_to_new_page可能会出现竞争条件,也就是说我们可能在浏览器处理点击之前就执行了脚本,这样page_has_loaded就会立刻返回真。

id

比较新页面的ID和旧页面的ID:

def page_has_loaded_id(self):
    self.log.info("Checking if {} page is loaded.".format(self.driver.current_url))
    try:
        new_page = browser.find_element_by_tag_name('html')
        return new_page.id != old_page.id
    except NoSuchElementException:
        return False

比较ID可能没有等待过期引用异常那么有效。

staleness_of

使用staleness_of方法:

@contextlib.contextmanager
def wait_for_page_load(self, timeout=10):
    self.log.debug("Waiting for page to load at {}.".format(self.driver.current_url))
    old_page = self.find_element_by_tag_name('html')
    yield
    WebDriverWait(self, timeout).until(staleness_of(old_page))

想了解更多细节,可以查看Harry的博客

121

我尝试把 find_element_by_id 传给 presence_of_element_located 的构造函数(就像在被接受的答案中所示),结果引发了 NoSuchElementException 错误。我不得不使用fragles评论中的语法:

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get('url')
timeout = 5
try:
    element_present = EC.presence_of_element_located((By.ID, 'element_id'))
    WebDriverWait(driver, timeout).until(element_present)
except TimeoutException:
    print "Timed out waiting for page to load"

这和文档中的例子是一样的。这里有By的文档链接

455

webdriver 默认情况下会通过 .get() 方法等待页面加载完成。

正如 @user227215 所说,如果你想找某个特定的元素,你应该使用 WebDriverWait 来等待页面中某个元素的出现:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException

browser = webdriver.Firefox()
browser.get("url")
delay = 3 # seconds
try:
    myElem = WebDriverWait(browser, delay).until(EC.presence_of_element_located((By.ID, 'IdOfMyElement')))
    print "Page is ready!"
except TimeoutException:
    print "Loading took too much time!"

我用它来检查警告框。你也可以用其他方法来找到元素的位置。

编辑 1:

我得提一下,webdriver 默认会等待页面加载完成,但它不会等待框架内的内容或 ajax 请求的加载。这意味着当你使用 .get('url') 时,浏览器会等到页面完全加载好后,才会执行代码中的下一条命令。但是当你发送 ajax 请求时,webdriver 不会等待,这就需要你自己去控制等待的时间,以确保页面或页面的一部分加载完成;所以有一个叫 expected_conditions 的模块可以帮助你。

撰写回答