selenium+PhantomJS使用总结

在 Sun 25 September 2016 发布于 爬虫 分类

selenium 是一套web自动化测试工具,其可以模拟真实的浏览器,执行JavaScript等功能。正因为如此,其功能的丰富和强大,让它不单单是个一个web自动化的测试工具。本篇文章主要讲的是其在爬虫抓取上的应用,不会涉及的自动化web测试相关的东西,对于复杂的动态页面,普通的正常抓取只是抓取到其源码页面,真正的内容可能是通过JavaScript,ajax等动态加载,使用selenium来模拟正常的浏览器来帮助进行抓取是一种很有效的方法。selenium支持不同的WebDriver,包括:google chrome ,firefox, IE, PhantomJs等。Python的selenium库提供了一系列简单易用的API, 可以让我们通过代码逻辑模拟点击,拖动,翻页等效果。

对于Pythonselenium模块的安装很简单,直接pip install selenium就可以搞定。对于webDriver,可以根据自己的喜好来进行选着,我目前使用的google chromePhantomJS。在下载完webDriver后可以直接放入到/usr/bin/或者/bin目录下,那样在启动webDriver的时候就不需要指定路径了,如果放入别的路径的话,那就需要在启动的时候指定路径了。google chromewebDriver下载地址, PhantomJS的可以直接在官网上下载已经编译过的,源码下载编译太蛋疼了。

selenium webDriver提供了相当灵活和设置,可以伪造User_Agent和各种头信息,如: 还有一些设置能够让webDriver运行起来更具有效率,如:

启动一个webDriver实例非常简单

  browser = webdriver.PhantomJS(executable_path='/path/to/PhantomJS')

executable_path这个参数为PhantomJS的路径,当然还有其他参数,根据[API文档]中的介绍,其有一个参数desired_capabilities,其类型为selenium.webdriver.common.desired_capabilities.DesiredCapabilities。 其主要是为webDriver提供灵活的设置,伪造User_Agent和各种头信息。

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
dcap = DesiredCapabilities.PHANTOMJS.copy()
dcap['phantomjs.page.settings.resourceTimeout']  =5
dcap['phantomjs.page.settings.loadImages'] = False
dcap['phantomjs.page.settings.userAgent'] = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
dcap['phantomjs.page.customHeaders.Referer'] = 'https://s.1688.com'

另一个比较有用的参数就是service_args, 可以通过其来设置phantomJS进行参数设置,具体有哪些参数,可以在命令行下执行phantomjs --help便可了解,最长用的就是对proxy的设置了。

当一切设置完毕后,就可以browser.get(targeturl)来进行抓取了,需要注意的是browser.get(targeturl)函数是阻塞的,即browser会等整个页面全部加载完成后才会返回,然后程序逻辑才能往下进行,但是,动态页面大多数都使用了Ajax的技术,有时你会发现browser.get(targeturl)会阻塞相当长的一段时间,严重影响效率, 可以设置超时时间。 这个是显示的设置

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

但是,browser.get(targeturl)很快执行完成,这里有个陷阱,页面还是在loading其他东西,一般保险的做法就是等上一段时间。

获取HTML中的标签元素以及提取信息

WebDriver提供了一系列类似于find_element_by_*的函数来获取单一特定的selenium.webdriver.remote.webelement.WebElement对象实例,如find_element_by_id, find_element_by_class_name, find_element_by_xpath, find_element_by_css_selectors等, 也可以使用其提供的find_elements_by_*的函数来获取一系列selenium.webdriver.remote.webelement.WebElement的对象实例的集合,然后可以通过其成员函数元素的属性。 需要注意的在同一个窗口,是不论是在页面切换后,然后切换回来,之前获取的WebElement元素对象实例都失效了,需要重新获取。 当然, 对于提取内容,最方便的方法就是使用scrapy的Selector模块来提取内容,或者直接使用parsel(scrapySelector就是封装了该模块)模块来进行内容的提取,并且通过方法browser.page_source获取的直接就是unicode编码格式的网页源码了, 但这种方法只能提取静态的页面元素,如果是通过js动态生成的页面元素,就提取不了了。 也可以通过browser.execuate_script()来执行JavaScript脚本来提取页面信息,个人觉得不怎么高效,不到万不得已的时候很少用到。

模拟点击,拖拽,输入等动作

在抓取的时候,通常需要进行login模拟,但是login的过程避免不了输入,点击,拖拽等过程, WebElement提供了以下简单的事件方法:

方法 描述
click 简单的点击事件,可以是一个提交按钮,或者一个超链接,只要页面元素可以进行点击事件的触发
send_keys 可以模拟输入,当参数时Keys.ENTER的时候类似于click事件
submit 模拟提交表单事件

但是对于复杂的认证,比如淘宝的登陆有时会在你输入了用户名和密码完后会出现了一个滑块,让你进行拖拽,并刷新出验证码(这个验证码可以使用打码平台来识别),显然上面的模拟方法肯定完成不了拖拽动作,好在ActionChains对象来模拟一系列复杂的动作:

描述
click 模拟点击
click_and_hold 模拟左键点击页面元素, 并持续保持按下动作
context_click 模拟右键点击,出菜单选项
double_click 模拟双点击
drag_and_drop 模拟拖拽动作, 把元素移动到目标元素位置, 然后释放
drag_and_drop_by_offset 和drag_and_drop效果一样,但是移动到相对坐标上
key_up/key_down/send_keys 这三个方法经常一块用,模拟组合键,key_down和key_up的参数模拟ShiftCtrlAlt,send_keys的参数为枚举Keys或者键盘上的字符
move_by_offset 模拟移动鼠标到当前位置的相对位置
move_to_element 模拟移动鼠标到摸个元素的中央
move_to_element_with_offset
perform 执行所有的事件链
release 模拟释放鼠标按下的动作

可以组合以上这些模拟动作的方法来达到目的,如:

action_chains = ActionChains(browser)
#模拟拖拽
action_chains.click_and_hold(browser.find_element_by_css_selector('')).move_by_offset(x, y).release().perform()

模拟返回上一个历史记录和下一个历史记录:

  browser.forward()
  browser.back()

窗口之间的的切换

在模拟点击超链接的时候,多数的时候是在点击超链接进入下一级url,需要注意超链接的属性target,其有以下几个值,对应的动作也不同:

描述
_blank 在新窗口中打开
_self 默认。在相同的框架中打开
_parent 在父框架中打开
_top 在整个窗口中打开

在日常的工作中,_parent_top在和页面的frame有关, _blank_self的居多,会涉及到的多窗口切换的问题,selenium webdriver模拟的每个窗口有唯一的标示,可以通过属性current_window_handle来获取当前窗口的唯一表示,如果是在当前窗口打开链接,需要注意的,当点击完成后(打开新的窗口, 在同一个窗口打开没有影响),此时current_window_hanle的值还是之前之前窗口的值,即窗口的焦点没有变化,如果需要操作新的窗口,可以使用遍历窗口的方法,如:

main_window_handle = browser.current_window_handle
#点击超链接
browser.find_element_by_xpath('a').click()
all_windows_handles = browser.window_handles
for window in all_windows_handles:
    if window == main_window_handle:
        continue
    else:
        #切换到新打开的窗口
        browser.switch_to_window(window)
        break
#do the work

同窗口下的frame之间的切换,可以使用browser.switch_to_frame(targetWindowHandle)成员函数来进行模拟。 另外,也可以直接执行javascript脚本在新窗口打开超链接,如:

    browser.execute_open('window.open(\'' + target_url + '\')')