这些天踩过的selenium的坑

前言

不得不说,selenium是1个很不错的Web自动化工具,提供了多种语言的支持,其中包括Python、Ruby、Java、Nodejs等。但不得不说,随着对该工具使用的越来越多,就会发现它存在的一些不足甚至有待改进的地方。
而对该工具的使用,还得追溯到几年前信息采集的1个项目,当时是赚点人品学习了一下,并没有太深入的使用。而这次,为了项目的1个需求,深入的挖掘并进行总结,希望给后来的人抛砖引玉,减少走弯路。

适应人群

这篇文章写给有志于从事如下岗位工作的人群:

  • Web自动化测试
  • 信息安全开发
  • 爬虫工程师
  • 打算找份Python工作

说完了题外话,下面我们还是讲讲实际的操作。这里以Python为例进行说明。

找不到浏览器执行文件

在selenium高版本中,比如3.141中,即使你不指定浏览器的位置,selenium也是可以找到其位置,这是让自己觉得神奇的地方。当然,为了保险起见,还是把对应的浏览器可执行文件及相关的驱动放在环境变量PATH目录下吧。
如果实在找不到浏览器可执行文件,那么可以通过下面的方式进行指定,以火狐为例:

from selenium.webdriver.firefox.firefox_binary import FirefoxBinary  
binary = FirefoxBinary("E:/Program Files/Mozilla Firefox/firefox.exe")  
browser = webdriver.Firefox(firefox_binary=binary)

我们需要导入FirefoxBinary类,然后对其进行实例化,传入的参数是你本地系统上安装的绝对路径,最后将其作为firefox_binary参数传递给Firefox构造器。

浏览器无任何响应

如果上面的找不到浏览器执行文件是个小坑的话,那么这里的浏览器无任何响应可以说就是个隐形的陷阱。你会神奇的发现,当你把selenium、浏览器及对应驱动,如chromedriver、geckodriver都安装了,但是当浏览器执行到这一行:

browser = webdriver.Firefox()  
browser.get("https://www.baidu.com")

结果等了10秒,浏览器还是空白的一片。正常不是应该访问百度的首页吗?怎么不跳转呢,然后什么异常或者提示都没有。
当你遇到这样的情况,那么你就要静下心来先想一下,你当前的浏览器版本是多少?浏览器的驱动是多少?还有你的selenium库的版本是多少?
自己遇到1个问题就是,公司PC上安装的上述版本如下:

  
selenium = 2.53  
Firefox = 56.0  
geckodriver = 0.19  

按照常理,它是可以正常运行的,但是结果就是大半天都没响应。实际上,上述的版本中火狐版本56.0与selenium的版本是不兼容的,必须升级selenium库。
你需要记住的是,selenium的版本2.53最多只能支持到版本54即可,而geckodriver版本0.19是支持火狐55以上的。
详情可以参考火狐官方文档

访问的URL的请求获取问题

解决完或者逃过了上述2个问题后,对于业务需要获取访问的URL地址的请求的问题,可以说还是有难度的。但是,还是有解决方案的。
对于Python3来说,真的很简单。直接pip安装selenium-wire就好了,该库要求Python版本大于3.4,这完全可以说就不是事情。而很不幸,公司的项目采用的是Python2.7开发的,对于这样的问题,实际上有多种解决方案,这里简单的说下通过代理的方式。
最开始,公司方面使用的是proxy2这个库来获取其请求,其实际上通过Python标准库中的BaseHTTPServer和ThreadMixin来实现1个简易的代理。但是后来在实践中发现,该库存在3个不足的地方:

  • 会出现偶尔获取不了HTTPS请求包
  • HTTPS包即使能获取到但是很容易出现异常
  • 并发情况下直接请求很明显就是不对的

然后又选择了1个使用Tornado的toproxy的库,然而该库对于HTTPS的支持还是存在问题,而且要与项目集成也不容易。
最后,选择了1个Java编写的代理browsermob-proxy。对于该库,需要说的是,在pip安装时包的名称是browsermob-proxy而不是browsermobproxy,否则你会发现与网上的教程根本不是同一个事情。

