草庐IT

selenium爬虫翻页、刷新+循环的深坑

_illusion_ 2023-10-08 原文

最近在用selenium开发爬虫,爬取网站就是51job,在爬虫时,总会在翻页后就遇到这类错误:

StaleElementReferenceException: Message: stale element reference: element is not attached to the page document

总之,就是这个StaleElementReferenceException,差点给我搞崩溃了。因为这个错误其实并不难理解,Stale就是“过期的、失效的、坏掉的”意思,而这个错误也说的很明确,当前你使用的这个selenium element,并未attach到网页上,是个错误的element

下面,我就讲讲我的分析,说说这个错误为什么会出现、可能会在什么情况下出现、以及该怎么解决,或者更准确地说,应该是怎么规避它

1. 发现问题

我的爬虫代码已经过于复杂,在这里用伪代码+注释的方式简介一下:

具体情况就是,我在逐页爬取各个职位链接(为了直观,把链接甩这儿,其实没啥特别意义)。由于职位链接有多页,所以我每爬完一页后要翻页。翻页后,再次对每个职位链接click时,出现上面所述的StaleElementReferenceException错误

while True:
    job_elements_list = driver.find_elements(...)  # 找到当前页面上所有的岗位链接
    for job_ele in job_elements_list :   # 挨个链接点进去、获取页面信息
        job_ele.click()  # when you in 2nd **while** loop -> Boom!!!!!!!!!!!!
        do_somt_analyze()
    # 当前页都爬完,翻页
    fanye_button = driver.find_element(By.CSS_SELECTOR, fanye_button_css)  # 找到翻页按钮
    fanye_button.click()  # 点它!

    if is_last_page:  # 当走到最后一页,没法再翻页的时候,结束这罪恶的循环
        break
    

2. 定位与分析问题

经过我几次三番的尝试后,我基本可以把问题定位在“翻页”上。当然了,页面刷新、后退、前进等操作,只要是你在当前页面重新加载了,就都可以出现这个问题。

既然StaleElementReferenceException这个问题出自element的过期和失效,自然容易想到,我可能在第二页的页面上,仍然在使用第一页相同位置的元素进行click。这的确可能会导致问题,这也是不少其他博客提到的错误。但经排查,我这个问题还没这个简单。因为按理说如果真的是在爬取第二页时还在用上一页的element,那意味着我再打开第二页时,element没有重新获取,那就再获取一遍就完了呗?事实证明,我也并没有犯这个错误,因为如上代码,我每翻一页,元素都会再次获取

看到我上面标红的字了吗?“再次获取”,是否意味着获取到的元素,就是新的(第二页的)?

问题就是出在这里!!!!!

当我还在纠结,为何我单步调试似乎就没问题、一跑起来就出这个bug 的时候,我突然意识到,由于翻页操作并没有打开新的标签页,而代码的运行速度要比网页加载速度快上好多,这就导致了,我做了翻页、正在打开第二页后,while-loop内我再次获取新的职位链接element时,可能获取的还是第一页的网页中的对应element。所以我哪怕每次都重新获取页面element,但你没能保证你这个页面是新页面,所以自然也无法保证你这个element是前一页的stale element,还是下一页的新element。而我单步调试时,手速较慢,翻页后给了网页足够的加载时间,此时第二页加载好了,所以我获取的就都是第二页上的新element,自然也就没了这个bug

3. 解决问题

问题已经掰扯清楚了,那么说说咋解决。

其实这个问题,说白了就是“未等网页加载完毕就获取元素”的问题,强制sleep一会儿倒是也能解决,但显然既低效又可能出现少量的没等够时间的问题。所以有两个思路:

思路1:等待网页加载完成(按理说可行,但我没成功过...)

我们需确认翻页后的当前页已经完成全部加载,那么每次翻页后都先等待网页加载完毕就好了嘛。这方面,网上有不少例子,包括显式等待(driver.implicitly_wait(5))、隐式等待(等待网页最外层元素(我爬取的网站是html)加载完毕,代码很简单不再赘述),但我尝试都会出现偶尔不成功的现象,实属怪异。希望有成功的铁子们给我段样例代码,看看到底是我错了,还是别的什么原因

思路2:重新开个新标签页(打不过,我跑还不行吗?o(╥﹏╥)o)

