```
我们要注意/和//的区别,其中/是用于获取直接子节点,//用于获取子孙节点。
获取父节点
我们已经知道了获取子节点和子孙节点,获取父节点可以用..来实现。比如我们想获取href属性为/films/1218273的a节点的父节点的class属性,也就是p的class属性,可以这样来实现:
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = html.xpath('//a[@href="/films/1218273"]/../@class')
print(result)
运行结果如下:
['name']
0x05 属性匹配
上面我们获取父节点的属性时已经用到了属性的知识,在xpath中我们可以用@符号来进行属性过滤。比如这里要选取class为star的p节点,可以这样实现:
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = html.xpath('//p[@class="star"]') # 属性匹配
print(result)
这里我们通过加入[@class="star"],限制了节点的class属性为star,而在案例的HTML文本中符合条件的p节点有两个,所以结果应该返回两个匹配到的元素。结果如下:
[, ]
0x06文本获取
我们可以利用XPath中的test()方法来获取文本内容,接下来尝试获取属性为class的p节点的文本,也就是我们的电影名称
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = html.xpath('//p[@class="name"]/text()')
print(result)
运行结果如下:
[]
我们可以看到我们并没有获取到任何文本,书上给的解释是:XPath中text()前面是/,而此处/的含义是选取直接子节点,p的直接子节点都是a节点,文本都是在a节点内部的。我们错误的使用了/使得我们没有匹配到想要获取的内容
如果想要获取p节点的内部文本,就有两种获取方式,一种是先获取a节点在获取文本,另一种就是使用//。接下来,我们来看一下二者的区别:
首先,选取到a节点在获取文本,代码如下:
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = html.xpath('//p[@class="name"]/a/text()')
print(result)
运行结果如下:
['误杀', '叶问4:完结篇']
可以看到这里的返回值是两个,内容都是属性为name的p节点的文本。这里我们是逐层选取的,先选取了p节点,有利用/选取了其直接子节点a,然后再选取文本,得到的结果恰好是我们预期的两个结果
再来看下用另外一种方式(即使用//)选取的结果,代码如下:
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = html.xpath('//p[@class="name"]//text()') # 使用//获取p节点下的文本
print(result)
运行结果如下:
['误杀', '叶问4:完结篇']
我们可以看到我们还是得到了预期的结果,但是如果我们想获取属性为star的p节点下的文本,代码如下:
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = html.xpath('//p[@class="star"]//text()')
print(result)
运行结果:
['\n 主演:肖央,谭卓,陈冲\n', '\n 主演:甄子丹,吴樾,吴建豪\n ']
我们发现虽然获取到了我们想要的内容,但是里面还有一些换行符。所以说,如果想要获取子孙节点内部的所有文本,可以直接使用//加text()方式,这样可以保证获取到最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。如果想要获取某些特定子孙节点下的所有文本,可以先获取到特定的子孙节点,然后再调用text()方法获取其内部文本,这样可以保证我们的结果是整洁的
0x07 属性获取
我们知道用text()可以获取节点内部文本,那么节点属性该怎样获取呢?其实还是用@符号就可以。例如,我们想获取所有p节点下所有a节点的href属性,代码如下:
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = html.xpath('//p/a/@href')
print(result)
这里我们通过@href即可获取节点的href属性。注意,此处和属性匹配的方法不同,属性匹配是中括号加属性名和值来限定某个属性,如[@href="/films/1218273"],而此处的@href指的是获取节点的某个属性,二者需要做好区分。
运行结果如下:
['/films/1218273', '/films/1190122']
我们成功获取了所有p节点下的a节点的href属性,他们以列表的形式返回
0x08 属性多值匹配
有时候,某些节点的某个属性可能有多个值,例如:
from lxml import etree
text = '''
first item'''
html = etree.HTML(text)
result = html.xpath('//li[@class="li"]/a/text()')
print(result)
这里HTML文本中li节点的class属性有两个值li和li-first,此时如果还想用之前的属性匹配获取,就无法匹配了,此时的运行结果如下:
[]
这时就需要用到contains()函数了,代码可以改写如下:
from lxml import etree
text = '''
first item'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)
这样通过contains()方法,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配了。
此时运行结果如下:
['first item']
0x09 多属性匹配
另外,我们可能遇到一种情况,那就是根据多个属性确定一个节点,这时就需要同时匹配多个属性。此时可以使用运算符and来连接,示例如下:
from lxml import etree
text = '''
first item'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)
这里的li节点有增加了一个属性name。要确定这个节点,需要同时根据class和name属性来选择,一个条件是class属性里面包含li字符串,另一个条件是name属性为item字符串,二者需要同时满足,需要用and操作符相连,相连之后置于中括号内进行条件筛选。运行结果如下:
['first item']
0x10 按序选择
有时候我们在选择的时候某些属性可能同时匹配了多个节点,但是又想要其中的某个节点,这是我们可以利用中括号传入索引的方法来获取特定次序的节点,示例如下:
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result_1 = html.xpath('//li[1]/a/text()')
print(result_1)
result_2 = html.xpath('//li[last()]/a/text()')
print(result_2)
result_3 = html.xpath('//li[position()<3]/a/text()')
print(result_3)
result_4 = html.xpath('//li[last()-2]/a/text()')
print(result_4)
第一次选择时我们选取第一个li节点,中括号传入数字1即可。第二次选择时我们选取最后一个li节点,中括号中传入last()即可。第三次选择时,我们选取了位置小于3的li节点,也就是位置序号为1和2的li节点。第四次选择时,我们选取了倒数第三个li节点
运行结果如下:
['first item']
['fifth item']
['first item', 'second item']
结语
XPath选择器的功能十分强大,使用起来简单方便,大大的提升了我们爬虫的效率。