Python 可靠地检测页面加载或超时,Selenium 2

Python 可靠地检测页面加载或超时,Selenium 2,python,webdriver,selenium-webdriver,Python,Webdriver,Selenium Webdriver,我正在使用Selenium 2(2.33版Python绑定,Firefox驱动程序)编写一个通用的web scraper。它应该获取任意URL,加载页面,并报告所有出站链接。因为URL是任意的,我不能对页面的内容做任何假设,所以通常的建议(等待特定元素出现)是不适用的 我有一个应该轮询文档的代码。readyState直到它达到“完成”或30秒超时,然后继续: def readystate_complete(d): # AFAICT Selenium offers no better wa

我正在使用Selenium 2(2.33版Python绑定,Firefox驱动程序)编写一个通用的web scraper。它应该获取任意URL,加载页面,并报告所有出站链接。因为URL是任意的,我不能对页面的内容做任何假设,所以通常的建议(等待特定元素出现)是不适用的

我有一个应该轮询
文档的代码。readyState
直到它达到“完成”或30秒超时,然后继续:

def readystate_complete(d):
    # AFAICT Selenium offers no better way to wait for the document to be loaded,
    # if one is in ignorance of its contents.
    return d.execute_script("return document.readyState") == "complete"

def load_page(driver, url):
    try:
        driver.get(url)
        WebDriverWait(driver, 30).until(readystate_complete)
    except WebDriverException:
        pass

    links = []
    try:
        for elt in driver.find_elements_by_xpath("//a[@href]"):
            try: links.append(elt.get_attribute("href"))
            except WebDriverException: pass
    except WebDriverException: pass
    return links
这种方法很有效,但是在大约五分之一的页面上,
.until
调用永远挂起。当这种情况发生时,通常浏览器实际上还没有完成加载页面(“throbber”仍在旋转),但几十分钟过去了,超时不会触发。但有时页面看起来已经完全加载,脚本仍然无法继续

有什么好处?如何使超时可靠地工作?是否有更好的方法请求等待页面加载(如果无法对内容做出任何假设)

注意:
WebDriverException
的强制捕获和忽略被证明是必要的,以确保它从页面中提取尽可能多的链接,而不管页面中的JavaScript是否正在用DOM做有趣的事情(例如,我过去常常在提取HREF属性的循环中出现“stale element”错误)


注意:这个问题在这个网站和其他地方都有很多不同之处,但它们都有细微但关键的区别,这使得答案(如果有的话)对我来说毫无用处,或者我尝试过这些建议,但它们都不起作用。请准确回答我提出的问题。

如果页面仍在无限期加载,我猜readyState永远不会达到“完成”。如果您使用的是Firefox,可以通过调用
window.stop()
,强制页面加载停止:


我有一个类似的情况,我使用Selenium为一个相当知名的网站服务编写了屏幕截图系统,遇到了同样的困境:我对加载的页面一无所知

在与一些Selenium开发人员交谈之后,答案是不同的WebDriver实现(例如Firefox驱动程序与IEDriver)在网页何时被认为是加载的以及WebDriver何时返回控制时做出了不同的选择

如果您深入研究Selenium代码,您可以找到尝试并做出最佳选择的地方,但由于有许多事情可能会导致正在查找的状态失败,例如多个帧中一个帧没有及时完成,因此在某些情况下,驱动程序显然不会返回

有人告诉我,“这是一个开源项目”,可能不会/无法针对每种可能的情况进行纠正,但我可以在适用的情况下进行修复并提交补丁

从长远来看,这对我来说有点太难了,和你一样,我创建了自己的超时过程。因为我使用Java,所以我创建了一个新线程,当达到超时时,它会尝试做几件事让WebDriver返回,即使有时只需按某些键让浏览器响应也行得通。如果它没有返回,那么我将关闭浏览器并重试

再次启动驱动程序为我们处理了大多数情况,就好像浏览器的第二次加载使其处于更稳定的状态一样(请注意,我们是从虚拟机启动的,浏览器经常希望检查更新,并在最近未启动时运行某些例程)

另一个方面是,我们首先启动一个已知的url,确认浏览器的某些方面,并且在继续之前,我们实际上能够与它进行交互。这些步骤加在一起,故障率相当低,大约3%,在所有浏览器/版本/操作系统(FF、IE、CHROME、Safari、Opera、iOS、Android等)上进行了1000次测试


