Selenium浅析

Selenium 设计的目的是为了自动化的操作浏览器,完成自动化 Web 测试任务。当然,Selenium 的能力不止如此,所有浏览器自动化的工作都可以考虑使用 Selenium 完成。Selenium 受大部分浏览器厂家支持如 chrome、firefox,并且在一些平台成为浏览器原生的一部分。Selenium 分为 WebDriver 和 IDE 两部分,如果想创建自动化或测试任务你需要使用 WebDriver,如果目的要记录某个 feature 或者 bug 的重现路径那么需要使用 IDE,本文主要讲述 WebDriver。

首先 Selenium 从实现原理上可以看做是一个驱动程序,为不同厂商封装了统一接口,接口下调用了不同厂商的浏览器实现,从适配器模式的角度理解

适配器模式适配器模式

Selenium 为 adptor,chromedriver 即为其中的一个 Adaptee, 比较值得一提的是 PhantomJS,PhantomJS 是一个没有 GUI 的浏览器,能够运行 JavaScript 脚本。同时 Selenium 支持 PhantomJS 做为 Adaptee 前端,那么我们便可以结合 PhantomJS 与 Selenium 模拟抓取一些 js 代码比较多的网页,为了直观效果,本文使用 Chrome 作为前端。Selenium 支持多种开发语言,如 Java、C、Ruby 等,下文以 python 为例介绍 Selenium 的一些简单用法。

Selenium 通过 chromedriver 支持对 chrome 的操作,下载 chromedriver 放到执行 PATH 路径中然后通过 pip 安装 Selenium 即可使用。

pip install selenium

Selenium 是模拟测试工具,在开发基于 Selenium 的 Task 时应该遵循模拟用户的操作这一原则。比如在抓取网络数据时,通常的思路是通过嗅探 header 字段,构造 http 请求并解析返回结果,但是在 Selenium 中需要模拟的是用户的输入、下滑、翻页等操作,然后通过 driver 的 page_source 拿到网页源码然后解析结果。

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()
driver.get("http://baidu.com")
print(driver.title)

elem = driver.find_element_by_name("kw")
elem.send_keys("heheda")
elem.send_keys(Keys.RETURN)
print driver.page_source

driver.find_element_by_id('su').click()

driver.get 方法会在页面的 onload 事件触发时立即返回,也就是说如果页面中有很多 ajax 请求的话,driver.get 方法返回时可能页面还没有加载完成。driver 还提供了一些 find_element_by_* 方法方便定位到特定的 DOM 节点进行后续操作。同时 selenium.webdriver.common.keys 模块中提供了一些特定的键值来模拟用户输入操作。driver.quit() 与 driver.close() 提供了关闭浏览器或者浏览器标签的方法。

Selenium 提供了简单的设置或者获取 Cookie 的方法:

# Go to the correct domain
driver.get("http://www.example.com")

# Now set the cookie. This one's valid for the entire domain
cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’}
driver.add_cookie(cookie)

//获取 Cookie
# Go to the correct domain
driver.get("http://www.example.com")

# And now output all the available cookies for the current URL
driver.get_cookies()

更多 Selenium 相关 API 请阅读文档 http://selenium-python.readthedocs.io/

页面等待

刚才说到 driver.get 方法会在 onload 时立即返回,对于大量使用 ajax 的页面我们可能无法正确获取到目标元素,或者一些页面设置了延时加载策略,针对这种情况我们需要设置一个合理的等待策略,在等待一段时间后再去获取页面元素以避免 ElementNotVisibleException。Selenium 提供了两种不同的等待策略:显式隐式

显式策略是指明确的指定一个等待时间,如果在指定时间到达时不能顺利获取元素则抛出异常,其本质就是简单的 time.sleep(),Selenium 为这一情况封装了更为便捷的语法:

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

上述代码会抛出 TimeoutException 如果在 10 秒内没有找到相关元素。Selenium 会每格 500 毫秒检测一次 try 中的代码,如果能够顺利获取结果则正常返回。同时 Selenium 已经封装了大多数事件如下,用户不需要重写规则

  • title_is
  • title_contains
  • presence_of_element_located
  • visibility_of_element_located
  • visibility_of
  • presence_of_all_elements_located
  • text_to_be_present_in_element
  • text_to_be_present_in_element_value
  • frame_to_be_available_and_switch_to_it
  • invisibility_of_element_located
  • element_to_be_clickable
  • staleness_of
  • element_to_be_selected
  • element_located_to_be_selected
  • element_selection_state_to_be
  • element_located_selection_state_to_be
  • alert_is_present

隐式策略则是设置一个固定的等待时间(默认为0),如果在这个时间之内找到元素则返回,如果到时候仍未找到则抛出异常,这个固定的等待时间对 driver 全局有效。

from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")

截图

使用 save_screenshot 方法保存网页截图

from selenium import webdriver

driver = webdriver.Chrome()
driver.get("http://baidu.com")

driver.set_window_size(900, 600)
driver.save_screenshot('1.png')

为获取特定元素的截图,可以先从 driver 中获取到该元素,然后取得元素的位置信息,通过图片处理工具截取得到:

element = driver.find_element_by_*

left = element.location['x']
top = element.location['y']
right = left + element.size['width']
bottom = top + element.size['height']

im = Image.open('screenshot.png')
im_element = im.crop((left, top, right, bottom))
im_element.save('element.png')

通过上述代码可以轻松的获取到页面中的验证码,二维码等一些结构信息便于后续使用。但是在 driver 的 save_screenshot 中,不同的前端处理方法不同,PhantomJS 可以直接截取整个屏幕的长图,但是 Chrome 只能截取到当前屏幕内容。另外对于一些网页内图片的延时加载问题,可以通过注入一段 js 让 driver 翻滚到在下方的方法加载到。

想要学习更多 Selenium 知识可以参考 https://www.guru99.com/selenium-tutorial.html