Python爬虫开发——XPath的使用

版权声明:如需转载,请注明原文出处,作者:vergilben https://blog.csdn.net/weixin_42751456/article/details/89607294

Python爬虫开发——XPath的使用

XPath简介:XPath,全程XML Path Language,即XML路径语言,它是一门在XML文档中查找信息的语言,它最初是用来搜寻XML文档的,但是它同样适用于HTML文档的搜索。因此在编写爬虫时,我们可以使用XPath来进行信息的抽取。XPath的选择功能十分强大,它提供了简洁明了的路径选择表达式。另外它还提供了超过100个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。几乎所有我们想要定位的节点,都可以用XPath来选择。、

XPath常用规则

表达式 描述
nodename 选取此节点的所有子节点
/ 从当前节点选取直接子节点
// 从当前节点选取子孙节点
. 选取当前节点
选取当前节点的父节点
@ 选取属性

例如:

//title[@lang='eng']

它代表选择所有名称为title,同时属性lang的值为eng的节点。

实例简介

看如下一段代码:

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-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result.decode('utf-8'))

这里首先导入lxml库的etree模块,然后声明了一段HTML文本,调用HTML类进行初始化,这样就构造了一个XPath解析对象。这里需要注意的是,HTML文本中的最后一个li节点是没有闭合的,但是使用etree模块可以自动修正HTML文本。我们调用tostring()方法即可输出修正后的代码,但结果是bytes类型。这里利用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-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div>
</body></html>

可以发现,经过处理之后,li节点标签被补全,并还自动添加了body、html节点。

所有节点

我们一般会用//开头的XPath规则来选取所有符合要求的节点。以上面的HTML文本(test.html)为例,若要选取所有节点,可以这样实现:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//*')
print(result)

上述代码中,使用*代表匹配所有节点,也就是整个HTML文本中的所有节点都会被获取。
运行结果如下:

[<Element html at 0x75b4c38>, <Element body at 0x75b4c10>, <Element div at 0x75b4be8>, <Element ul at 0x75b4bc0>, <Element li at 0x75b4918>, <Element a at 0x75b4710>, <Element li at 0x75b4738>, <Element a at 0x75b4760>, <Element li at 0x75b4580>, <Element a at 0x75b4558>, <Element li at 0x75b4418>, <Element a at 0x75b43f0>, <Element li at 0x75b4210>, <Element a at 0x75b41e8>]

可以看到,返回形式是一个列表,每个元素是Element类型,其后跟了节点的名称,如html、body、div、ul、li、a等,所有的节点都包含在列表中了。
当然这样匹配也可以指定节点名称。如果想获取所有li节点:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//li')
print(result)
print(result[0])

使用//,后面加上节点名称即可。调用时直接使用xpath()方法即可,结果:

[<Element li at 0x7c84c88>, <Element li at 0x7c84c60>, <Element li at 0x7c84c38>, <Element li at 0x7c84990>, <Element li at 0x7c84788>]
<Element li at 0x7c84c88>

可以看出,提取结果是一个列表形式,其中每个元素都是一个Element对象。如果要取出其中一个对象,加上索引即可。

子节点

我们可以通过/或//查找元素的子节点或子孙节点。//li用于选中所有li节点,/a用于选中li节点的所有直接子节点a,二者结合在一起即代表选取所有li节点下的a子节点,接着用上面的HTML文档来举例:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//li/a')
print(result)

运行结果如下:

[<Element a at 0x7e54be8>, <Element a at 0x7e54bc0>, <Element a at 0x7e54b98>, <Element a at 0x7e548f0>, <Element a at 0x7e546e8>]

如果要选中子孙节点,则使用//。例如//ul//a代表选取ul节点下的所有子孙节点a:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//ul//a')
print(result)

运行结果是相同的,但是如果使用//ul/a,就无法获取结果了,因为在ul节点下没有直接的a子节点,只有li节点。

属性匹配

在选取的时候,我们可以用@进行属性过滤,下面选取class属性为item-0的a节点:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
print(result)

运行结果:

[<Element li at 0x7655a80>, <Element li at 0x7655a58>]

父节点

我们知道通过连续的/或//可以查找子节点或子孙节点,那么怎样查找父节点呢。
通过文章开头的表格可以知道,这能用..来实现。
举个例子:首先酸中href属性为link4.html的a节点,然后再获取其父节点,再获取其class属性:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)

运行结果如下:

['item-1']

同时我们还可以用parent::来获取父节点:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/parent::*/@class')
print(result)

运行结果:

['item-1']

文本获取

用XPath中的text()方法获取节点的文本,我们尝试获取前面li节点中的文本,相关代码如下:

from lxml import etree

html = etree.parse('test.html',etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
print(result)

运行结果:

['\r\n']

结果并不像预期那样,因为XPath中text()前面是/,而此处/的含义是选取直接子节点,很明显li的直接子节点都是a节点,文本都是在a节点内部的,所以这里匹配到的结果就是被修正的li节点内部的换行符,因为自动修正的li节点的为标签换行了。
下面是我们选中的两个节点:
在这里插入图片描述后面的一个节点因为自动修正,li节点的为标签添加的时候换行了,所以提取文本得到的唯一结果就是li节点的尾标签和a节点的尾标签之间的换行符。

我们可以发现要匹配的文本在节点a之间,那么不妨再选中li节点下的a节点,再进行text():

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的子节点a节点内部的文本,最后一个则是li节点内部的文本。所以如果想获取某些特定子孙节点下的所有文本,可以先选取到特定的子孙节点,再调用text()方法,这样获得的信息是整洁的。

属性获取

上面说了用text()方法可以获取节点内部文本,如果想要获取节点的属性,用@符号就可以了。例如获取所有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指的是获取节点的某个(href)属性。

属性多值匹配

有时某些节点的某个属性可能有多个值,例如:

<li class="li li-first"><a href="link.html">first item</a></li>

这里的HTML中li节点下a的属性有两个值li和li-first,这时就要用到contains()函数:

from lxml import etree

text = """
<li class="li li-first"><a href="link.html">first item</a></li>
"""
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)

运行结果:

['first item']

通过contains()方法,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配。这种方式在某个节点的某个属性有多个值时经常用到,如某个节点的class属性通常有多个。

猜你喜欢

转载自blog.csdn.net/weixin_42751456/article/details/89607294