使用Selenium WebDriver等待页面加载完成(Python)
我想要抓取一个使用无限滚动的页面上的所有数据。下面的Python代码可以实现这个功能。
for i in range(100):
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(5)
这段代码的意思是,每次我滚动到页面底部时,需要等5秒钟,这通常足够让页面加载新生成的内容。但是,这样可能效率不高,因为页面可能在5秒内就加载完了新内容。我该如何判断每次滚动到底部时,页面是否已经加载完新内容呢?如果我能检测到这一点,就可以在页面加载完成后再继续滚动,这样会更省时间。
16 个回答
来自 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"))
正如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
、.className
、tagName
的格式。
LINK_TEXT
通过HTML标签中包含的文本找到标签。例如,可以使用
(By.LINK_TEXT, "Next")
来选择一个显示“下一步”的链接。
PARTIAL_LINK_TEXT
与
LINK_TEXT
类似,但匹配部分字符串。
NAME
通过name属性找到HTML标签。这在处理HTML表单时很方便。
TAG_NAME
通过标签名找到HTML标签。
XPATH
使用XPath表达式来选择匹配的元素。
下面介绍三种方法:
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的博客。
我尝试把 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"
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
的模块可以帮助你。