XPath 定位语法

被测试网页的 HTML 代码如下:

<html>
    <body>
        <div id="div1" style="text-align: center">
            <img alt="XPath 定位语法"
            src="http://www.sogou.com/images/logo/new/sogou.png"
            href="http://www.sogou.com">搜狗图片</img><br />
            <input name="div1input">
            <a href="http://www.sogou.com">搜狗搜索</a>
            <input type="button" value="查询">
        </div>
        <br>
        <div name="div2" style="text-align: center">
            <img alt="XPath 定位语法" src="http://www.baidu.com/img/bdlogo.png"
            href="http://www.baidu.com">百度图片</img><br />
            <input name="div2input">
            <a href="http://www.baidu.com">百度搜索</a>
        </div>
    </body>
</html>

使用上面的 HTML 代码生成被测试网页如下,基于此网页来实践各种不同的页面元素的 XPath 定位方法。
XPath 定位语法

1. 使用绝对路径定位元素

绝对路径表示页面元素在被测网页的 HTML 代码结构中,从根节点一层层地搜索到需要被定位的页面元素,绝对路径起始于正斜杠(/),每一步均被斜杠分割。

目的:
在被测试网页中,查找第一个 div 标签下的“查询”按钮。
XPath 定位表达式:
/html/body/div/input[@value="查询"]
Python 定位语句:
query = driver.find_element_by_xpath('/html/body/div/input[@value="查询"]')
代码解释:
上述 XPath 定位表达式从 HTML DOM 树的根节点(html 节点)开始逐层寻找,最后定位到“查询”按钮节点。路径表达式“/”表示根节点。
更多说明:
使用绝对路径定位页面元素的好处在于可以验证页面是否发生变化。如果页面结构发生变化,可能会造成原先有效的 XPath 表达式失效。使用绝对路径定位是十分脆弱的,因为即便页面代码结构只发生了微小的变化,也可能会造成原先有效的 XPath 定位表达式定位失败。

2. 使用相对路径定位元素

