269

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

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

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

4

15 回答 15

361

默认情况下,将通过方法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.

于 2014-10-25T21:44:05.793 回答
100

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

于 2016-05-18T14:49:05.127 回答
67

找到以下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))

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

于 2015-05-21T23:09:40.473 回答
39

正如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使用, .className,tagName约定按元素的类、id 或标记名称查找元素。

LINK_TEXT

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

PARTIAL_LINK_TEXT

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

NAME

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

TAG_NAME

按标签名称查找 HTML 标签。

XPATH

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

于 2016-10-14T07:19:32.790 回答
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"))
于 2017-01-26T12:17:08.237 回答
17

附带说明一下,您可以检查是否没有更多对 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
于 2017-07-09T16:18:52.933 回答
13

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

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

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

为了能够解决这个问题,我必须添加新文本。请务必在implicitly_wait.

于 2018-05-13T04:36:48.773 回答
8

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

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
于 2018-10-27T15:44:53.800 回答
7

持续加载数据的 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))
于 2020-07-22T20:43:54.257 回答
7

如何将 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"
于 2017-05-08T06:44:07.127 回答
4

您可以通过此功能非常简单地做到这一点:

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')")
于 2020-07-10T08:23:18.253 回答
1

这里有很好的答案。等待的快速示例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
于 2021-01-18T12:23:07.427 回答
1

在代码中使用它:

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()
于 2020-08-16T13:43:50.540 回答
0

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

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!")
于 2021-11-30T20:18:01.650 回答
0

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

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

我们可以使用 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())

这是直到()在幕后所做的

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)
于 2021-09-06T07:05:37.810 回答