3.4.7 通过 XPath 选择
XPath(XML Path Language)是W3C定义的用来在XML文档中选择节点的语言。主流浏览器(Chrome、Firefox,Edge,Safari)也支持XPath语法。XPath有1.x 和2.x两个版本,支持的主要是XPath 1.X的版本,2.X的版本目前几乎不支持。
对于浏览器原生支持XPath的,Selenium尽量使用原生的XPath实现,有些浏览器也支持通过XPath来访问。我们推荐尽量使用CSS选择,而不是用XPath,因为CSS方式通畅速度更快,而且相对更容易理解。使用XPath定位的一个主要场景是。XPath有从当前节点选择父节点的功能,这是CSS selector所不具备的。如果我们要选择一个子节点(比如子节点有id,方便定位)的父节点,可以使用XPath。就比较方便。 另外,我们后面学习的 移动App 自动化里面没有css选择元素,只有xpath选择,所以我们有必要也去了解一下Xpath。
大家看下面这段html代码:
定位网页元素
This is a heading
This is a paragraph.
Cheddar
Gouda
转到百度
黄瓜
牛肉
黄瓜pp
青菜
黄瓜2
牛肉3
test2
one
two
three
test3
test4
test5
可以在 element tab 中输入 //option也可以在 console tab验证。
html文档被看成 文件系统一样的 树状结构, 文件系统树的根节点用 / 表示,那么html的根节点是什么console输入$x('/')
点击 后发现, 高亮显示的元素,对应整个html文档,如果我们想选择的是根节点下面的html节点console输入$x('/html')
,如果要继续选择html下面的一层层节点console输入$x('/html/body/p'),大概等价于css中的html >body>p,假如路径以正斜杠(/)开始,表示从根节点开始,则此路径始终代表到某元素的绝对路径!很像Linux里面的绝对路径的概念,我们的Selenium代码 也 可以用这样的xpath来查找元素。
比如:
eles = driver.find_element_by_xpath("/html/body/p")
我们怎么表示 整个文档中的所有的option(选项)节点,不管它在什么位置? 就像css 中的option这样呢?xpath 需要前面加// , //表示 从当前节点往下寻找所有的后代元素,不管它在什么位置。 既然开始的时候,当前节点是根节点, 如果以//开头就表示从根节点 开始, 往下寻找所有的后代元素,不管它在什么位置,比如:
//option
表示 从当前节点往下寻找所有的后代元素中名字为option的。
当然 // 符号 也可以继续加在后面。
//div//p
表示选择 所有 的 div 元素里面的所有p元素 , 不管div 在什么位置, 不管 p 元素在 div 下面的什么位置。就等于 css 选择器的div p。
如果我们要找某个元素下面的直接子节点,而不是所有的后代节点呢?比如body节点下面直接的p节点, 而不是它所有的后代节点p节点那就是:
//body/p
就等于 css 选择器的body > p 和 css 一样 xpath 也有通配符 *
比如选择所有的节点:
//div/*
选择 div 下面所有的 直接子元素。
3.4.8 XPath 根据属性和属性的值选择
比如:选择所有具有style属性的元素,注意前面必须有个@
//*[@style]
又比如:选择所有具有 spec 值为 len2 的p元素。
//p[@spec='len2']
注意: css 中 如果 属性值 没有空格 可以不加引号。但是 xpath 则必须要引号,单双引号都可以。
如果我们要想根据id选择, 因为id也是一个属性 可以:
//div[@id="food"]
要想根据class选择, 因为 class 也是一个属性 可以:
//div[@class="cheese"]
3.4.9 子元素选择
注意在css和XPath中索引是从1开始的,和python中不一样,在xpath中 第几个某类型的子元素 直接用:
//p[2]
表示 选择属于其父元素的第2个p类型的子元素(不一定在所有的元素中排第2个)等价于 css 中的:
p:nth-of-type(2)
比如:
//*[@id="food"]/p[1]
等价于 css 中的:
#food > p:nth-of-type(1)
那么,如果是表示 其父元素的所有的元素中 第几个呢, 而不是像上面这样, 某种类型的第几个,该这样写:
//*[@id="food"]/*[1]
因为*
表示所有的类型的元素。
在xpath中 倒数第几个p类型子元素用:
//p[last()-1]
表示 属于其父元素的倒数第二个 p 类型的子元素,等价于 css 中的:
p:nth-last-of-type(2)
倒数第一呢? 当然就是:
p[last()]
等价于css中的:
p:nth-last-of-type(1)
比如:选择 倒数第1个span类型元素:
//*[@id="food"]/span[last()]
等价于 css 中的:
#food > span:nth-last-of-type(1)
同样的道理, 如果是表示 其父元素的所有的元素中 倒数第几个,就是这样:
//*[@id="food"]/*[last()-1]
因为 *
表示所有的类型的元素。
如果我们要表示的是 属于其父元素的所有类型元素的 第二个子元素,在xpath中,除了上面说的用 *[2]
,还有一种方法用 *[position()=2]
试一下,比如:
//*[@id="food"]/*[position()=2]
等价于 css 中的:
*:nth-child(2)
比如:
//*[@id="food"]/*[position()=3]
等价于 css 中的:
#food > *:nth-child(3)
那么既然下面的写法都可以,后者还更麻烦,用它有什么好处呢?
因为 position 函数还支持其他的 比较操作符, 可以多选元素,比如:
//*[@id="food"]/*[position()< 3]
//*[@id="food"]/*[position()<= 3]
//*[@id="food"]/*[position()>=3]
这个我们需要选择第几个以后的元素的时候, 就方便多了。
//*[@id='food']/*[position()>1][position()<3]
注意,这种情况用css比较麻烦,难懂,这里就是xpath表达式的长处了,倒数第几个元素也是类似同样的方法,我们除了可以用:
//*[@id="food"]/*[last()-1]
也可以:
//*[@id="food"]/*[position()=last()-1]
#//body/*[position()=last()-1]
当然, 使用position函数的最大好处是可以多选, 比如:
//*[@id="food"]/*[position() > last()-3]
从倒数第3个开始 选择所有的。
css 有组选择,可以 同时选择多个元素,逗号隔开,比如 p,button 选择所有的 p 和button 那么xpath对应多个选择是怎么做的呢?用竖线隔开//p | //button
xpath 还有很多其他的选择语法, 我们可以看,css中选择兄弟节点,xpath 还有很多其他的选择语法。
css中选择兄弟节点:
打开html 尝试一下#food ~ div 选择 id 为food 的元素后面的所有 div 兄弟节点,xpath中 相邻兄弟选择器 following-sibling 和 preceding-sibling
#food ~ div
等价于 注意是两个冒号
//*[@id="food"]/following-sibling::div
如果我们要选择 选择 id 为 food 的元素 前面 的所有 div 兄弟节点 css 就没有办法了,但是 xpath 有:
//*[@id="food"]/preceding-sibling::div
也可以根据次序来找,写1表示紧挨着前面的第一个,写2就是第二个
//*[@id="food"]/preceding-sibling::div[1]
css 中 #food + div ,选择紧接着 后面的的那个兄弟节点,xpath可以这样
//*[@id='food']/following-sibling::div[1]
//*[@id='food']/following-sibling::div[position()>=1]
css 选择不能用的, 而 xpath 可以的, 还有选择父节点 直接用 .. 表示
比如 //*[@id='food']/..
就选择了body节点,那么这个到底有什么用处呢?有的时候我们最终想选择的元素 不好定位(没有id,父节点也没有id, 没有唯一好定位的属性), 但是它的子节点好定位,比如有id,看下
定位网页元素
#8765#
牛肉
#8766#
猪肉
#8767#
三文鱼
如果我们想 验证 其中猪肉前面的代码(当前网页是8766), 该怎么做? 因为代码在猪肉 那个span节点的父节点。
最好的方式是根据其id为 pork 的span子节点来定位器父节点。这时可以用XPath的选择器,代码如下
//*[@id='pork']/..
这样就得到父元素对应的webelement了, 再获取其text属性,去掉#
就可以了对应的代码是:
# coding=utf8
from selenium import webdriver
driver = webdriver.Chrome(r"d:\tools\webdrivers\chromedriver.exe")
driver.get('file:///D:/gsync/workspace/sq/selenium/samples_selenium/wd/lesson05/xpath.html')
ele = driver.find_element_by_xpath("//*[@id='pork']/..")
print (ele.text.split('#')[1])
# eles = driver.find_elements_by_xpath("//*[@id='beef']/../..//span[@price>20]")
# for ele in eles:
# print(ele.text)
driver.quit()
还可以继续找上层,比如
//span[@id='pork']/../../../..
补充讲解:
这是一个特别要注意的小技巧对xpath来说,如果是从当前webelement调用find_element_by_xpath,需要前面加个点 比如:
food = driver.find_element_by_id("food")
eles = food.find_elements_by_xpath('//p')
for ele in eles:
print('----------')
print(ele.get_attribute('outerHTML'))
就不对,应该是
food = driver.find_element_by_id("food")
eles = food.find_elements_by_xpath('.//p')
for ele in eles:
print('----------')
print(ele.get_attribute('outerHTML'))
更多的语法大家可以去W3c去看:http://www.w3school.com.cn/xpath/xpath_axes.asp
3.4.10 通过 JavaScript 选择
我们还可以通过让浏览器执行 JavaScript 代码来选择 web 元素。只要我们给浏览器执行的js代码最后返回一个DOM Element对象。
from selenium import webdriver
driver = webdriver.Chrome('E:\ChromDriver\chromedriver.exe')
driver.get('http://www.baidu.com')
kw = driver.execute_script("return $('#kw')[0]")
kw.send_key('测试')
driver.quit()
driver.execute_script方法让浏览器执行我们制定的js代码。
通过执行我们自己定义的JavaScript,给了我们极大的灵活性,不仅可以用在测试方面,甚至可以修改web页面里面的内容,达到各种目的。当然这需要js开发的基础。有兴趣的同学可以自行深入研究。
本章就到这里了,喜欢麻烦点个赞!!!