Flintx

自动化测试工具 Selenium Python 使用浅析

前不久做的项目里有个很坑的事,项目要求通过远程摄像头来实时获取景区的客流量信息,一开始是用海康威视提供的SDK在做,孰料用SDK提供的远程登陆接口始终登陆不上去,错误代码为访问超时。不仅如此,尝试了所有海康提供的所有demo与官方工具iVMS-4200,又关闭了防火墙,这个问题依然无解。

但奇怪的是,浏览器可以访问摄像头远程管理页面,通过tcping工具也能ping通。万般无奈之下,只好用爬虫访问远程管理页来抓取实时客流信息。

在写爬虫的过程中接触了三个比较有趣的东西:用于自动化测试的selenium、用于读取配置文件的configParser、和Windows定时任务。这两天相对有时间,准备把学习过程中的一些东西记录下来。

这篇文章主要讲selenium。

Selenium是一个用来做自动化测试的工具,支持包括 Chrome,Safari,Firefox 等主流浏览器,并且能用包括Java、C#、Python、Ruby、PHP、Perl、JavaScript等多种语言进行开发。这里我们选取Python与Chrome来进行下一步的学习。

Selenium能够模拟人工的多种操作,例如点击事件、回车键触发、文本输入、表单填充、复选框切换等。利用Selenium可以将较为复杂琐碎的手动操作实现自动化。

Selenium官网的使用手册:Selenium with Python

环境搭建

  1. 安装selenium,可以通过pip或下载源码来安装

    1
    2
    # pip 方法
    pip install selenium
    1
    2
    # 源码安装
    python setup.py install

  2. 下载浏览器驱动。selenium需要安装相应的driver来驱动真实浏览器,常见浏览器的driver下载地址如下,Chrome driver版本与内核版本对应关系参考这里,下载后将其解压到PATH变量中的路径即可。(Linux放置在 /usr/bin/usr/local/bin/ 下)

  1. 使用测试

    1
    2
    3
    4
    from selenium import webdriver
    # browser = webdriver.Chrome(chrome_driver_path) 通过参数来指定driver地址也可以
    browser = webdriver.Chrome()
    browser.get("http://www.baidu.com/")

使用浅析

find-查找页面元素

selenium提供了多种多样的查询元素的方式,而这些方式均有 find_element_by_xxx()find_elements_by_xxx() 两个版本 :

  • find_element_by_id() / find_elements_by_id() : 根据元素id来查找指定元素
  • find_element_by_name() / find_elements_by_name() : 根据元素name来查找指定元素
  • find_element_by_class_name() / find_elements_by_class_name() : 根据元素类名来查找指定元素
  • find_element_by_tag_name() / find_elements_by_tag_name() : 根据标签名来查找指定元素
  • find_element_by_link_text() / find_elements_by_link_text() : 根据链接文本来查找指定元素
  • find_element_by_partial_link_text() / find_elements_by_partial_link_text() : 根据部分链接文本来查找指定元素
  • find_element_by_xpath() / find_elements_by_xpath() : 根据xpath来查找指定元素,关于xpath的资料可参考:XPath 语法
  • find_element_by_css_selector() / find_elements_by_css_selector() : 查找CSS 选择器来查找元素

一个元素的xpath或css selector,可以使用开发者工具来获取:

使用参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
def test_selenium():
browser = webdriver.Chrome()
browser.get("http://www.baidu.com")
# by id, 等价于 elem_qrcode_img = browser.find_element(By.CLASS_NAME, "qrcode-img")
elem_img_qrcode = browser.find_element_by_class_name("qrcode-img")
print(elem_img_qrcode.size)
# by name, 等价于 elem_news_a = browser.find_element(By.NAME, "tj_trnews")
elem_a_news = browser.find_element_by_name("tj_trnews")
print(elem_a_news.text)
# by class name, 等价于 elem_qrcode_img = browser.find_element(By.ID, "su")
elem_baidu_button = browser.find_element_by_id("su")
# by tag name, 等价于 elem_p = browser.find_elements(By.TAG_NAME, "p")
elem_p_list = browser.find_elements_by_tag_name("p")
elem_p_text_list = list(map(lambda x: x.text, elem_p_list))
print(elem_p_text_list)
# 通过链接文本内容, 等价于 elem_tieba_a = browser.find_element(By.LINK_TEXT, "贴吧")
elem_a_tieba = browser.find_element_by_link_text("贴吧")
print(elem_a_tieba.get_property("href"))
# 通过部分链接文本内容, 等价于 elem_hao123_a = browser.find_element(By.PARTIAL_LINK_TEXT, "hao")
elem_a_hao123 = browser.find_element_by_partial_link_text("hao")
print(elem_a_hao123.text + ": " + elem_a_hao123.get_property("href"))
# by XPath or CSS Selector
elem_input_kw = browser.find_element_by_xpath("//*[@id='kw']")
elem_input_kw = browser.find_element_by_css_selector("#kw")
elem_input_kw.send_keys("bilibili")
elem_baidu_button.click()
browser.close() # 关闭tab
browser.quit() # 退出浏览器
if __name__ == '__main__':
test_selenium()

