等待页面加载有Selenium WebDriver for Python


181

我想抓取无限滚动实现的页面的所有数据。以下python代码有效。

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

这意味着每次我向下滚动到底部时,我都需要等待5秒钟,这通常足以使页面完成加载新生成的内容。但是,这可能不是省时的。该页面可能会在5秒内完成新内容的加载。每次向下滚动时,如何检测页面是否完成了新内容的加载?如果我可以检测到此情况,则知道页面完成加载后,可以再次向下滚动以查看更多内容。这样更省时。


1
了解有关页面的更多信息可能会有所帮助。元素是顺序的还是可预测的?您可以通过使用id或xpath检查可见性来等待元素加载
2014年

我正在抓取以下页面:pinterest.com/cremedelacrumb/yum
apogne 2014年


这回答了你的问题了吗?等待硒中的页面加载
Matej J

Answers:


234

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
我是越来越“find_element()*后的参数必须是一个序列,不WebElement”改为“WebDriverWait(浏览器,延迟)。直到(EC.presence_of_element_located((By.ID, ”IdOfMyElement“)))”看到手册硒python.readthedocs.org/en/latest/waits.html
2015年

2
@fragles的评论和David Cullen的回答对我有用。也许这个接受的答案可以相应地更新?
Michael Ohlrogge

6
通过browser.find_element_by_id('IdOfMyElement')会使a NoSuchElementException升高。该文档说要传递一个如下所示的元组:(By.ID, 'IdOfMyElement')。看看我的回答
David Cullen

2
希望这可以帮助其他人,因为起初我还不清楚:WebDriverWait实际上会返回一个Web对象,然后您可以对其执行操作(例如click()),从中读取文本等。我​​误以为它只是引起了等待,之后您仍然必须找到该元素。如果您进行了等待,然后查找元素,硒将出错,因为它会在旧的等待仍在处理的同时尝试查找该元素(希望这很有意义)。最重要的是,在使用WebDriverWait之后,您无需查找元素-它已经是一个对象。
本·威尔逊

1
@Gopgop 哇,这太丑陋了,不是建设性的评论。这有什么丑陋的?怎样才能更好?
Modus Tollens

72

试图传递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文档的链接。


2
谢谢!是的,这也是我所需要的。ID不是唯一可以使用的属性,要获取完整列表,请使用help(By)。例如,我曾经使用过EC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge

这也是它对我有效的方式!我写了一个额外的答案,扩展了该By对象可用的不同定位器。
J0ANMM

我发布了一个跟期望有关的后续问题,该期望可能会加载不同的页面,而不是总是加载同一页面:stackoverflow.com/questions/51641546/…–
Liquidgenius

48

查找以下3种方法:

readyState

检查页面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))

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


为什么说不self.driver.execute_script('return document.readyState;')可靠?对于我的用例来说,它似乎工作得很好,它正在等待将静态文件加载到新标签页中(该标签页是通过JavaScript在另一个标签页中打开的,而不是.get())。
亚瑟·赫伯特

1
@ArthurHebert由于比赛条件可能不可靠,我已经添加了相关引用。
kenorb

23

正如David Cullen回答中所提到的,我一直看到建议使用类似于以下内容的行:

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

对于我来说,很难找到可以与一起使用的所有可能的定位器By,因此我认为在此处提供列表会很有用。根据Ryan Mitchell的《使用Python进行Web爬取》

ID

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

CLASS_NAME

用于通过其HTML类属性查找元素。为什么这个功能CLASS_NAME不简单CLASS?使用表单object.CLASS 会给Selenium的Java库带来问题,这.class是保留方法。为了使Selenium语法在不同语言之间保持一致,CLASS_NAME使用了替代语言。

CSS_SELECTOR

通过他们的阶级,ID或标签名称找到元素,使用#idName.classNametagName约定。

LINK_TEXT

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

PARTIAL_LINK_TEXT

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

NAME

通过名称属性查找HTML标记。这对于HTML表单很方便。

TAG_NAME

按标记名称查找HTML标记。

XPATH

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


5
By文档列出了可用作定位器的属性。
David Cullen

1
那就是我一直在寻找的东西!谢谢!好吧,现在应该更容易找到了,因为google正在将我发送给我这个问题,而不是发送给官方文档。
J0ANMM

感谢您对本书的引用。它比文档清楚得多。
ZygD


11

附带说明一下,您可以检查是否没有对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

这很有用。但是500代表什么?它足够大到页面的结尾吗?
Moondra '18

这是页面应滚动的数量...您应将其设置得尽可能高。我刚刚发现这个数字对我来说已经足够了,因为它使页面滚动到底部,直到AJAX元素被延迟加载为止,从而激发了重新加载页面的需要
raffaem

这有助于确保完全加载gitlab中关于某个问题的所有注释。
bgStack15

7

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

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

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

为了能够解决此问题,我必须添加新文本。确保在中使用小写字母“ w” implicitly_wait


隐式等待和webdriverwait有什么区别?
song0089

4

如何将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"

你不需要循环?
科里·戈德堡

4

在这里,我使用了一种非常简单的形式:

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

1

您可以通过以下功能非常简单地执行此操作:

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')")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.