【问题标题】:Wait until page is loaded with Selenium WebDriver for Python等到页面用 Selenium WebDriver for Python 加载
【发布时间】:2014-12-21 09:44:43
【问题描述】:

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

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

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

【问题讨论】:

标签: python selenium execute-script


【解决方案1】:

如果您尝试滚动并查找页面上的所有项目。您可以考虑使用以下内容。这是其他人在这里提到的几种方法的组合。它为我完成了这项工作:

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

【讨论】:

    【解决方案2】:

    我有点挣扎才能让它工作,因为它并没有像预期的那样对我有用。任何仍在努力使其正常工作的人都可以检查一下。

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

    我们可以使用 WebDriverWait(driver, 10, 1).until(),但问题是 until() 需要一个 函数,它可以执行一段时间每 1 秒提供超时(在我们的例子中为 10)。所以保持它像下面对我有用。

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

    这是 until() 在幕后所做的

    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)
    

    【讨论】:

      【解决方案3】:

      这里有很好的答案。等待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
      

      【讨论】:

        【解决方案4】:

        在代码中使用它:

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

        【讨论】:

          【解决方案5】:

          持续加载数据的 ajax 页面的解决方案。所述的预览方法不起作用。我们可以做的是获取页面 dom 并对其进行哈希处理,然后在 delta 时间内将新旧哈希值进行比较。

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

          【讨论】:

            【解决方案6】:

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

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

            【讨论】:

            • 那是纯粹的 CPU 阻塞脚本。
            • 投了反对票,这真是一种效率低下的忙等待,没有人应该这样做
            • 赞成正确性。最优性是一个单独的问题,但这通常是有效的。
            【解决方案7】:

            正如answer from David Cullen 中提到的那样,我一直看到有人建议使用如下行:

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

            我很难在某个地方找到可以与By 一起使用的所有可能的定位器,所以我认为在此处提供列表会很有用。 根据 Ryan Mitchell 的 Web Scraping with Python 所说:

            ID

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

            CLASS_NAME

            用于通过 HTML 类属性查找元素。为什么是这样 函数CLASS_NAME 不仅仅是CLASS?使用表格object.CLASS 会给 Selenium 的 Java 库带来问题,其中 .class 是 保留方法。为了保持 Selenium 语法一致 在不同语言之间,改用CLASS_NAME

            CSS_SELECTOR

            使用#idName,按类、id 或标签名称查找元素, .className, tagName 约定。

            LINK_TEXT

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

            PARTIAL_LINK_TEXT

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

            NAME

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

            TAG_NAME

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

            XPATH

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

            【讨论】:

            • documentation for By 列出了可以用作定位器的属性。
            • 这就是我一直在寻找的!谢谢!好吧,现在应该更容易找到,因为 google 向我发送了这个问题,而不是官方文档。
            • 感谢本书的引用。它比文档清晰得多。
            【解决方案8】:

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

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

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

            为了能够修正这个答案,我必须添加新文本。请务必在implicitly_wait 中使用小写的“w”。

            【讨论】:

            • 隐式等待和webdriverwait有什么区别?
            • @song0089 检查thisthisthis 讨论。
            【解决方案9】:

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

            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
            

            【讨论】:

              【解决方案10】:

              找到以下 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 只是返回立即生效。

              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's blog

              【讨论】:

              • 为什么说self.driver.execute_script('return document.readyState;')不靠谱?它似乎非常适合我的用例,它正在等待将静态文件加载到新选项卡中(通过 javascript 在另一个选项卡而不是 .get() 中打开)。
              • @ArthurHebert 由于比赛条件可能不可靠,我添加了相关引用。
              【解决方案11】:

              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的模块。

              【讨论】:

              • 我得到“find_element() argument after * must be a sequence, not WebElement”改为“WebDriverWait(browser, delay).until(EC.presence_of_element_located((By.ID, "IdOfMyElement" ))) " 见手册selenium-python.readthedocs.org/en/latest/waits.html
              • @fragles 的评论和 David Cullen 的回答对我有用。也许这个接受的答案可以相应更新?
              • 传递browser.find_element_by_id('IdOfMyElement') 会引发NoSuchElementExceptiondocumentation 表示要传递一个如下所示的元组:(By.ID, 'IdOfMyElement')。见my answer
              • 希望这对其他人有所帮助,因为我最初并不清楚:WebDriverWait 实际上会返回一个 Web 对象,然后您可以对其执行操作(例如 click()),读取文本等等我的错误印象是它只是导致了等待,之后你仍然必须找到元素。如果您等待,然后再查找元素,selenium 将出错,因为它会在旧等待仍在处理时尝试查找元素(希望这是有道理的)。底线是,您不需要在使用 WebDriverWait 后找到该元素——它已经是一个对象。
              • @Gopgop 哇,这太丑了 不是建设性的评论。它有什么难看的?怎样才能做得更好?
              【解决方案12】:

              附带说明,您可以检查 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代表什么?是否足够大到页面末尾?
              • 这是页面应该滚动的量......你应该将它设置得尽可能高。我刚刚发现这个数字对我来说已经足够了,因为它会使页面滚动到底部直到 AJAX 元素被延迟加载,从而需要再次重新加载页面
              • 这有助于确保 gitlab 中某个问题上的所有 cmets 都已完全加载。
              • 这很棒。感谢分享。
              【解决方案13】:

              来自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"))
              

              【讨论】:

                【解决方案14】:

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

                【讨论】:

                • 你不需要循环吗?
                【解决方案15】:

                试图将find_element_by_id 传递给presence_of_element_located 的构造函数(如accepted answer 所示)导致NoSuchElementException 被提升。我不得不使用fragles'comment中的语法:

                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"
                

                这与example in the documentation 匹配。这是documentation for By的链接。

                【讨论】:

                • 谢谢!是的,这也是我需要的。 ID 不是唯一可以使用的属性,要获取完整列表,请使用 help(By)。例如。我用EC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
                • 这对我来说也是如此!我写了一个额外的answer 扩展了By 对象可用的不同定位器。
                • 我发布了一个后续问题,处理可能加载不同页面的预期,而不是总是相同的页面:stackoverflow.com/questions/51641546/…
                • 在某些情况下这种方法不起作用。例如,如果您抓取同一网站的第一页,然后获取第二页,则两个页面中的所有 Id 都相同,.until(element_present) 始终为True
                猜你喜欢
                • 2021-03-11
                • 2012-07-07
                • 2021-01-18
                • 2012-07-12
                • 1970-01-01
                • 2017-03-27
                • 1970-01-01
                相关资源
                最近更新 更多