盘点selenium phantomJS使用的坑

99封情书 提交于 2019-12-25 22:23:12

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

selenium是python的一个第三方自动化测试库,虽然是测试库,却也非常适合用来写爬虫,而phantomJS是其子包webdriver下面的一个浏览器。phantomJS本身是一个无头浏览器(headless browser),也称无界面浏览器。可以在通过官网下载运行phantomjs.exe,简单几行代码也能访问网页,爬取数据。但本文主要讨论通过python的selenium库使用phantomJS。除了phantomJS浏览器,webdriver还整合了Chrome、Firefox、IE等浏览器,并提供了操作这些浏览器的接口。

由于phantomJS是无界面浏览器,不需要界面的同时占用的内存也相对较小,更适用于大规模多进程爬数据(试想,如果开几十个Chrome进程爬数据,那真是内存噩梦!)。本文主要讨论使用selenium phantomJS过程中遇到的bug,而不是selenium phantomJS使用教程,有需要了解selenium基本用法的同学,请移步官方文档。

1. 查看phantomJS文档

phantomJS是selenium子包webdriver下面多个浏览器中的一个,而selenium包对不同的浏览器都提供了统一的接口,所以直接查看selenium的官方文档即可,也有对应的中文文档。文档内容不多,但很全面。遇到不懂的问题,先看文档肯定没错。

这里需要注意的是,百度搜索phantomJS得到的结果只是phantomJS的官方文档,而phantomJS是一个独立的无界面浏览器,也称JS模拟器,本来就独立于python。我们需要的是phantomJS的python接口,也就是通过python调用phantomJS,所以只需查看selenium的webdriver文档。

2. phantomJS的配置问题

selenium官方文档中,phantomJS对象的帮助文档很详细,但当涉及phantomJS浏览器的配置,比如user-agent伪装、代理、超时返回等选项时,有用的信息就非常少了。结合网上的资料和自己遇到的各种坑,我总结了常用的phantomJS配置选项,对普通的爬虫来说,应该够用了。

from selenium import webdriver
# 引入配置对象DesiredCapabilities
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
dcap = dict(DesiredCapabilities.PHANTOMJS)
#从USER_AGENTS列表中随机选一个浏览器头,伪装浏览器
dcap["phantomjs.page.settings.userAgent"] = (random.choice(USER_AGENTS))
# 不载入图片,爬页面速度会快很多
dcap["phantomjs.page.settings.loadImages"] = False
# 设置代理
service_args = ['--proxy=127.0.0.1:9999','--proxy-type=socks5']
#打开带配置信息的phantomJS浏览器
driver = webdriver.PhantomJS(phantomjs_driver_path, desired_capabilities=dcap,service_args=service_args)                
# 隐式等待5秒,可以自己调节
driver.implicitly_wait(5)
# 设置10秒页面超时返回,类似于requests.get()的timeout选项,driver.get()没有timeout选项
# 以前遇到过driver.get(url)一直不返回,但也不报错的问题,这时程序会卡住,设置超时选项能解决这个问题。
driver.set_page_load_timeout(10)
# 设置10秒脚本超时时间
driver.set_script_timeout(10)

phantomJS的并发问题

phantomJS爬数据比较慢,并发编程几乎是必选项。最初,我考虑采用多线程/协程的方式,毕竟对于这种IO密集型的程序,多线程/协程比较合适。但多次测试下来,程序却遇到各种问题,有时能成功运行,有时却不能。尝试将phantomJS改成Chrome,程序居然能正常运行,这基本确定是phantomJS的锅了。所以,如果需要并发编程提高效率,用Chrome比较好,虽然内存占用相对较多,况且经下面简友提醒,在没界面的主机上也可以跑Chrome,那自然更好了。

在网上仔细查找了相关资料(这玩意的中文资料极少,只能去国外技术论坛潜水),原来phantomJS本身在多线程方面还有很多bug,建议使用多进程,具体什么原因有时间再去了解。

关于多进程,推荐使用multiprocessing库,简洁、高效!下面几行代码便实现了多进程并发。

from multiprocessing import Pool
pool = Pool(8)
data_list = pool.map(get, url_list)
pool.close()
pool.join()

phantomJS进程不自动退出问题

try:    
    self.driver.get(url)    
    self.wait_()    
    return Trueexcept 
Exception as e:   
    return False

程序的逻辑是:如果在打开url的过程中报错,那么就返回False,反之返回True。

似乎直接return False的处理太粗心了。我尝试着在return False前加上一行self.driver.quit()。再次运行程序,并用top查看内存使用情况,发现程序的内存使用一直都在正常范围内,并没有出现多个phantomJS进程的情况,问题搞定!后面在网上找到的资料也证实了我的猜想:主程序退出后,selenium不保证phantomJS也成功退出,最好手动关闭phantomJS进程。

不同frame间的转换

有时,phantomJS获得的页面源码的确存在某元素,但通过find_element_by_xpath()等定位函数却无法获得该元素对象,总是提示“元素不存在”的错误。遇到这种情况,除了检查元素节点路径是否正确外,还应该分析页面源码,检查元素是否被包裹在一个特定的frame中,如果是后者,那么在使用查找函数前,需要额外的处理。

比如网页源码中有如下代码:

<iframe id="topmenuFrame" width="100%" scrolling="no" height="100%" src="topmenu.aspx?>
<div id="haha">text</div></iframe>

假如你想要获取id="haha"的div标签,直接通过driver.find_element_by_id('haha')就会提示“元素不存在“的错误。

这时需要使用driver.switch_to_frame(driver.find_element_by_id``("topmenuFrame")),即先进入id为topmenuFrame的frame,然后再执行driver.find_element_by_id("haha"),就能正确获得该元素了。

需要注意的是,切换到这个frame之后,只能访问当前frame的内容,如果想要回到默认的内容范围,相当于默认的frame,还需要使用driver.switch_to_default_content()。

页面中有多个frame时,要注意frame之间的切换。

implicit_wait、WebDriverWait不一定靠谱

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!