相对路径的每一步都根据当前节点集之中的节点来进行计算,起始于双正斜杠(//)。

目的:
在被测试网页中,查找第一个 div 标签下的“查询”按钮。
XPath 定位表达式:
//input[@value="查询"]
Python 定位语句:
query = driver.find_element_by_xpath('//input[@value="查询"]')
代码解释:
上述 XPath 定位表达式中的“//”表示从匹配选择的当前节点开始选择文档中的节点,而不考虑它们的位置。input[@value=“查询”] 表示定位 value 值为“查询”两个字的 input 页面元素。
更多说明:
相对路径的 XPath 定位表达式更加简洁,不管页面发生了何种变化,只要 input 标签的 value 属性值没变,始终都可以定位到。推荐使用相对路径的 XPath 表达式,并且越简洁越好,可大大降低测试脚本中定位表达式的维护成本。

3. 使用索引号定位元素

索引号表示某个被定位的页面元素在其父元素节点下的同名元素中的位置序号,需要从 1 开始。

目的:
在被测试网页中,查找第一个 div 标签下的“查询”按钮。
XPath 定位表达式:
//input[2]
Python 定位语句:
query = driver.find_element_by_xpath('//input[2]')
代码解释:
索引号定位方式是根据该页面元素在页面中相同标签名之间出现的索引位置来进行定位的。上述 XPath 定位表达式表示查找页面中第二个出现的 input 元素,即被测试页面上的“查询”按钮。
更多说明:
若在 Chrome 浏览器的开发者工具中按下 Ctrl+F 使用“//input[1]”定位表达式进行页面元素定位,可以发现在结果中查找到两个符合要求的元素,两个 div 标签下的第一个 input 标签都被定位到,这和只查找第一个 input 元素相冲突,这是由于被测试网页中两个 div 标签下都包含了 input 标签,XPath 在查找的时候把每个 div 节点都当作相同的起始层级开始查找,所以用“//input[1]”表达式会同时查找到两个 div 节点下的第一个 input 元素。如果在两 div 标签下还有嵌套的 div,并且嵌套的 div 下也有 input 标签,使用“//input[1]”定位表达式,也会定位到嵌套 div 下的 input 标签,也就是说无论嵌套多少层 HTML 标签,只要这些 HTML 标签的子标签里有 input 标签,第一个 input 标签都会被定位到。因此在使用索引号定位页面元素的时候,需要注意网页 HTML 代码中是否包含了多个层级完全相同的代码结构(比如本例中的两个 div 层级),若出现了这种情况,就需要修改定位表达式,以确保能唯一定位所需要的页面元素。
但如果想同时定位多个相同 input 页面元素,可以使用如下 Python 语句:
inputList = driver.find_elements_by_xpath("//input[1]")
将定位的多个元素存储到 list 对象中,然后根据 list 对象的索引号获取想要的页面元素。但如果发现页面元素会经常增加或减少,就不建议使用索引号定位的方式,因为页面变化很可能会让使用索引号的 XPath 定位表达式定位失败。
基于实例中的被测试网页,下面给出更多的通过索引号定位的实例,如下表所示。

预期定位的页面元素 定位表达式实例 使用的属性值
定位第二个 div 下的超链接 //div[last()]/a div[last()] 表示最后一个 div 元素,last() 函数获取的是指定元素的最后的索引号
定位第一个 div 下的超链接 //div[last() - 1]/a div[last() - 1] 表示倒数第二个 div 元素
定位最前面一个属于 div 元素的子元素中的 input 元素 //div/input[position()=2] position() 函数获取当前元素 input 的位置序列号

4. 使用页面元素的属性值定位元素

在定位页面元素的时候,经常会遇到各种复杂结构的被测试网页,并且很多页面元素也没有设计 ID、Name 等属性,同时又不想使用绝对路径或索引号来定位页面元素,但是发现要被定位的页面元素拥有某些固定不变的属性及属性值,此时推荐使用属性定位方式来定位页面元素。

目的:
定位被测试网页中的第一张 img 元素。
XPath 定位表达式:
//img[@alt="XPath 定位语法"]
Python 定位语句:
img = driver.find_element_by_xpath('//img[@alt="XPath 定位语法"]')
代码解释:
表达式使用了相对路径再结合元素拥有的特定属性的方法进行定位,定位元素 img 的属性是“alt”,其属性值为“div1-img1”,使用 @符号指明后面接的是属性,并同属性及属性值一起写到元素后的方括号中。
更多解释:
被测试网页的元素通常会包含各种各样的属性值,并且很多属性值具有唯一性。若能确认属性值不常变并且唯一,强烈建议使用相对路径再结合属性的定位方式来编写 XPath 定位表达式,使用此方法可以解决 99% 的页面元素定位难题。基于实例中的被测试网页,下面给出更多的属性值定位实例,如下表所示。

预期定位的页面元素 定位表达式实例 使用的属性值
定位页面的第一张图片 //img[@href=“http://www.sogou.com”] 使用 img 标签的 href 属性值
定位第二个 div 中第一个 input 输入框 //div[@name=“div2”]/input[@name=“div2input”] 使用 div 标签的 name 属性值 使用 input 标签的 name 属性值
定位第一个 div 中的第一个链接 //div[@id=“div1”]/a[@href=“http://www.sogou.com”] 使用 div 标签的 ID 属性值 使用 a 标签的 href 属性值
定位页面的查询按钮 //input[@type=“button”] 使用 type 属性值

5. 使用模糊属性值定位元素

模糊属性值定位方式表示使用属性值的一部分内容进行定位。常常会遇到页面元素的属性值是动态生成的,也就是说每次访问属性值都不一样,此类页面元素会加大定位难度,使用模糊属性值定位方式可以解决一部分此类难题,但前提是属性值中有一部分内容保持不变。XPath 提供了一些可实现模糊属性值的定位需求的函数,如下表所示。

XPath 函数 定位表达式实例 表达式解释
starts-with(str1, str2) //img[starts-with(@alt, “div1”)] 查找属性 alt 的属性值以“div1”关键字开始的页面元素
contains(str1, str2) //img[contains(@alt, “img”)] 查找 alt 属性的属性值包含“img”关键字的页面元素,只要包含即可,无须考虑位置

contains()函数属于 XPath 的高级用法,使用场景比较多,尽管页面元素的属性值经常发生变化,但只要其属性值有几个固定不变的关键词,就可以使用 contains() 函数进行定位。

6. 使用 XPath 轴(Axes)定位元素

轴可以定义相对于当前节点的节点集。使用 XPath(Axes)定位方式可根据在文档树中的元素相对位置关系进行页面元素定位。先找到一个相对好定位的元素,让它作为轴,根据它和要定位元素间的相对位置关系进行定位,可解决一些元素难以定位的问题。
根据文章开头提供的被测试网页的 HTML 代码,画出一棵图形化文档树状图,如下图所示。
XPath 定位语法

XPath 常用轴关键字如下表所示。

XPath 轴关键字 轴的含义说明 定位表达式实例 表达式解释
parent 选择当前节点的上层父节点 //img[@alt=“div2-img2”]/parent::div 查找到属性 alt 的属性值为 div2-img2 的 img 元素,并基于该 img 元素的位置找到它上一级的 div 页面元素
child 选择当前节点的下层所有子节点 //div[@id=“div1”]/child::img 查找到 ID 属性值为 div1 的 div 元素,并基于 div 的位置找到它下层节点中的 img 页面元素
ancestor 选择当前节点的所有上层的节点 //img[@alt=“div2-img2”]/ancestor::div 查找到属性 alt 的属性值为 div2-img2 的 img 元素,并基于该 img 元素的位置找到它上级的 div 页面元素
descendant 选择当前节点所有下层的节点(子、孙等) //div[@name=“div2”]/descendant::img 查找到属性 name 的属性值为 div2 的 div 页面元素,并基于该元素的位置找到它下级所有节点中的 img 页面元素
following 选择在当前节点之后显示的所有节点 //div[@id=“div1”]/following::img 查找到 ID 属性值为 div1 的 div 页面元素,并基于 div 的位置找到它后面节点中的 img 页面元素
following-sibling 选择当前节点后续所有兄弟节点 //a[@href=“http://www.sogou.com”]/following-sibling::input 查找到链接地址为http://www.sogou.com 的链接页面元素 a,并基于链接的位置找到它后续兄弟节点中的 input 页面元素
preceding 选择当前节点前面的所有节点 //img[@alt=“div2-img2”]/preceding::div 查找到属性 alt 的属性值为 div2-img2 的图片页面元素 img,并基于图片的位置找到它前面节点中的 div 页面元素
preceding-sibling 选择当前节点前面的所有兄弟节点 //input[@value=“查询”]/preceding-sibling::a[1] 查找到 value 属性值为”查询“的输入框页面元素,并基于该输入框的位置找到它前面同级节点中的第一个链接页面元素

更多说明:
有时候我们会在轴后面加一个星号(),表示通配符,比如 //input[@value=“查询”]/preceding-sibling::,它表示查找属性 value 的值为”查询“的输入框 input 元素前面所有的同级元素,但不包括 input 元素本身。

7. 使用页面元素的文本定位元素

通过 text() 函数可以定位到元素文本包含某些关键内容的页面元素。

XPath 表达式:

(1)//a[text()="搜狗搜索"]
(2)//a[.="搜狗搜索"]
(3)//a[contains(., "百度")]
(4)//a[contains(text(), "百度")]
(5)//a[contains(text(), "百度")]/preceding::div
(6)//a[contains(., "百度")]/..

Python 定位语句:

sogou_a = driver.find_element_by_xpath('//a[text()="搜狗搜索"]')
sogou_a = driver.fing_element_by_xpath('//a[.="搜狗搜索"]')
baidu_a = driver.find_element_by_xpath('//a[contains(., "百度")]')
baidu_a = driver.find_element_by_xpath('//a[contains(text(), "百度")]')
div = driver.find_element_by_xpath('//a[contains(text(), "百度")]/preceding::div')
div = driver.find_element_by_xpath('//a[contains(., "百度")]/..')

代码解释:

  • XPath 表达式 1 和表达式 2 等价,都是查找文本内容为”搜狗搜索“的链接页面元素,使用的是精准匹配方式,也就是说文本内容必须完全匹配,不能多一个字也不能少一个字。第二个 XPath 语句中使用了一个点(.),这里的点等价于 text() 函数,都指代的是当前节点的文本内容。
  • XPath 表达式 3 和表达式 4 等价,都是查找文本内容包含“百度”关键字的链接页面元素,使用的是模糊匹配方式,即可以根据部分文本关键字进行匹配。
  • XPath 表达式 5 和表达式 6 等价,都是查找文本内容包含“百度”关键字的链接页面元素 a 的上层父元素 div,第 6 句 XPath 表达式最后使用了两个点(..),它表示选取当前节点的父节点,等价于 preceding::div。

更多说明:
使用文本内容匹配模式进行定位,为定位复杂的页面元素又提供了一种强大的定位模式,在遇到定位困难时,可以优先考虑使用此方式进行定位。