get-获取页面信息

页面信息可以通过浏览器 driver 对象与元素 element 对象来获取。

1
2
driver = webdriver.Chrome()
element = driver.find_element(By.xxx, "xxx")

WebElement

  • WebElement.id : 获取当前元素的ID

  • WebElement.text : 获取当前元素的内容

  • WebElement.tag_name : 获取当前元素标签名

  • WebElement.get_attribute(name) : 获得当前元素特性值

  • WebElement.get_property(name) : 获得当前元素属性值,关于 attribute 与 property 的区别可参考:详解JavaScript中attribute和property的区别以及最佳实践 ,在js中推荐使用 property 方法,但由于 selenium 库中 get_attribute() 方法中首先是执行 get_property() 方法,若 property 不存在则寻找 attribute , get_attribute() 方法源码注释为:

    This method will first try to return the value of a property with the
    given name. If a property with that name doesn’t exist, it returns the
    value of the attribute with the same name. If there’s no attribute with
    that name, None is returned.

    Values which are considered truthy, that is equals “true” or “false”,
    are returned as booleans. All other non-None values are returned
    as strings. For attributes or properties which do not exist, None
    is returned.

    所以这里推荐使用 get_attribute() 方法。

  • WebElement.is_displayed() : 判断当前元素是否可见

  • WebElement.is_enabled() : 判断当前元素是否被启用

  • WebElement.is_selected() : 判断当前元素是否被选中

  • WebElement.size : 获取当前元素尺寸

  • WebElement.location : 获取当前元素坐标,可以通过 WebElement.rect 来同时获取 sizelocation

WebDriver

  • WebDriver.current_url : 获取当前链接
  • WebDriver.title : 获取当前标题
  • WebDriver.page_source : 获取页面源码

operation-页面操作

基本操作

  • .clear() : 清除可编辑元素的内容
  • .send_keys(txt) : 在可编辑元素元素上模拟按键输入
  • .submit() : 提交表单,要求提交对象是一个表单

以上三个操作只能用于可编辑的元素或表单。

  • .click() : 单击元素,可用于任何可以点击的元素

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
elem_input_kw = driver.find_element_by_id("kw")
elem_input_kw.send_keys("bilibili")
# 三种方法是同一效果
# 1.
# elem_input_kw.send_keys(Keys.RETURN)
# 2.
# elem_input_kw.submit()
# 3.
driver.find_element_by_id("su").click()
time.sleep(3)
driver.save_screenshot("E:\\screen1.png")
elem_input_kw = driver.find_element_by_id("kw")
elem_input_kw.clear()
driver.save_screenshot("E:\\screen2.png")

等待操作

由于目前大部分网页都不是纯静态的,都用到了大量Ajax这一类的异步动态加载技术,所以可能由于受限于网络速率、稳定性等问题,导致找不到需要的 WebElement(实际上是未加载完毕)。selenium为我们提供了两种等待页面加载的操作方法:显式(Explicit)等待与隐式(Implicit)等待。

显式等待

所谓显式等待,就是指预先定义好等待条件,待条件满足后再开始执行。

看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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_existed = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "myDynamicElement"))
)
finally:
driver.quit()

这段代码中,我们实例化了一个等待类 WebDriverWait,并传入了两个参数:当前浏览器 driver 与等待时间 10 (秒)。然后调用 until 方法,方法参数为等待条件,即id=”myDynamicElement”的元素是否存在。运行这个 until 方法,方法将会在满足等待条件后返回一个True,否则当等待时间到将抛出 TimeoutException 异常。

