• 盘点selenium phantomJS使用的坑


    转载自简书

    说到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使用小结,虽然不是很全面,也不确保完全准确,算是对我这段学习历程的总结吧,希望对大家有用。

  • 相关阅读:
    C# 图片处理(添加水印 & 原尺寸压缩 & 等比缩放 & 创建缩略图)
    Quill中文文档及基本使用
    2015年 阿里2道前端笔试题+堆糖2道前端笔试题
    Guava Ordering , table BiMap Multimap RangeMap (Map的骚操作)
    9条消除if...else的锦囊妙计,助你写出更优雅的代码 (转) 主要是设计模式
    Docker容器性能监控工具google/cadvisor
    双重检查锁的漏洞 volatile 指令进行重排
    java使用AES加密解密 AES128ECB加密
    Hutool——国产良心工具包, 加密,身份证处理
    浏览器解析工具UserAgentUtils
  • 原文地址:https://www.cnblogs.com/fonttian/p/9162840.html
Copyright © 2020-2023  润新知