美文网首页python爬虫python爱好者Python
盘点selenium phantomJS使用的坑

盘点selenium phantomJS使用的坑

作者: Rabin_xie | 来源:发表于2016-11-29 08:23 被阅读31711次

    说到python爬虫,刚开始主要用urllib库,虽然接口比较繁琐,但也能实现基本功能。等见识了requests库的威力后,便放弃urllib库,并且也不打算回去了。但对一些动态加载的网站,经常要先分析请求,再用requests模拟,比较麻烦。直到遇到了selenium库,才发现爬动态网页也可以这么简单,果断入坑!

    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基本用法的同学,请移步官方文档

    个人用phantomJS爬数据有一段时间了,爬虫程序也大致完工了,过程中遇到了很多坑,统一总结如下。

    1. 查看phantomJS文档

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

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

    当然,官方文档很全面,但也相对繁杂。python有个查看文档的小技巧,直接使用help()就能查看某个对象的帮助文档。比如help(driver)即可直接查看driver这个对象的文档,包括其内部函数、变量的说明。如果driver是一个phantomJS对象,那么会显示phantomJS浏览器对象的函数和变量的文档,具体内容和官方文档一样。对所有的python对象都可以这样干,非常便捷。似乎现在各种IDE也有这个功能:当鼠标悬停在某个对象上,就显示该对象的帮助文档。不过多掌握个方法总归没错。

    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)
    

    3. 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()
    

    4. phantomJS进程不自动退出问题

    话说,一开始我写好程序后,先在本地测试了一段时间,确认程序各方面都没问题后,直接扔阿里云主机上跑了。过了一段时间,查了下程序运行日志,很好,一切如常。于是我就高高兴兴地摸鱼去了。

    第二天准备登录主机验收程序时,却发现居然无法登录!啥,无法登录?难不成这个小爬虫程序还能把主机搞崩?我先在阿里云后台查看了主机的运行日志,发现主机的内存使用越来越高,应该是内存耗尽后,强制关机了。似乎是程序没有回收内存,导致占用的内存越来越大。明确大致原因后,就是痛苦的查bug过程了。

    由于bug涉及内存的使用,我自然地想到了用top命令查看进程的内存使用情况。先运行程序,然后运行top命令,实时检测程序的内存使用情况。一开始程序占用内存在正常范围,只有一个phantomJS进程在运行,似乎没有什么不对。但随着时间的增长,内存中居然同时有好几个phantomJS进程在运行,内存所剩空间越来越小!但根据程序的逻辑,任何时候都只有一个phantomJS进程在爬数据。我意识到可能是由于phantomJS进程没有正常关闭,所以在内存中驻留的phantomJS进程越来越多,最终吃光了内存。

    带着这个问题,我重新检查了一次代码,尤其在程序异常退出的地方。最终找到了类似下面的代码:

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

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

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

    5. 其他问题

    5.1 不同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之间的切换。

    5.2 implicit_wait、WebDriverWait不一定靠谱

    宿舍哥们用phantomJS爬数据时,遇到了一个匪夷所思的bug。起初,他写了个很简单的程序,从个方面来看都没问题,但实际运行却提示各种错误,让人十分费解。折腾大半天之后,他直接注释掉自己不太熟悉的implicit_wait(),改用time.sleep()作延时,程序居然就能正确运行了!原来implicit_wait()有bug。同样的,对于WebDriverWait,大家使用时也要特别注意。

    看来python的selenium库不是很成熟,还存在一些问题,一些函数的实际运行情况并不是预期的那样,在查bug时,要留意这些问题。

    6. 总结

    总的来说,selenium库简单,容易上手,是爬动态网页的杀手级武器,但对phantomJS浏览器的支持还不是特别完善。当然,了解存在的问题,并找到对应的解决方法,就能发挥phantomJS的威力了。

    以上就是我个人这段时间的phantomJS使用小结,虽然不是很全面,也不确保完全准确,算是对我这段学习历程的总结吧,希望对大家有用。

    相关文章

      网友评论

      • c600a39f6558:使用PhantomjsDriver 时执行action.clickAndHold(eles.get(moveNum[0])).moveToElement(eles.get(moveNum[1])).moveToElement(eles.get(moveNum[2])).moveToElement(eles.get(moveNum[3])).click().perform();发现第一个点没有点中是怎么回事,用firfoxDriver没问题。
      • 5f4ebec330b7:太棒了!感谢启发!
      • 2450b76b22e3:你好,今天使用代理发现个问题,如果使用http代理是没问题的,但是一旦使用https 的代理出出错了,确认代理都可用,请问楼主遇到这个问题吗,本人使用的代理是西刺
      • wyb2017:可以的,WebDriverWait(browser, 10).until的确有问题,很多时候数据加载不出来
        9dbf62bf4b66:@自封铲屎官 你好请教下用的什么方式等待页面加载的不会是time.sleep()吧
        d97747026692:是的,我用别的方式都可以取到,但是这个就是不行
      • lh12565:虽然PhantomJS是无界面的,但在我电脑上为什么Chrome比PhantomJS快很多?
        Voyage:你chrome开着吗?全部关闭试试,光打开chrome就需要消耗不少时间
      • mrlevo520:不错,刚好ESC遇到同样的问题,估计就是这个原因了,多谢
      • d0d1d783ee20:USER_AGENTS
        你好,请问为啥pychorm运行报错报:name 'USER_AGENTS' is not defined
        d0d1d783ee20:@哈雷路亚_7dae 好了就是自己定义的定义完了!
        d0d1d783ee20:这个USER_AGENTS列表是自带的吗?还是得pip
      • 小幸运Q:弱弱的问一句......如果想在浏览器启动了之后切换UA的话该怎么办啊?😔
        小幸运Q: @小明的简书之路 这个我也看过,但并没有换ua啊(,,•́ . •̀,,)
        5662011a5cf9:在知乎上看到的,希望对你有帮助。

        # 不使用代理代打开ip138
        browser=webdriver.PhantomJS(PATH_PHANTOMJS)
        browser.get('http://1212.ip138.com/ic.asp')
        print('1: ',browser.session_id)
        print('2: ',browser.page_source)
        print('3: ',browser.get_cookies())

        # 利用DesiredCapabilities(代理设置)参数值,重新打开一个sessionId,我看意思就相当于浏览器清空缓存后,加上代理重新访问一次url
        proxy=webdriver.Proxy()
        proxy.proxy_type=ProxyType.MANUAL
        proxy.http_proxy='1.9.171.51:800'
        # 将代理设置添加到webdriver.DesiredCapabilities.PHANTOMJS中
        proxy.add_to_capabilities(webdriver.DesiredCapabilities.PHANTOMJS)
        browser.start_session(webdriver.DesiredCapabilities.PHANTOMJS)
        browser.get('http://1212.ip138.com/ic.asp')
        print('1: ',browser.session_id)
        print('2: ',browser.page_source)
        print('3: ',browser.get_cookies())

        # 还原为系统代理
        proxy=webdriver.Proxy()
        proxy.proxy_type=ProxyType.DIRECT
        proxy.add_to_capabilities(webdriver.DesiredCapabilities.PHANTOMJS)
        browser.start_session(webdriver.DesiredCapabilities.PHANTOMJS)
        browser.get('http://1212.ip138.com/ic.asp')
      • 交易狗二哈:楼主 能问下 运行时phantomjs时的弹窗能关掉么,黑窗口那个
        打开多个网页弹出那么多个挺不舒服的0.0
        交易狗二哈:@Rabin_xie 额, 是跳出 phantomjs exe 这个黑窗口
        交易狗二哈:@Rabin_xie 是这样么:joy: ,我每次都会弹出一个cmd窗口
        Rabin_xie: @股锅 在python程序里面调用phantomjs不用打开黑窗口的
      • dc82fb6915ad:楼主 能否加一下我的QQ 有些问题我想请教一下。。有偿 531711213
      • 踏云小子:内容很干货啊,想请教下楼主是如何在centOS安装phantomJS的
        戏之地:@Will_CodeFarmer 嗯嗯,加油,有什么问题可以交流,一块进步
        踏云小子:@jatrix 已经解决了,谢谢
        戏之地:下载,然后解压。。。貌似有点笼统。。。。
      • zjybjtu:selenium chrome可以在开发机上跑,界面可以隐藏的。
        挨木批三:@zjybjtu 您好,能不能分享下如何实现呢,我在mac上提示 Xvbf的错误,但是没有找到如何在mac上安装xvbf。然后我在linux下安装了全部依赖,也指定了chromdriver的位置,但是运行代码依然提示找不到二进制文件。。。。目前处于崩溃状态。。。求助。。。:dizzy_face:
        Rabin_xie:@zjybjtu 可以可以,回去试试!
        zjybjtu:@zjybjtu
        from pyvirtualdisplay import Display
        display = Display(visible=0, size=(800,800))
        display.start()
      • adminlzzs:不错,希望再出多点关于phantomjs的总结

      本文标题:盘点selenium phantomJS使用的坑

      本文链接:https://www.haomeiwen.com/subject/tudapttx.html