python网络爬虫学习笔记(八):XPath的使用

使用XPath

XPath,全称XML Path Language,即XML路径语言,它是一门在XML文档中查找信息的语言。

1.XPath常用规则

在这里插入图片描述

这里列出了XPath的常用匹配规则,示例如下:

//title[@lang='eng']

这就是一个XPath规则,它代表选择所有名称为title,同时属性lang的值为eng的节点。

后面会通过Python的lxml库,利用XPath进行HTML的解析。

我们来看一个实例:

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'))
<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>

这里首先导入lxml库的etree模块,然后声明了一段HTML文本,调用HTML类进行初始化,这样就成功构造了一个XPath解析对象。HTML文本中的最后一个li节点是没有闭合的,但是etree模块可以自动修正HTML文本。

这里我们调用tostring()方法即可输出修正后的HTML代码,但是结果是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节点。

另外,也可以直接读取文本文件进行解析,示例如下:

from lxml import etree
 
html = etree.parse('test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div>&#13;
    <ul>&#13;
         <li class="item-0"><a href="link1.html">first item</a></li>&#13;
         <li class="item-1"><a href="link2.html">second item</a></li>&#13;
         <li class="item-inactive"><a href="link3.html">third item</a></li>&#13;
         <li class="item-1"><a href="link4.html">fourth item</a></li>&#13;
         <li class="item-0"><a href="link5.html">fifth item</a>&#13;
     </li></ul>&#13;
 </div></body></html>

这次的输出结果略有不同,多了一个DOCTYPE的声明,不过对解析无任何影响

2.所有节点

我们一般会用//开头的XPath规则来选取所有符合要求的节点。这里以前面的HTML文本为例,如果要选取所有节点,可以这样实现:

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

[<Element html at 0x1f6c0205648>, <Element body at 0x1f6c0019bc8>, <Element div at 0x1f6c0019c88>, <Element ul at 0x1f6c0205348>, <Element li at 0x1f6c0205688>, <Element a at 0x1f6c0205708>, <Element li at 0x1f6c0205748>, <Element a at 0x1f6c0205788>, <Element li at 0x1f6c02057c8>, <Element a at 0x1f6c02056c8>, <Element li at 0x1f6c0205808>, <Element a at 0x1f6c0205848>, <Element li at 0x1f6c0205888>, <Element a at 0x1f6c02058c8>]

这里使用*代表匹配所有节点,也就是整个HTML文本中的所有节点都会被获取。可以看到,返回形式是一个列表,每个元素是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])
[<Element li at 0x1f6c01e6f88>, <Element li at 0x1f6c0205cc8>, <Element li at 0x1f6c0205d08>, <Element li at 0x1f6c0205d48>, <Element li at 0x1f6c0205d88>]
<Element li at 0x1f6c01e6f88>

这里可以看到提取结果是一个列表形式,其中每个元素都是一个 Element对象。如果要取出其中一个对象,可以直接用中括号加索引,如[0]。

3.子节点

我们通过/或//即可查找元素的子节点或子孙节点。假如现在想选择li节点的所有直接a子节点,可以这样实现:

from lxml import etree
 
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)
[<Element a at 0x1f6c01e6fc8>, <Element a at 0x1f6c0205588>, <Element a at 0x1f6c0205e08>, <Element a at 0x1f6c0205e48>, <Element a at 0x1f6c0205e88>]

/用于选取直接子节点,如果要获取所有子孙节点,就可以使用//。例如,要获取ul节点下的所有子孙a节点,可以这样实现:

from lxml import etree
 
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul//a')
print(result)
[<Element a at 0x1f6c02058c8>, <Element a at 0x1f6c0205908>, <Element a at 0x1f6c02070c8>, <Element a at 0x1f6c0207108>, <Element a at 0x1f6c0207148>]

但是如果这里用//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']

我们也可以通过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']

5.属性匹配

@符号用于进行属性过滤,如选取class为item-1的li节点:

from lxml import etree

html = etree.parse('test.html', etree.HTMLParser())

result = html.xpath('//li[@class = "item-0"]')
print(result)
[<Element li at 0x1f6c038cd08>, <Element li at 0x1f6c038c788>]

6.文本获取

我们用XPath中的text()方法获取li节点中的文本:

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节点内部的文本,即换行符。

7.属性获取

使用@符号获取属性

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']

某些节点的某个属性可能有多个值,这里HTML文本中li节点的class属性有两个值li和li-first,此时如果还想用之前的属性匹配获取,就无法匹配了:

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[@class="li"]/a/text()')
print(result)
[]

我们可以使用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']

如果一个节点都多个属性,可以使用and来连接

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

另外,还有很多运算符,如or、mod等:

在这里插入图片描述

8.按序选择

我们选取了第一个li节点,中括号中传入数字1,选取最后一个li节点,中括号中传入last(),选取倒数第三个li节点,中括号中传入last() - 2

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 = html.xpath('//li[1]/a/text()')
print(result)
result = html.xpath('//li[last()]/a/text()')
print(result)
result = html.xpath('//li[position()<3]/a/text()')
print(result)
result = html.xpath('//li[last()-2]/a/text()')
print(result)
['first item']
['fifth item']
['first item', 'second item']
['third item']

猜你喜欢

转载自blog.csdn.net/qq_43328040/article/details/108808251