【Python3 爬虫学习笔记】解析库的使用 1 —— 使用XPath 1

XPath,全称XML Path Language,即XML路径语言,它是一门在XML文档中查找信息的于洋。它最初是用来搜寻XML文档的,但它同样适用于HTML文档的搜索。

1. XPath概览

XPath的选择功能十分强大,它提供了非常简洁明了的路径选择表达式。另外,它还提供了超过100个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。
XPath于1999年11月16日成为W3C标准,它被设计为供XSLT、XPointer以及其他XML解析软件使用。

2. XPath常用规则

下表列举了XPath的几个常用规则。

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

XPath的常用匹配规则,示例如下:

//title[@lang='eng']

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-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()方法即可输出修正后的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'))

其中test.html的内容就是上面例子中的HTML代码。

<!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的声明,不过解析无任何影响。

4. 所有节点

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

from lxml import etree

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

运行结果如下:

[<Element html at 0x1f2b3c1d248>,
 <Element body at 0x1f2b3c00ac8>,
  <Element div at 0x1f2b3c00a88>,
   <Element ul at 0x1f2b3aee588>,
   <Element li at 0x1f2b3c1d288>, 
   <Element a at 0x1f2b3c1d308>, 
   <Element li at 0x1f2b3c1d348>, 
   <Element a at 0x1f2b3c1d388>, 
   <Element li at 0x1f2b3c1d3c8>, 
   <Element a at 0x1f2b3c1d2c8>, 
   <Element li at 0x1f2b3c1d408>, 
   <Element a at 0x1f2b3c1d448>, 
   <Element li at 0x1f2b3c1d488>, 
   <Element a at 0x1f2b3c1d4c8>]

这里使用*代表匹配所有节点,也就是整个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])

这里要选取所有li节点,可以使用//,然后直接上节点名称即可,调用时直接使用xpath()方法即可。
运行结果:

[<Element li at 0x1f2b3c1d208>, 
<Element li at 0x1f2b3b992c8>, 
<Element li at 0x1f2b3acfa48>, 
<Element li at 0x1f2b3aea708>,
 <Element li at 0x1f2b3ac8148>]
<Element li at 0x1f2b3c1d208>

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

5. 子节点

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

from lxml import etree

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

这里通过追加/a即选择了所有li节点的所有直接a子节点。因为//li用于选中所有li节点,/a用于选中li节点的所有直接子节点a,二者组合在一起即获取所有li节点的所有直接a子节点。
运行结果如下:

[<Element a at 0x1f2b3c31d88>, <Element a at 0x1f2b3c31dc8>, <Element a at 0x1f2b3ae4d08>, <Element a at 0x1f2b3aeea08>, <Element a at 0x1f2b3aeec48>]

此处的/用于选取直接子节点,如果要获取所有子孙节点,就可以使用//。例如,要获取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节点,所以无法获取任何匹配结果。

6. 父节点

如果要选中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)

7. 属性匹配

在选取的时候,我们还可以通过使用@符合进行属性过滤,如这里要选取class为item-0的li节点,可以这样实现:

from lxml import etree

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

这里我们通过加入[@class=“item-0”],限制了节点的class属性为item-0,而HTML文本中符合条件的li节点有两个,所以结果应该返回两个匹配的元素。结果如下:

[<Element li at 0x1f2b3ae3348>, <Element li at 0x1f2b3ae30c8>]

猜你喜欢

转载自blog.csdn.net/htsait4113/article/details/82846990