XPath使用
XPath
一,简介
全称XMl Path Language,是一种在XML中寻找信息的语言,同样适用于HTML文档搜索。XPath功能十分强大,提供了非常简洁明了的路径选择表达式。拥有超过100个内建函数,用于字符串,数字,时间以及节点的匹配,序列的处理等。几乎所有想要定位的节点都可以用XPath来选择。
二,使用
1.常用规则
nodename :选取此节点的所有子节点
/ :从当前节点选取直接子节点
// :从当前节点选取子孙节点
. :选取当前节点
.. :选取当前节点的父节点
@ :选取属性
示例://title[@lang='eng'],代表锁选择的名称为title,同时属性;lang的值为eng的节点。
2,安装lxml
windows:pip3 install lxml
3,使用示例
from lxml import etree text = """ <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-active"><a href="link3.html">third item</a></li> <li class="item-4"><a href="link4.html">fourth item</a></li> <li class="item-5"><a href="link5.html">fifth item</a> </ul> </div> """ html = etree.HTML(text) res = etree.tostring(html) print(res.decode('utf-8'))
首先到如etree模块,声明一段HTML文本,调用HTML类进行初始化,这样就成功构造了一个Xpath解析对象,text中最后一个li节点时没有闭合的,但是etree可以自动修正HTML文本。调用tostring输出修正后的文本,在decode转成str。
<html><body><div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-active"><a href="link3.html">third item</a></li> <li class="item-4"><a href="link4.html">fourth item</a></li> <li class="item-5"><a href="link5.html">fifth item</a> </li></ul> </div> </body></html>
经过处理,li节点被不全,并自动添加了,body,html节点。另外,可以直接读取文本文件进行解析:
html = etree.parse('./text.html', etree.HTMLParser()) print(etree.tostring(html).decode('utf-8'))
4.选取所有节点
我们一般会使用//开头的XPath规则来选取所有符合要求的节点。
# 选取所有节点 html = etree.parse('./text.html', etree.HTMLParser()) res = html.xpath('//*') print(res)
运行结果:
[<Element html at 0x117994842c8>, <Element head at 0x117994843c8>, <Element meta at 0x11799484408>,
<Element title at 0x11799484448>, <Element body at 0x11799484488>, <Element div at 0x11799484508>,
<Element ul at 0x11799484548>, <Element li at 0x11799484588>, <Element a at 0x117994845c8>,
<Element li at 0x117994844c8>, <Element a at 0x11799484608>, <Element li at 0x11799484648>,
<Element a at 0x11799484688>, <Element li at 0x117994846c8>, <Element a at 0x11799484708>,
<Element li at 0x11799484748>, <Element a at 0x11799484788>]
这里*代表所有节点,返回的是一个列表,每个元素都是element类型,其后跟了节点的名称,如html,body,title,ul,li等等,所有的节点都包含在其中。当日也可以获取指定节点:
res = html.xpath('//li') print(res) print(res[0])
运行结果:
[<Element li at 0x22855a593c8>, <Element li at 0x22855a59408>,<Element li at 0x22855a59448>,
<Element li at 0x22855a59488>, <Element li at 0x22855a594c8>]
<Element li at 0x22855a593c8>
想要取出其中的某一个对象,直接使用索引。
6.选取子节点
通过/或//可以查找元素的子节点或者子孙节点。查找li节点的所有直接a节点
# 选取子节点 res = html.xpath('//li/a') print(res)
运行结果:
[<Element a at 0x27107af43c8>, <Element a at 0x27107af4408>,
<Element a at 0x27107af4448>, <Element a at 0x27107af4488>, <Element a at 0x27107af44c8>]
这里通过追加/a选择所有li节点下面的直接子节点a。//li用于选取所有的li节点,/a用于选中li节点中所有直接子节点a。想要获取所有子孙节点可以使用//,获取ul节点下的所有孙子a节点:
res = html.xpath('//ul//a') print(res)
运行结果:
[<Element a at 0x1a88f8c43c8>, <Element a at 0x1a88f8c4408>, <Element a at 0x1a88f8c4448>,
<Element a at 0x1a88f8c4488>, <Element a at 0x1a88f8c44c8>]
与上面的例子运行结果是相同的。但是这里如果使用//ul/a,就无法获取任何结果了,ul节点下面没有直接子节点a。
7.属性选择
在选取的时候,我们还可以使用@符号进行属性过滤。选取class值为item-0的li节点:
# 属性选择 res = html.xpath('//li[@class="item-0"]') print(res)
运行结果:
[<Element li at 0x178e69653c8>]
8.父节点
选取href属性为link4.html的a节点的父节点,再获取其class的属性,使用..选择父节点:
res = html.xpath('//a[@href="link4.html"]/../@class') print(res)
运行结果:
['item-4']
9.文本获取
我们可以使用XPath中的text()方法进行文本获取,尝试获取li节点中的文本:
# 文本获取,选取li节点所有子孙节点的文本 res = html.xpath('//li[@class="item-5"]//text()') print(res)
运行结果:
['fifth item']
# 文本获取,先选取直接子节点a,再获取其文本res = html.xpath('//li[@class="item-5"]/a/text()') print(res)
运行结果:
['fifth item']
10.属性多值匹配
某些时候,某个节点的属性可能存在多个值,例如:
from lxml import etree text = "<li class='li li-first'><a href='link.html'>first li</a></li>" html = etree.HTML(text) res = html.xpath('//li[@class="li"]/a/text()') print(res)
运行结果:
[]
这里的li节点的属性存在li,li-first两个值,使用之前的获取方法是无法获取的。此时就需要使用contains()函数了,:
res = html.xpath('//li[contains(@class,"li")]/a/text()') print(res) 运行结果: ['first li']
通过contains()方法,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就能完成匹配了。可以在某个节点的某个属性存在多个值的时候使用。
11.多属性匹配
根据多个属性来确定一个节点,需要同时匹配多个属性:
text = "<li class='li li-first' name='item'><a href='link.html'>first li</a></li>" html = etree.HTML(text) res = html.xpath('//li[contains(@class,"li") and @name="item"]/a/text()') print(res)
运行结果:
['first li']
li节点里面又增加了一个name属性,且li节点的class属性存在多个值。此时需要同时使用class和name属性进行选择,一个条件时class里面包含li字符串,另一个条件是name的属性值为item,二者需要同时满足,使用and操作符相连,之后再置于中括号内进行条件筛选。
xpath运算符介绍
or:或。 例:age=19 or age=20,如果age是19或20,则返回True,否则返回False
and:与。 例:age>19 and age<21,同时满足and前后条件则返回True,否则False
mod:计算除法的余数。 例:5 mod 2 , 1
| :计算两个节点集。 例://bock | //cd ,返回所有用于bock和cd元素的节点集
+: 加。 例:6 + 2, 8
-: 减。 例:6 - 2, 4
*: 乘。 例:6 * 2, 12
div: 除。 例:6 div 2, 3
=: 等于
!=: 不等于
<: 小于
<=: 小于等于
>: 大于
>=: 大于等于
12.按序选择
有时候,我们在选择节点时同时匹配了多个节点,但是只想要其中的某个节点,或者第二个节点,这时候该怎么办呢?
from lxml import etree text = """ <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-active"><a href="link3.html">third item</a></li> <li class="item-4"><a href="link4.html">fourth item</a></li> <li class="item-5"><a href="link5.html">fifth item</a> </ul> </div> """ # 按序选择 html = etree.HTML(text) res = html.xpath('//li[1]/a/text()') print(res) res2 = html.xpath('//li[last()]/a/text()') print(res2) res3 = html.xpath('//li[position()<3]/a/text()') print(res3) res4 = html.xpath('//li[last()-2]/a/text()') print(res4)
运行结果:
['first item'] ['fifth item'] ['first item', 'second item'] ['third item']
第一次选择,选取第一个li节点,在括号中传入数字1。这里与索引不同,序号是以1开始的。
第二次选择,选取最后一个li节点,使用last()即可。
第三次选择,选取位置小于3的li节点,也就是位置序号为1和2的li节点。
第四次选择,选取倒数第三个li节点,在括号中传入last()-2即可,因为last()为最后一个,那么last()-2则为倒数第三个。
13.节点轴选择
XPath提供了很多节点轴的选择方法,包括子元素,兄弟元素,父元素,祖先元素等,:
from lxml import etree text = """ <div> <ul> <li class="item-0"><a href="link1.html"><span>first item</span></a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-active"><a href="link3.html">third item</a></li> <li class="item-4"><a href="link4.html">fourth item</a></li> <li class="item-5"><a href="link5.html">fifth item</a> </ul> </div> """ # 节点轴选择 html = etree.HTML(text) res = html.xpath('//li[1]/ancestor::*') print(res) res2 = html.xpath('//li[1]/ancestor::div') print(res2) res3 = html.xpath('//li[1]/attribute::*') print(res3) res4 = html.xpath('//li[1]/child::a[@href="link1.html"]') print(res4) res5 = html.xpath('//li[1]/descendant::span') print(res5) res6 = html.xpath('//li[1]/following::*[2]') print(res6) res7 = html.xpath('//li[1]/following-sibling::*') print(res7)
运行结果:
[<Element html at 0x165f62363c8>, <Element body at 0x165f6236348>, <Element div at 0x165f6236308>, <Element ul at 0x165f6236408>] [<Element div at 0x165f6236308>] ['item-0'] [<Element a at 0x165f62364c8>] [<Element span at 0x165f6236548>] [<Element a at 0x165f6236588>] [<Element li at 0x165f6236648>, <Element li at 0x165f6236688>, <Element li at 0x165f62366c8>, <Element li at 0x165f6236708>]
第一次选择,调用ancestor轴可以获取所有祖先节点。其后跟两个冒号,然后是节点的选择器,这里直接使用*表示匹配所有节点,返回的是第一个li节点的所有祖先节点,包括body,html,div和ul。
第二次选择,我们在后面加上了限制条件,只选择div。
第三次选择,调用attribute轴,可以获取所有节点的属性。加上*,返回的是li节点的所有属性值。
第四次选择,调用child轴,可以获取所有子孙节点。加上先限制条件,选取href值为link1.html的a节点。
第五次选择,调用descendant轴,可以获取所有子孙节点。这里我们又加上了限制span节点,返回结果只包含span节点而不包含a节点。
第六次选择,调用following轴,获取当前节点之后的所有节点。这里虽然使用了*匹配所有,但又加上了索引,返回的只有第二个后续节点。
第七次选择,调用following-sibling轴,获取当前节点之后所有的同级节点。使用*获取后续所有的同级节点。
三,总结
到此为止,我们基本上把XPath可能用到的选择器介绍完了。是不是觉得,哇!!!XPath好强大!内置函数非常多,熟练之后可以大大提升HTML信息提取效率。