最近我在做一个自动化填表的项目,就遇到这样的问题——需要定位HTML页面上的某个元素,而在这其中,需要被定位的元素是动态的,这就很头疼了。

例如:

..



需要被定位的内容....自动生成的div



当我们要定位body底下的第一个div时,我们只需要写Xpath表达式为:/html/body/div[1]

定位body底下的第二个div时:/html/body/div[2]

问题是,如果我们要定位/html/body/div[1]下面的倒数第3个元素,或者是最后一个元素,于此同时,/html/body/div[1]下面的子元素是动态生成的,内容是不确定的。这种情况怎么办?

解决方案1 蠢办法

Xpath轴计算:

我们可以看看W3C对Xpath轴的解释:



轴的语法是:轴名称::节点测试[谓语]


我们可以通过/html/body/div[1]/child::div来定位所有的div

代码:

f = "/html/body/div[1]" #父元素路径
web = webdriver.Firefox()
web.get(url)....

第一步,得到/html/body/div[1]下所有子div数

div_num = len(web.find_elements_by_xpath(f + "/child::div"))

#/html/body/div[1]/child::div 注意是elements不是element,顾名思义,elements是获得一些,而element是获得一个

第二步,根据需求直接找到子div位置

假如定位最后一个div元素并点击:web.find_element_by_xpath(f + "/div[" + str(len) + "]").click()

#注意长度不用-1,因为Xpath是从1开始的,而不是0

定位倒数第三个div元素并点击:web.find_element_by_xpath(f + "/div[" + str(len - 3) + "]").click()

还有一种办法:

elems = web.find_elements_by_xpath("/html/body/div[1]/child::div")
#返回一个列表,列表内的元素为所有找到的element对象
点击最后一个子div:
elems[len(elems) -1].click()
#一定要-1,列表是从0开始的
点击倒数第2个子div:
elems[len(elems) -1 -2].click()

解决方案2 更聪明的办法

我们可以直接用谓语,或者是轴+谓语的方式来解决这个问题。

W3C的解释:谓语用来查找某个特定的节点或者包含某个指定的值的节点。谓语被嵌在方括号中。

没错,实际上,我们Xpath定义的时候,用的//div[1],这个1其实就是谓语,说明要在该层定位第1个div

这些谓语的用法:


说说加粗的部分:

/bookstore/book[last()]:重要的是last()这个谓语,显然他很像一个函数,不过在Xpath中的函数并不是这样使用的,而是"fn:函数"这种方式,暂且我们把它称作伪函数,尽管W3C把它归类为谓语的一部分,这个last()会返回子元素的个数。

假如我们要定位:/html/body/div下面的最后一个div:

web.find_element_by_xpath("/html/body/div/div[last()]")
#如果此时/html/body/div/下面有3个子元素,那么last()会返回3,此时相当于/html/body/div/div[3],由此就达到了定位最后一个元素的效果

定位倒数第三个div:

web.find_element_by_xpath("/html/body/div/div[last() - 3]")

/bookstore/book[position()<3]:position()看样子,似乎是和last()功能差不多,当你认为它的作用是返回//当前路径的同辈元素,那你就大错特错了。

事实上,position()是获得当前定位的元素下标位置。

这里以Selenium为例,当我们使用find_element_by_xpath("/bookstore/book[position()<3]")定位的时候,因为只获得一个元素,那么肯定是只获得/bookstore/book[1]这个元素,而我们用find_elements_by_xpath("/bookstore/book[position()<3]"),因为是获得一些,所以肯定是获得前2个元素。

这样理解:

find_element_by_xpath和find_elements_by_xpath的区别是,find_element_by_xpath是直接根据xpath路径定位,而find_elements_by_xpath则是每次都调用find_element_by_xpath进行一个一个的筛选,匹配则填入列表,直到xpath的条件逻辑为False。

定位倒数最后2个div,用last:

find_elements_by_xpath("/html/body/div/div[last()] | /html/body/div/div[last() - 1]")

position下标位置定位法,同样定位最后2个子div:

find_elements_by_xpath("/html/body/div/div[position() = last() or last() - position() = 1]")
#把position() = last() or last() - position() = 1拆开就能理解了:
#如果position() = last() 则为True,此时定位的是最后一个div
#or或 last() - position() = 1,如果 last() - position()的结果是1,则为True

定位倒数第3个div:

find_element_by_xpath("/html/body/div/div[last() - position() = 2 ]")
find_element_by_xpath("/html/body/div/div[last() - 2 ]")

定位倒数第3个和第4个div:

find_elements_by_xpath("/html/body/div/div[last() - 2 ] | /html/body/div/div[last() - 3 ]")
find_elements_by_xpath("/html/body/div/div[last() - position() = 2 or last() - position() = 3]")

定位倒数第三个和第五个div

find_elements_by_xpath("/html/body/div/div[last() - 2 ] | /html/body/div/div[last() - 4 ]")
find_elements_by_xpath("/html/body/div/div[last() - position() = 2 or last() - position() = 4]")

定位倒数第一个到倒数第5个div

find_elements_by_xpath("/html/body/div/div[last() - position() <= 4]")

取前面的几个div:

取第2个div

div[position() = 2]

取第2到第5个div

div[position() = 2 and position() <=5]

取第1到第10个div:

div[position() <= 10]

从前面往后取很简单,从后面往前取,稍微有点头疼,只要把它这当作一道小学生题目就行了,就很容易理解了:把position()当做左加数,把last()当做和,求右加数。

position() + ? = last()