同理,有 until 方法,也有 until_not 方法。这两个方法的参数包括:

  • timeout : 等待时间,单位为秒
  • poll_frequency : 调用判定等待条件方法的时间间隔,单位为秒,默认为0.5s
  • ignored_exceptions : 忽略的异常,如果在调用until或until_not的过程中抛出这个元组中的异常,则不中断代码,继续等待,如果抛出的是这个元组外的异常,则中断代码,抛出异常。默认只有NoSuchElementException。

而 selenium 的 expected_conditions 模块为我们提供了一系列构造等待条件的方法,其中包括:

  • title_is : 验证 driver 的 title 是否与传入的 title 一致
  • title_contains : 验证 driver 的 title 中是否包含传入的 title
  • presence_of_element_located : 验证页面中是否存在传入的元素,传入元素的格式是 locator 元组,如 (By.ID, "id1")
  • visibility_of_element_located : 验证页面中传入的元素( locator 元组格式 )是否可见,这里的可见不仅仅是 display 属性非 None ,还意味着宽高均大于0
  • visibility_of : 验证页面中传入的元素( WebElement 格式 )是否可见。
  • presence_of_all_elements_located : 验证页面中是否存在传入的所有元素,传入元素的格式是 locator 元组构成的 list,如 [(By.ID, "id1"), (By.NAME, "name1")
  • text_to_be_present_in_element : 验证在指定页面元素的text中是否包含传入的文本
  • text_to_be_present_in_element_value : 验证在指定页面元素的value中是否包含传入的文本
  • frame_to_be_available_and_switch_to_it : 验证frame是否可切入,传入 locator 元组 或 WebElement
  • invisibility_of_element_located : 验证页面中传入的元素( locator 元组格式 )是否可见
  • element_to_be_clickable : 验证页面中传入的元素( WebElement 格式 )是否可见。
  • staleness_of : 判断传入元素(WebElement 格式)是否仍在DOM中
  • element_to_be_selected : 判断传入元素(WebElement 格式)是否被选中
  • element_located_to_be_selected : 判断传入元素(locator 元组格式)是否被选中
  • element_selection_state_to_be : 验证传入的可选择元素(WebElement 格式)是否处于某传入状态
  • element_located_selection_state_to_be : 验证传入的可选择元素(WebElement 格式)是否处于某传入状态
  • alert_is_present : 验证是否有 alert 出现。

更多API可参考官方文档

隐式等待

所谓隐式等待,就是 time.sleep(seconds) 。不过selenium还是为其提供了标准方法:

1
2
3
4
5
6
from selenium import webdriver
driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")

鼠标操作

ActionChains

示例:

1
2
3
4
5
6
from selenium.webdriver import ActionChains
element = driver.find_element_by_name("source")
target = driver.find_element_by_name("target")
action_chains = ActionChains(driver)
action_chains.drag_and_drop(element, target).perform()

键盘/表单操作

Keys

Select

示例:

1
2
3
4
5
6
7
8
9
10
from selenium.webdriver.support.ui import Select
select = Select(driver.find_element_by_id('my_id'))
select.select_by_index(index) # 根据索引值
select.select_by_visible_text("text") # 根据选项文本
select.select_by_value(value) # 根据选项值
select.deselect_all() # 全部取消选择/重置
all_selected_options = select.all_selected_options # 获取所有已选项
options = select.options # 获取所有可选项

窗口/切换操作

  • driver.switch_to_window(window_name) : 切换到指定的window,window_name 可以通过 driver.window_handles 来获取:
1
2
3
4
5
6
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
js = "window.open('http://flintx.me')"
driver.execute_script(js)
driver.switch_to_window(driver.window_handles[0])

  • driver.switch_to_frame(frame_name) : 切换到指定的frame

  • driver.switch_to_alert() : 切换到当前弹出的alert

  • driver.forward() : 前进

  • driver.back() : 后退

  • driver.maximize_window() : 窗口最大化

Cookies处理

  • 添加 Cookies : add_cookie(cookie_dict)
  • 获取 Cookies : get_cookie(name) / get_cookies()
  • 删除 Cookies : delete_cookie(name) / delete_all_cookies()

参考文章

由衷感谢这些资料的撰写者与提供者!