等待页面加载Selenium WebDriver for Python

我想刮取无限滚动实现的页面的所有数据。下面的python代码可以工作。

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

这意味着每当我向下滚动到底部时,我都需要等待5秒,这通常足以让页面完成加载新生成的内容。但是,这可能并不省时。页面可能在5秒内完成新内容的加载。如何在每次向下滚动时检测页面是否完成了新内容的加载?如果我能检测到这一点,一旦我知道页面完成加载,我就可以再次向下滚动以查看更多内容。这样更节省时间。

663491 次浏览

默认情况下,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的模块。

找到以下3种方法:

请求处理

检查页面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直接返回true。

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))

有关更多详细信息,请查看哈利的博客

试图将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"

这与文档中的示例匹配。这里有一个指向的链接。

正如在大卫·卡伦的回答中提到的,我总是看到使用如下一行的建议:

element_present = EC.presence_of_element_located((By.ID, 'element_id'))
WebDriverWait(driver, timeout).until(element_present)
对于我来说,很难在某个地方找到所有可以与By一起使用的定位器,所以我认为在这里提供列表是有用的。 根据Ryan Mitchell的Web scraption with Python . cn:

< em > ID < / em >

在例子中使用;通过HTML id属性查找元素

< em > CLASS_NAME < / em >

用于根据HTML类属性查找元素。为什么会这样 函数CLASS_NAME不是简单的CLASS?使用object.CLASS形式 会给Selenium的Java库带来问题,其中.class是一个 保留方法。以保持Selenium语法的一致性 在不同的语言之间,CLASS_NAME被使用

< em > CSS_SELECTOR < / em >

通过类,id或标记名查找元素,使用#idName.classNametagName convention.

< em > LINK_TEXT < / em >

根据HTML标签包含的文本查找它们。例如,一个链接 表示“Next”可以使用(By.LINK_TEXT, "Next")选择

< em > PARTIAL_LINK_TEXT < / em >

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

< em > NAME < / em >

根据名称属性查找HTML标记。这对于HTML表单来说很方便。

< em > TAG_NAME < / em >

根据标记名称查找HTML标记。

< em > XPATH < / em >

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

硒/ webdriver /支持/ wait.py

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

把WebDriverWait放在While循环中并捕获异常如何?

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


browser = webdriver.Firefox()
browser.get("url")
delay = 3 # seconds
while True:
try:
WebDriverWait(browser, delay).until(EC.presence_of_element_located(browser.find_element_by_id('IdOfMyElement')))
print "Page is ready!"
break # it will break from the loop once the specific element will be present.
except TimeoutException:
print "Loading took too much time!-Try again"

另外,您可以检查DOM是否没有更多的修改,而不是向下滚动100次(在页面底部是AJAX惰性加载的情况下)

def scrollDown(driver, value):
driver.execute_script("window.scrollBy(0,"+str(value)+")")


# Scroll down the page
def scrollDownAllTheWay(driver):
old_page = driver.page_source
while True:
logging.debug("Scrolling loop")
for i in range(2):
scrollDown(driver, 500)
time.sleep(2)
new_page = driver.page_source
if new_page != old_page:
old_page = new_page
else:
break
return True

你试过driver.implicitly_wait吗?它就像驱动程序的一个设置,所以你只在会话中调用它一次,它基本上告诉驱动程序等待给定的时间,直到每个命令都可以执行。

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

因此,如果您设置等待时间为10秒,它将尽快执行命令,等待10秒后才放弃。我在类似的滚动场景中使用过这个,所以我不明白为什么它在您的情况下不起作用。希望这对你有帮助。

