022.(RDC暑假学习期 7.13-7.14)认识XPath

参考书籍:《Python 3网络爬虫开发实战》


6月6号早晨突然收到考核结果通过的通知,惊喜成为RDC大数据组的一员。两个月的思考与突破,是疫情在家学习时光的难忘记忆之一。从进度条的%5到接近所有任务要求的%90,一路下来,也靠行动,靠意志。

考试月让工作室的日子暂告一段落。考试结束后的今天,将迎来RDC暑假学习期(小小996)。之前师兄师姐也提到过,大数据从业方向主要有算法和开发两者。暑假的学习主要在于前者,希望通过这1个月多的学习,自己既能更加确定自己的兴趣和方向,也能尽快提升团队合作能力,多多出力锻炼。

今天,简单回顾之前的爬虫基础后,便开始XPath1的学习。

介绍

之前自己实现的爬虫提取页面信息时,基本使用正则表达式,这还是比较
烦琐,而且万一有地方写错了,可能导致匹配失败,所以使用正则表达式提取页面信息、多多少少还有些不方便。

对于网页的节点(tag)来说,它可以定义 id,class 或其他属性,而且节点之间还有层次关系,在网页中可以通过 XPath,CSS选择器来定位一个或多个节点 那么,在页面解析时,利用 XPath,CSS选择器来提取某个节点,然后再调用相应方法获取它的正文内容或者属性,就可以提取我们想要的任意信息。

XPath 全称 XML Path Language ,即XML 路径语言,它是一门在 XML 文档中查找信息的语言。它最初是用来搜寻 XML 文档的,但是它同样适用于 HTML 文档2的搜索。所以在做爬虫时,我们完全可以使用 XPath 来做相应的信息抽取。

而常见的解析库包括 lxml(一种使用 Python 编写的库,可以迅速、灵活地处理 XML 和 HTML,还可以用于web爬取),Beautiful Soup,pyquery 等。

常用规则

在这里插入图片描述

实例引入

from lxml import etree
# 导入 lxml 库的 etree 模块
# 然后声明一段 HTML 文本,调用 HTML类进行初始化,成功构造一个 XPath解析对象。

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 文本中最后一个 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'))

节点

用以 // 开头的 XPath 规则来选取所有符合要求的节点:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)
# * 代表匹配所有节点,返回的结果是一个列表,每个元素都是一个 Element 类型,后跟节点名称。

# 运行结果:
"""
[<Element html at 0x1d6610ebe08>, <Element body at 0x1d6610ebf08>, 
<Element div at 0x1d6610ebf48>, <Element ul at 0x1d6610ebf88>, 
<Element li at 0x1d6610ebfc8>, <Element a at 0x1d661115088>, 
<Element li at 0x1d6611150c8>, <Element a at 0x1d661115108>, 
<Element li at 0x1d661115148>, <Element a at 0x1d661115048>, 
<Element li at 0x1d661115188>, <Element a at 0x1d6611151c8>, 
<Element li at 0x1d661115208>, <Element a at 0x1d661115248>]
"""

#  也可以指定匹配的节点名称:
result1 = html.xpath('//li')

# 通过 / 或 // 即可查找元素的子节点或子孙节点。
# 选择 li 节点的所有直接 a 子节点:

result2 = html.xpath('//li/a')

#  知道子节点,查询父节点可以用 .. 来实现:

result3 = html.xpath('//a[@href="link4.html"]/../@class')

# 获得 href 属性为 link4.html 的 a 节点的父节点的 class 属性
# parent::*也是父节点的表示(斜杠要加上)

文本获取

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

# 第一种
from lxml import etree

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

# 第二种(子孙节点内部的所有文本)
from lxml import etree

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

# 第二种方法会获取到补全代码时换行产生的特殊字符
# 推荐使用第一种方法,可以保证获取的结果是整洁的。

属性获取

区别于属性匹配,但其实还是用@符号就可以实现。

# 获取节点的 href 属性
result = html.xpath('//li/a/@href') 
print(result)

# 运行结果:['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

属性多值匹配

当某个节点的某个属性有多个值时,前面的单一匹配法就为空了。

通过 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)

# 例中的li节点的class属性有2个值
# 运行结果:['first item']

多属性匹配

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

# 要用 and 操作符相连,相连之后置于 中括号 内进行条件筛选
# 这里的 and 其实是 XPath 中的运算符

在这里插入图片描述

按序选择

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)
# 获取第一个(注意,这里和代码中不同,序号是以1开头的,不是以0开头)
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']
"""

这里使用了 last ()、 position()等函数。 XPath中,提供了 100 多个函数,包括存取值、字符串、逻辑、节点、序列等处理功能, 详细可见使用文档。

节点轴选择

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-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]/ancestor::*')
print(result)
# 获取 div 祖先节点
result = html.xpath('//li[1]/ancestor::div')
print(result)
# 获取当前节点所有属性值
result = html.xpath('//li[1]/attribute::*')
print(result)
# 获取 href 属性值为 link1.html 的直接子节点
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
# 获取所有的的子孙节点中包含 span 节点但不包含 a 节点
result = html.xpath('//li[1]/descendant::span')
print(result)
# 获取当前所有节点之后的第二个节点
result = html.xpath('//li[1]/following::*[2]')
print(result)
# 获取当前节点之后的所有同级节点
result = html.xpath('//li[1]/following-sibling::*')
print(result)

"""
[<Element html at 0x231a8965388>, <Element body at 0x231a8965308>, <Element div at 0x231a89652c8>, <Element ul at 0x231a89653c8>]
[<Element div at 0x231a89652c8>]
['item-0']
[<Element a at 0x231a89653c8>]
[<Element span at 0x231a89652c8>]
[<Element a at 0x231a89653c8>]
[<Element li at 0x231a8965308>, <Element li at 0x231a8965408>, <Element li at 0x231a8965448>, <Element li at 0x231a8965488>]
"""

关于XPath和正则表达式

XPath主要是用来处理 XML 格式文档的,它是基于 XML 文档的层次结构来确定一个到达指定节点的路径的,因此特别适合处理这种层级结构明显的数据。

正则表达式可以处理任何格式的字符串文档,它是基于文本的特征来匹配、查找指定数据的。

个人认为,要分清目标数据是层次结构明显还是特征明显3,这个分清楚了,该选什么也就基本确定了,后续要思考的其实算是优化的步骤。此外,XPath,可读性和可维护型都是比较好的。当然一般的正则表达式效
率是比较高的,这个前提是不太复杂的正则表达式。


  1. XPath 官方文档 ↩︎

  2. html 与 xml 的区别与联系 ↩︎

  3. XPath与正则表达式在文本数据提取时该如何选择? ↩︎

猜你喜欢

转载自blog.csdn.net/u013598957/article/details/107319594