XPath的使用
1.使用实例
通过Python的lxml库,利用XPath进行HTML的解析。
xpath对网页解析
代码:
from lxml import etree text = ''' ''' html = etree.HTML(text) #调用HTML类进行初始化,构造一个XPath解析对象 result = etree.tostring(html) print(result.decode('utf-8')) #调用tostring()方法即可输出修正后的HTML代码,但是结果是bytes类型 #用decode()方法将其转换为str类型
或者读取文本文件进行解析:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) #读取文件解析,test.html与前面的HTML代码相同 result = etree.tostring(html) print(result.decode('utf-8'))
运行结果:输出
- class="item-0">"link1.html">first item
- class="item-1">"link2.html">second item
- class="item-inactive">"link3.html">third item
- class="item-1">"link4.html">fourth item
- class="item-0">"link5.html">fifth item
结果补全了li节点标签,自动添加了body、html节点。
(直接读取文件输出多了一个DOCTYPE声明,对解析结果无影响)
2.所有节点
我们一般会用 // 开头的XPath规则来选取所有符合要求的结果。
使用 * 代表匹配所有节点。
以前面的test.html文件来演示。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//*') print(result)
运行结果:输出
[, , , , , , , , , , , , , ]
如果要选取所有的li节点,只需要使用//加上li节点名称即可。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li') print(result)
运行结果:输出
[, , , , ]
提取出来的结果都是列表形式,每个元素都是Element对象。
3.子节点
通过 // 或 / 即可查找元素的子节点或子孙节点。
如果想选择li节点的所有直接a子节点。/a用于选择li节点的所有直接子节点a。
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li/a') print(result)
运行结果:输出
[, , , , ]
如果要获取所有的子孙节点,就可以使用 // 。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//ul//a') print(result)
运行结果:输出
[, , , , ]
但是如果使用//ul/a就不能获得任何结果,因为ul节点下没有直接的a子节点,只有li节点。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//ul/a') print(result)
运行结果:输出
[]
/ :用于获取直接子节点
// :用于获取子孙节点
4.父节点
如果知道了子节点,可以通过 .. 来查找父节点。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//a[@href="link4.html"]/../@class') print(result)
运行结果:输出
['item-1']
获取到了目标li节点的类名class。
也可以使用 parent:: 来获取父节点。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//a[@href="link4.html"]/parent::*/@class') print(result)
运行结果同上。
5.属性匹配
可以使用@符号进行属性过滤。比如要选择class为item-0的li节点。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]') print(result)
运行结果:输出
[, ]
6.文本获取
使用XPatn中的text()方法获取文本中的节点。
尝试获取li节点中的文本。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]/text()') #使用text() print(result)
运行结果:输出
['\r\n ']
并没有获取到任何文本,只获取到了换行符,这是为什么呢?因为 XPath 中 text 方法前面是 /,而此处 / 的含义是选取直接子节点,
很明显 li 的直接子节点都是 a 节点,文本都是在 a 节点内部的,所以这里匹配到的结果就是被修正的 li 节点内部的换行符,因为自动修正的 li 节点的尾标签换行了。
如果要获取到文本,有两种方式,一种是先选取a节点再获取文本。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]/a/text()') print(result)
运行结果:输出
['first item', 'fifth item']
另一种是使用 //。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]//text()') print(result)
运行结果:输出
['first item', 'fifth item', '\r\n ']
但是,这里返回了三个结果,选取的是所有子孙节点的文本,最后一个是li内部的文本,即换行符。
7.属性获取
@可以获取节点属性。
如果我们想获取所有li节点下所有的a节点的href属性。
代码:
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li/a/@href') print(result)
运行结果:输出
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
此处和属性匹配的方法不同,属性匹配是中括号加属性名和值来限定某个属性,如 [@href="link1.html"],而此处的 @href 指的是获取节点的某个属性,二者需要做好区分。
8.属性多值匹配
某些节点的属性有多个值。
代码:
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()') #contains()方法,第一个参数传入属性名称,第二个参数传入属性值 print(result)
运行结果:输出
['first item']
9.多属性匹配
根据多个属性确定一个节点,就要同时匹配多个属性。
代码:
from lxml import etree text = '''first item ''' #这里li节点又增加了一个name属性,需要同时根据class和name来选择 html = etree.HTML(text) result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()') #使用and操作符相连接 print(result)
运行结果:输出
['first item']
and是XPath中的运算符,此外还有很多。
10.按序选择
有时匹配了多个节点,却只想要其中几个节点。
可以利用中括号传入索引的方法获取特定次序节点。
代码:
from lxml import etree text = ''' ''' html = etree.HTML(text) result = html.xpath('//li[1]/a/text()') #选取了第一个li节点,注意是以1开头的,而不是0 print(result) result = html.xpath('//li[last()]/a/text()') #选取最后一个li节点,中括号中传入last()即可 print(result) result = html.xpath('//li[position()<3]/a/text()') #选取了位置小于3的节点,也就是序号为1和2的节点 print(result) result = html.xpath('//li[last()-2]/a/text()') #选取了倒数第三个li节点,传入last()-2即可,因为last()是最后一个,last()-2就是倒数第三个 print(result)
运行结果:输出
['first item'] ['fifth item'] ['first item', 'second item'] ['third item']
使用了last()、position()等函数,在XPath中还有许多其他种类的函数。
11.节点轴选择
XPath提供了很多节点轴选择方法。
代码:
from lxml import etree text = ''' ''' html = etree.HTML(text) result = html.xpath('//li[1]/ancestor::*') #调用ancestor轴,可以获取所有的先祖节点。 #其后要跟两个冒号,其次是节点的选择器,使用 * 表示匹配所有节点。 print(result) result = html.xpath('//li[1]/ancestor::div') #*改为div,只得到div这一个先祖节点。 print(result) result = html.xpath('//li[1]/attribute::*') #调用attribute轴,可以获取所有属性值。 #使用*表示获取节点的所有属性。 print(result) result = html.xpath('//li[1]/child::a[@href="link1.html"]') #调用child轴,可以获取所有直接子节点。 #其后加了限定条件,选择href属性为link1.html的a节点。 print(result) result = html.xpath('//li[1]/descendant::span') #调用descendant轴,可以获取所有子孙节点。 #其后加了限定条件,获取span节点,所以返回的结果只包含span节点而不包含a节点。 print(result) result = html.xpath('//li[1]/following::*[2]') #调用following轴,可以获取当前节点之后的所有节点。 #虽然使用的是 * 匹配,但又加了索引选择,所以只获取了第二个后续节点。 print(result) result = html.xpath('//li[1]/following-sibling::*') #调用了following-sibling轴,可以获取当前节点之后的所有同级节点。 #使用 * 匹配,所以获取了所有后续同级节点。 print(result)
运行结果:输出
[, , , ] [ ] ['item-0'] [ ] [ ] [ ] [ , , , ]
参考用书《python3网络爬虫开发实战》