为了能够修复这个答案,我必须添加新的文本。确保在implicitly_wait中使用小写` w'。

这里我使用了一个相当简单的形式:

from selenium import webdriver
browser = webdriver.Firefox()
browser.get("url")
searchTxt=''
while not searchTxt:
try:
searchTxt=browser.find_element_by_name('NAME OF ELEMENT')
searchTxt.send_keys("USERNAME")
except:continue

你可以通过这个函数简单地做到这一点:

def page_is_loading(driver):
while True:
x = driver.execute_script("return document.readyState")
if x == "complete":
return True
else:
yield False

当你想在页面加载完成后做一些事情时,你可以使用:

Driver = webdriver.Firefox(options=Options, executable_path='geckodriver.exe')
Driver.get("https://www.google.com/")


while not page_is_loading(Driver):
continue


Driver.execute_script("alert('page is loaded')")

ajax页面连续加载数据的解决方案。所述的预览方法无效。我们可以做的是抓取页面dom并对其进行哈希,并在一段时间内比较新旧哈希值。

import time
from selenium import webdriver


def page_has_loaded(driver, sleep_time = 2):
'''
Waits for page to completely load by comparing current page hash values.
'''


def get_page_hash(driver):
'''
Returns html dom hash
'''
# can find element by either 'html' tag or by the html 'root' id
dom = driver.find_element_by_tag_name('html').get_attribute('innerHTML')
# dom = driver.find_element_by_id('root').get_attribute('innerHTML')
dom_hash = hash(dom.encode('utf-8'))
return dom_hash


page_hash = 'empty'
page_hash_new = ''
    

# comparing old and new page DOM hash together to verify the page is fully loaded
while page_hash != page_hash_new:
page_hash = get_page_hash(driver)
time.sleep(sleep_time)
page_hash_new = get_page_hash(driver)
print('<page_has_loaded> - page not loaded')


print('<page_has_loaded> - page loaded: {}'.format(driver.current_url))

在代码中使用:

from selenium import webdriver


driver = webdriver.Firefox() # or Chrome()
driver.implicitly_wait(10) # seconds
driver.get("http://www.......")

或者你可以使用这段代码,如果你正在寻找一个特定的标签:

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


driver = webdriver.Firefox() #or Chrome()
driver.get("http://www.......")
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "tag_id"))
)
finally:
driver.quit()

回答得很好。等待XPATH的快速示例。

# wait for sizes to load - 2s timeout
try:
WebDriverWait(driver, 2).until(expected_conditions.presence_of_element_located(
(By.XPATH, "//div[@id='stockSizes']//a")))
except TimeoutException:
pass

我挣扎了一点,让这个工作,因为它没有为我工作的预期。任何还在努力让它工作的人,可以检查一下。

我想等待一个元素出现在网页上,然后再继续我的操作。

我们可以使用WebDriverWait(driver, 10,1).until(),但catch是直到()期望一个函数,它可以在一段超时时间内执行(在我们的例子中是10)每1秒。所以保持它像下面这样对我有用。

element_found = wait_for_element.until(lambda x: x.find_element_by_class_name("MY_ELEMENT_CLASS_NAME").is_displayed())

下面是直到()在幕后做的事情

def until(self, method, message=''):
"""Calls the method provided with the driver as an argument until the \
return value is not False."""
screen = None
stacktrace = None


end_time = time.time() + self._timeout
while True:
try:
value = method(self._driver)
if value:
return value
except self._ignored_exceptions as exc:
screen = getattr(exc, 'screen', None)
stacktrace = getattr(exc, 'stacktrace', None)
time.sleep(self._poll)
if time.time() > end_time:
break
raise TimeoutException(message, screen, stacktrace)

如果您试图滚动并找到页面上的所有项目。您可以考虑使用以下方法。这是其他人在这里提到的一些方法的组合。它帮我完成了任务:

while True:
try:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
driver.implicitly_wait(30)
time.sleep(4)
elem1 = WebDriverWait(driver, 30).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "element-name")))
len_elem_1 = len(elem1)
print(f"A list Length {len_elem_1}")
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
driver.implicitly_wait(30)
time.sleep(4)
elem2 = WebDriverWait(driver, 30).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "element-name")))
len_elem_2 = len(elem2)
print(f"B list Length {len_elem_2}")
if len_elem_1 == len_elem_2:
print(f"final length = {len_elem_1}")
break
except TimeoutException:
print("Loading took too much time!")

Selenium无法检测页面是否完全加载,但javascript可以。我建议你试试这个。

from selenium.webdriver.support.ui import WebDriverWait
WebDriverWait(driver, 100).until(lambda driver: driver.execute_script('return document.readyState') == 'complete')

这将执行javascript代码,而不是使用python,因为javascript可以检测页面何时完全加载,它将显示“完成”。这个代码的意思是在100秒内,继续尝试这个文档。readyState直到complete显示。

nono = driver.current_url
driver.find_element(By.XPATH,"//button[@value='Send']").click()
while driver.current_url == nono:
pass
print("page loaded.")