解决 Selenium 的 NoSuchElementException 问题

Selenium是爬虫开发过程中很常用的一个工具,为开发者提供了浏览器驱动调用的接口。在使用Selenium的过程中,通过其 find 系列的方法定位页面上的某个元素时,有时候会遇到抛出NoSuchElementException异常的情况,那么针对这种情况,可以做些什么呢?

首先以Chrome Driver为例子,写一个简单的程序:用 Chrome 访问百度,输出百度搜索按钮上的文本内容。

1
2
3
4
5
6
from selenium.webdriver import Chrome

CHROME_DRIVER_PATH = '/Users/kelaiwei/Python/chromedriver-74.0.3729.6'
with Chrome(CHROME_DRIVER_PATH) as browser:
browser.get('https://www.baidu.com/')
print(browser.find_element_by_id('su').get_attribute('value'))

运行代码后输出了”百度一下”。接下来用代码试验下find_element_by_id查找一个不存在于页面上的元素:

1
print(browser.find_element_by_id('nonexistence').text)

结果就会抛出 NoSuchElementException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Traceback (most recent call last):
File "/Users/kelaiwei/Python/Python3/projects/test_selenium/demo.py", line 7, in <module>
print(browser.find_element_by_id('nonexistence').text)
File "/Users/kelaiwei/.pyenv/versions/test-selenium/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 360, in find_element_by_id
return self.find_element(by=By.ID, value=id_)
File "/Users/kelaiwei/.pyenv/versions/test-selenium/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 978, in find_element
'value': value})['value']
File "/Users/kelaiwei/.pyenv/versions/test-selenium/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute
self.error_handler.check_response(response)
File "/Users/kelaiwei/.pyenv/versions/test-selenium/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"id","selector":"nonexistence"}
(Session info: chrome=74.0.3729.108)
(Driver info: chromedriver=74.0.3729.6 (255758eccf3d244491b8a1317aa76e1ce10d57e9-refs/branch-heads/3729@{#29}),platform=Mac OS X 10.12.6 x86_64)

类似这种情况在生产过程中是很常见,比如要查找的元素是异步加载的,不能在页面加载完成后马上显示出来,如果直接按上面的代码,就会抛出异常。针对这种情况,Selenium提供一个方法——implicitly_wait

Sets a sticky timeout to implicitly wait for an element to be found, or a command to complete. This method only needs to be called one time per session. To set the timeout for calls to execute_async_script, see set_script_timeout.

根据以上的注释,该方法用于设置一个时间,隐式地等待某个元素被找到或者某个命令被完成,并且这个数值在一次会话中只用被设置一次。比如要查找的元素保证能在页面加载完的5秒内被异步加载,就可以设置一个5秒的隐式等待时间。对以上代码稍作修改即可:

1
2
3
4
5
6
7
8
def get_chrome_browser():
browser = Chrome(CHROME_DRIVER_PATH)
browser.implicitly_wait(5) # 隐式等待5s
return browser

browser.get('https://www.baidu.com/')
print(browser.find_element_by_id('slow_loading').text)
browser.quit()

与这个方法类似的还有set_script_timeout(设置脚本运行等待时间)、set_page_load_timeout(设置页面加载等待时间)。