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

最近在用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,也能暴力解决问题。但我就是想把这个离谱的问题搞清楚。如此刨根问底,不知又有多少意义呢?

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_illusion_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值