代理竟然没有效果

选择好代理后,接下来就应该为浏览器设置代理了,不然是没有办法截取到HTTP请求信息的。然而,对于Chrome浏览器而言,常用的如下方式是完全无效的:

option = ChromeOptions()  
option.add_argument("--proxy-server=localhost:8080")

这是网上99%教程的写法,然而对于公司PC上版本只有60的Chrome浏览器而言,基本无视。
而如果将上述代码修改为如下:

proxy = Proxy({  
    "httpProxy":"localhost:8080",  
    "sslProxy":"localhost:8080"  
})

那么这种方式,就可以正常的获取到对应的URL请求的信息。

HTTPS证书不可用

好不容易把浏览器代理给设置好了,接着可以心想总算可以放心获取请求的信息了。结果对于HTTPS的站点页面,那么浏览器弹出当前站点不可信,浏览器拒绝连接。要么,获取到的信息是请求CA证书站点的。
对于浏览器弹出当前站点不可信的问题,可以直接让浏览器不验证证书的安全性。而对于获取到请求CA证书站点信息的问题,对于火狐浏览器而言,需要手动创建1个Profile,可以在关闭浏览器的情况下,重要的话要重复3遍。
在浏览器关闭的情况下,运行下面的命令:

  
firefox.exe -p  

此时弹出如下的页面:

profile

然后创建1个配置文件,这里假设为xxx,并在创建浏览器的时候进行指定:

profile = webdriver.FireforxProfile("xxx")  
browser = webdriver.Firefox(firefox_profile=profile)

节点遍历时的坑

总算解决了上面琐碎的问题后,总可以安心编码,赶紧弄完,回家躺着。每天熬夜,被项目经理催着是种煎熬。然而,当自己在代码中有如下代码时:

href = browser.find_elements_by_tag("a")  
for href in href:  
    href.click()

然后就直接出来个StaleElementReferenceException的异常,直接翻译的话就是对应元素的引用不新鲜了。另外,还有2个常见的异常就是NoSuchElementException和ElementNoVisbleException,通俗的说就是元素找不到和看不到了。
对于这些问题,要根据业务进行取舍,对于后两者,如果业务不是很重要,可以直接跳过当前节点的点击。 而对于StaleElementReferenceException这个异常,只能在每次点击之前重新获取该节点,换句话说,需要这样进行操作:

href = browser.find_elements_by_tag("a")  
l = len(href)  
for i in range(l):  
    href = browser.find_elements_by_tag("a")[i]  
    href.click()

这样就可以点击所有的a标签的链接了。

浏览器的Headless与Linux的code=1退出

总算写完基本的逻辑,部署完就可以回家休息了。而在Linux上直接运行不到2分钟就直接code=1的异常退出,异常类似如下:

  
... can not kill an existed process  

对于这样的异常,90%是没有开启浏览器的Headless模式,导致selenium异常退出。
此时,可以通过如下2种方法来开启Headless模式:

option = FirefoxOption()  
option.add_argument("-headless")  #方法一  
option.headless = True            #方法二

这样上述的问题就可以完美的解决了。

结语

经过1个星期加班加点的工作,总算把selenium的工作可以告一段落了。不得不说,selenium代码接口更新迭代很快,网上很多教程说的也是云里雾里,没少走弯路。
而官方文档的描述更是寥寥几字,很多情况下还是查看其实现的源码可知道来龙去脉,才可以适当的进行修改。
但是,selenium真的是个不错的工具,至少减轻了些琐碎的操作。
对于Python的相关API可以参考:

https://seleniumhq.github.io/selenium/docs/api/py/api.html
https://selenium-python.readthedocs.io/

参考文章:

https://stackoverflow.com/questions/45949274/setting-proxy-in-selenium-in-python-for-firefox-geckodriver
https://stackoverflow.com/questions/52534658/webdriverexception-message-invalid-argument-cant-kill-an-exited-process-with
https://www.cnblogs.com/baihuitestsoftware/articles/7753583.html

若文章对您有帮助,请打赏1块钱。您的支持,可以让我分享更多精彩的文章。转载请注明来源


知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。