xpath解析

安装与导入

xpath 功能是在 lxml 库中,需要安装 lxml 库。

> pip install lxml

使用时要从 lxml 库中导入 etree 模块:

from lxml import etree

前置代码

xpath解析需要使用一段 xml或html进行演示,在此小节导入模块与定义 html片段:

from lxml import etree

html = """
<html>
<head></head>
<body>
    <div>
      <span>电影</span>
        <div class="movie" id="movie1">
            <div class="title">肖申克的救赎</div>
            <div class="director">弗兰克·德拉邦特</div>
            <div class="year">1994</div>
            <div class="grade">9.7</div>
        </div>
        <div class="movie" id="movie2">
            <div class="title">霸王别姬</div>
            <div class="director">陈凯歌</div>
            <div class="year">1993</div>
            <div class="grade">9.6</div>
        </div>
    </div>
</body>
</html>
"""

element_obj = etree.HTML(html)

xpath知识点总结

/        斜杠符号,表示当前节点的子层级,若是在开头,则表示根节点的子层级
*        匹配当前层级的任意节点
//       当前节点的所有子节点,包含子节点的子节点以及无限层级的子节点
./       一般用在开头,表示当前节点,与子节点对象配合使用

节点[@属性名]          获取有指定来这个属性的节点
节点[@属性名="值"]     获取指定属性值的节点
节点[从1开始的数字]     获取指定索引的节点
节点[last()]          获取最后一个节点
节点[last()-1]        获取倒数第二个节点
节点[position()<3]    获取前两个节点

text()     获取当前节点中的内容
@属性名     获取节点的属性的值

通过叙述的方式学习xpath

假设我们要获取电影二字,那么可以:

print(element_obj.xpath('/html/body/div/span/text()'))
# 结果为:['电影']

过程为:第一个/表示从根开始查找,之后的html的意思是找到根下面的html节点(在html语言中称为标签),再之后的/body 就是获取html节点下面的 body 节点,以此类推获取到 span节点,最通过 /text() 获取span节点中的内容。

由此可以得到两个知识点:

/        斜杠符号,表示当前节点的子层级,如例子中的第一个/,就表示要查找根下的子节点。
         例子中第二个/,就表示要进入到了html中的层级。
text()   获取当前节点中的内容。如例子中 span/text(),就表示span中的内容。

这样一级一级的向下查找固然很明确,但是很繁琐与复杂,中间的节点是否可以省略?

print(element_obj.xpath('/html/*/*/span/text()'))
print(element_obj.xpath('/html//span/text()'))
print(element_obj.xpath('//span/text()'))
# 以上的结果都是:['电影']

其中:

*       匹配当前层级的任意节点。 如上例中 /html/*,就是匹配html节点下面的任意节点,
        本例中html节点下仅有body节点,所有也就匹配到了body节点。
//      当前节点的所有子节点,包含子节点的子节点以及无限层级的子节点。
        如例子中,/html// 表示 html节点下的全部子节点。 第三个表示根节点下的全部子节点。

那么,我想要电影名称呢?

print(element_obj.xpath('//div/text()'))

这段代码获取到了包含电影名称的数据,并且还有很多其他内容。因为电影名称所在的节点是 div,但是还有很多节点也是div,这就导致代码把所有的div节点中的内容都获取到。我们可以指定div的属性来剔除其他div标签:

print(element_obj.xpath('//div[@class="title"]/text()'))

节点后面跟一对[]中括号,中括号内通过@属性名="值"的语法来指定节点中的属性。

如果我们仅仅要获取第一个电影名称呢?

print(element_obj.xpath('//div[@id="movie1"]/div[1]/text()'))

当当前层级同名节点很多并且我们仅需要其中一个时,可以同样使用[]中括号,在中括号中填入数字,数字就代表获取第几个节点。(索引值从1开始)。

节点[@属性名]         获取有指定来这个属性的节点
节点[@属性名="值"]     获取指定属性值的节点
节点[从1开始的数字]    获取指定索引的节点
节点[last()]          获取最后一个节点
节点[last()-1]        获取倒数第二个节点
节点[position()<3]    获取前两个节点

上例中,获取一个电影名称是通过id属性获取的,若不通过id属性获取则会很冗杂。其实可以通过多次xpath解析操作来使简化操作。

div_element_obj = element_obj.xpath('//div[@class="movie"]')[0]
print(div_element_obj.xpath('./div[1]/text()'))

第一行获取到了classmoviediv节点,因为要获取第一个电影名称,因此获取到索引为0的节点。

第一行的xpath解析需要从 div节点开始,所以不仅要使用div节点对象,还要在xpath解析中的开头使用./语法,表示当前,若依旧使用/// 语法则还是会从根目录开始。