emmm,这是被逼无奈下的选择——等待页面加载完,经常会等不到或等错,那直接新开的标签页吧,毕竟新标签页不会存在上一页的元素,也就不可能定位到和第二页位置一样、却是stale的元素了。最终这种方法尝试可行,现将我写得一小段代码留在下面:

def my_click_and_jump(element, switch_new_tab=False):
    '''
    单击,并跳转到新标签页
    :param element: 被点击的element
    :param switch_new_tab: 是否要在新标签页打开链接。默认不跳转
    :return: 新选项卡window对象
    '''
    time.sleep(1.5)
    element.click()

    if switch_new_tab:
        driver.execute_script(f'window.open("{driver.current_url}", "_blank");') # 注意js语句要带分号
        driver.close() # 关闭当前标签页(即第一页)

    # jump to new tab(即第二页)
    driver.switch_to.window(driver.window_handles[-1])
    return driver.current_window_handle

总之,这个问题就到此为止了。其实,就算每个页面强行sleep它个10s,也能暴力解决问题。但我就是想把这个离谱的问题搞清楚。如此刨根问底,不知又有多少意义呢?

有关selenium爬虫翻页、刷新+循环的深坑的更多相关文章

  1. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

  2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  5. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  6. ruby - Ruby 中的闭包和 for 循环 - 2

    我是Ruby的新手,有些闭包逻辑让我感到困惑。考虑这段代码:array=[]foriin(1..5)array[5,5,5,5,5]这对我来说很有意义,因为i被绑定(bind)在循环之外,所以每次循环都会捕获相同的变量。使用每个block可以解决这个问题对我来说也很有意义:array=[](1..5).each{|i|array[1,2,3,4,5]...因为现在每次通过时都单独声明i。但现在我迷路了:为什么我不能通过引入一个中间变量来修复它?array=[]foriin1..5j=iarray[5,5,5,5,5]因为j每次循环都是新的,我认为每次循环都会捕获不同的变量。例如,这绝对

  7. ruby - 下载位置 Selenium-webdriver Cucumber Chrome - 2

    我将Cucumber与Ruby结合使用。通过Selenium-Webdriver在Chrome中运行测试时,我想将下载位置更改为测试文件夹而不是用户下载文件夹。我当前的chrome驱动程序是这样设置的:Capybara.default_driver=:seleniumCapybara.register_driver:seleniumdo|app|Capybara::Selenium::Driver.new(app,:browser=>:chrome,desired_capabilities:{'chromeOptions'=>{'args'=>%w{window-size=1920,1

  8. Ruby:数组中的下一个/上一个值,循环数组,数组位置 - 2

    假设我有一个没有特定顺序的随机数数组。假设这些是参加马拉松比赛的人的ID#,他们按照完成的顺序添加到数组中,例如:race1=[8,102,67,58,91,16,27]race2=[51,31,7,15,99,58,22]这是一个简化且有些做作的示例,但我认为它传达了基本思想。现在有几个问题:首先,我如何获得特定条目之前和之后的ID?假设我正在查看运行者58,我想知道谁在他之前和之后完成了比赛。race1,runner58:previousfinisher=67,nextfinisher=91race2,runner58:previousfinisher=99,nextfinishe

  9. ruby - 奇怪的 ruby​​ for 循环行为(为什么这样做有效) - 2

    defreverse(ary)result=[]forresult[0,0]inaryendresultendassert_equal["baz","bar","foo"],reverse(["foo","bar","baz"])这行得通,我想了解原因。有什么解释吗? 最佳答案 如果我使用each而不是for/in重写它,它看起来像这样:defreverse(ary)result=[]#forresult[0,0]inaryary.eachdo|item|result[0,0]=itemendresultendforainb基本上就

  10. ruby - 如何证明 Ruby `for` 循环实际上是使用 `each` 方法实现的? - 2

    在EloquentRuby(第21页,第一版,第六次打印)一书中,作者(RussOlsen)提倡使用each方法而不是for循环,这与我在其他地方读到的所有内容一致。但是作者还继续说,这样做的一个原因是for循环实际上调用了each方法,所以为什么不直接删掉中间人并使用each?所以我想知道这实际上是如何工作的。为了调查,我确实在github上的Ruby存储库上进行了搜索,但发现很难确定我在哪里/如何看到它的实际效果。重述问题:我如何证明Rubyfor循环实际上是使用each方法实现的? 最佳答案 您可以通过编写一个实现每个的类来展

随机推荐