最后但并非最不重要的一点是,对于您的情况,听起来您只需要捕获页面上的链接,而不需要完全实现浏览器自动化。我还可以采用其他方法,比如cURL和linux工具

据我所知,您的
readystate\u complete
没有作为驱动程序执行任何操作。get()已经在检查该条件。不管怎样,我看到它在很多情况下都不起作用。您可以尝试的一件事是通过代理路由您的流量,并将其用于ping任何网络流量。Ie具有等待交通量停止方法:

def wait_for_traffic_to_stop(self, quiet_period, timeout):
"""
Waits for the network to be quiet
:Args:
- quiet_period - number of seconds the network needs to be quiet for
- timeout - max number of seconds to wait
"""
    r = requests.put('%s/proxy/%s/wait' % (self.host, self.port),
        {'quietPeriodInMs': quiet_period, 'timeoutInMs': timeout})
    return r.status_code

以下是(使用方法)提出的解决方案:

  • “推荐”(尽管仍然丑陋)的解决方案可以是:

  • 天真的尝试是这样的:

    def wait_for(condition_function):
        start_time = time.time()
        while time.time() < start_time + 3:
            if condition_function():
                return True
            else:
                time.sleep(0.1)
        raise Exception(
            'Timeout waiting for {}'.format(condition_function.__name__)
        )
    
    
    def click_through_to_new_page(link_text):
        browser.find_element_by_link_text('my link').click()
    
        def page_has_loaded():
            page_state = browser.execute_script(
                'return document.readyState;'
            ) 
            return page_state == 'complete'
    
        wait_for(page_has_loaded)
    
  • 最后一个示例包括如下比较页面ID(可以是防弹的):

    现在我们可以做:

    with wait_for_page_load(browser):
        browser.find_element_by_link_text('my link').click()
    

  • 以上代码示例来自。

    如果您使用的是
    WebDriverWait
    ,那么您使用的是Selenium 2,而不是Selenium RC。@RossPatterson I认为Selenium 2和Selenium RC是相同的东西,而Selenium IDE是老式的QuicKeys样式的东西。谢谢你的更正。你最后做了什么?@KnewB我放弃了。我的代码现在设置一分钟的全局超时,然后执行
    driver.get(url)
    ,后面紧跟
    driver.find\u elements\u by_xpath(“//a[@href]”)
    。这似乎是在报告链接之前等待页面加载。它仍然会一直挂起,所以我还编写了一个看门狗进程,如果它在五分钟内没有报告任何进展,就会终止并重新启动整个浏览器。它经常触发,令人头痛,但我不值得花时间进一步调试它。我仍然希望有更多线索的人会出现在这里。您可以使用pageLoadTimeOut()方法。这将花费浏览器等待页面加载的最长时间。
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait 
    from selenium.webdriver.support import expected_conditions
    
    old_value = browser.find_element_by_id('thing-on-old-page').text
    browser.find_element_by_link_text('my link').click()
    WebDriverWait(browser, 3).until(
        expected_conditions.text_to_be_present_in_element(
            (By.ID, 'thing-on-new-page'),
            'expected new text'
        )
    )
    
    def wait_for(condition_function):
        start_time = time.time()
        while time.time() < start_time + 3:
            if condition_function():
                return True
            else:
                time.sleep(0.1)
        raise Exception(
            'Timeout waiting for {}'.format(condition_function.__name__)
        )
    
    
    def click_through_to_new_page(link_text):
        browser.find_element_by_link_text('my link').click()
    
        def page_has_loaded():
            page_state = browser.execute_script(
                'return document.readyState;'
            ) 
            return page_state == 'complete'
    
        wait_for(page_has_loaded)
    
    def click_through_to_new_page(link_text):
        link = browser.find_element_by_link_text('my link')
        link.click()
    
        def link_has_gone_stale():
            try:
                # poll the link with an arbitrary call
                link.find_elements_by_id('doesnt-matter') 
                return False
            except StaleElementReferenceException:
                return True
    
        wait_for(link_has_gone_stale)
    
    class wait_for_page_load(object):
    
        def __init__(self, browser):
            self.browser = browser
    
        def __enter__(self):
            self.old_page = self.browser.find_element_by_tag_name('html')
    
        def page_has_loaded(self):
            new_page = self.browser.find_element_by_tag_name('html')
            return new_page.id != self.old_page.id
    
        def __exit__(self, *_):
            wait_for(self.page_has_loaded)
    
    with wait_for_page_load(browser):
        browser.find_element_by_link_text('my link').click()