Web爬虫|入门教程解析库lxml+XPth选择器

网络爬虫开发实战源码:https://github.com/MakerChen66/Python3Spider

原创不易,本文禁止抄袭、转载,多年爬虫实战开发经验总结,侵权必究!

一、XPth引入

当我们用正则表达式提取页面信息时会感觉比较繁琐,麻烦,而且如果有一个地方写错了,可能会导致匹配失败,所以使用正则表达式进行页面信息提取还是有一点不方便

那么,在页面解析时,我们可以利用XPth或CSS选择器来提取某个节点,然后再调用相应方法获取它的文本或者属性,不就可以提取我们想要的任意信息了吗?

在Python中这种解析库很多,其中比较强大的库有lxml,Beautiful Soup,pyquery等。其中Beautiful Soup之前已经为大家介绍过了,接下来将为大家介绍在lxml解析库中如何使用XPth选择器去定位节点元素,有了它们,我们就不需要再为正则表达式发愁,解析效率也会大大提高


二、XPth使用

XPth,全称为XML Path Language,即XML路径语言,它是一门在XML文档中查找信息的语言,但是它同样适用于HTML文档的搜索

2.1 XPth概览

XPath的选择功能十分强大,提供了超过100个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。几乎所有我们想要定位的节点,都可以用XPath来选择

XPath官方网站:
https://www.w3.org/TR/xpath/

2.2 XPath常用规则

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

这里列出了XPath的常用规则,示例如下:
//div[@name=‘loginname’]
这是一个XPath规则,表示选择所有名称为div,属性name为loginname的节点

后面会通过解析库lxml,利用XPath对HTML进行定位解析

2.3 安装

工欲善其事,必先利其器,先安装一下解析库lxml,若已安装可忽略

pip install lxml -i https://pypi.doubanio.com/simple

这里用-i指定安装的URL,此URL为豆瓣源,也可自行使用清华源,阿里源等。之所以要使用国内镜像源进行安装,是因为默认的安装URL是外网,可能会因为连接超时而导致安装失败,相信各位深有体会。使用国内镜像源秒安

也可使用PyCharm在软件内部进行安装


三、XPth实例

3.1 实例引入

现在通过实例来感受一下使用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'))

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

这里我们调用tostring()方法即可输出修正后的HTML代码,但是结果是bytes类型,接着利用decode()方法将其转成str类型,输出结果如下:
在这里插入图片描述
可以看到,处理之后,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代码,./表示当前目录下,输出如下:
在这里插入图片描述

3.2 所有节点

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

from lxml import etree

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

输出结果如下:
在这里插入图片描述
使用*代表匹配所有节点,也就是说整个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对象。如果想要取出其中一个对象,可以直接用中括号加索引,如[0]

3.3 子节点

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

from lxml import etree

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

输出结果如下:
在这里插入图片描述
这里通过在后面添加/a表示选择了所有li节点的所有直接子节点a,如果想选择li节点所有的子孙节点a,就写成//a,当然这样写的输出结果和上面是一样的,因为li节点之后只有一个a节点

3.4 父节点

我们知道用/或//可以选取所有的直接子节点或子孙节点,那么如果我们知道了子节点,怎样来查找父节点呢?可以用…来实现

假如现在要选取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']
[Finished in 0.5s]

检查一下test.html,发现’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)

输出结果也和上面一样。这种写法叫节点轴,详情请看第四部分

3.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)

输出结果如下:

[<Element li at 0x2176e366a48>, <Element li at 0x2176e366a88>]

匹配结果是两个,至于是不是正确的那两个,我们后面再验证

3.6 文本获取

XPath中的text()方法可以获取节点中的文本,接下来尝试获取前面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']

可以看到,这里返回值是两个,我们先选取了属性为item-0的所有li节点,又利用/选取了其所有直接子节点a,然后再获取其文本,得到的结果正好是我们想要的两个结果

3.7 属性获取

用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属性。注意,此处和属性匹配的方法不同,属性匹配是中括号加属性名和值来限定某个属性,如[@href=“link2.html”]
而此处的@href指的是获取节点的某个属性,二者需要做好区分

3.8 属性多值匹配

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

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)

输出结果如下:

[]

一个空的列表,因为text文本中li节点的class属性有两个值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']

此种方式在某个节点的某个属性有多个值时经常用到,如某个节点的class属性通常有多个

3.9 多属性匹配

另外,我们可能还会遇到一种情况,那就是根据多个属性确定一个节点,这时就需要同时匹配多个属性。此时可以使用运算符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']

这里的li节点又增加了一个属性name。要确定这个节点,需要同时根据class和name属性来选择,一个条件是class属性里面包含1i字符串,另一个条件是name属性为item字符串,二者需要同时满足,需要用and操作符相连,相连之后置于中括号内进行条件筛选

这里的and其实是XPath中的运算符。另外还有很多运算符,如or,mod等,更多运算符可参考链接:
https://www.w3school.com.cn/xpath/xpath_operators.asp

3.10 按序选择

有时候,我们在选择的时候某些属性可能同时匹配了多个节点,但是只想要其中的某个节点,如第二个节点或者最后一个节点,这时该怎么办呢?

这时可以利用中括号传入索引的方法获取特定次序的节点,示例如下:

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)

第一次选择时,我们选取了第一个li节点,中括号中传入数字1即可。注意,这里和代码中不同,序号是以1开头的,不是以0开头

第二次选择时,我们选取了最后一个li节点,中括号中传入last()即可,返回的便是最后一个li节点

第三次选择时,我们选取了位置小于3的li节点,也就是位置序号为1和2的节点,得到的结果就是前两个li节点

第四次选择时,我们选取了倒数第三个li节点,中括号中传入last0)-2即可。因为last()是最后一个,所以last()-2就是倒数第三个

输出结果如下:
在这里插入图片描述
这里我们使用了last()、position()等函数。在XPah中,提供了100多个函数,包括存取、数值、字符串、逻辑、节点、序列等处理功能,更多的函数及作用可以参考链接:
http://www.w3school.com.cn/xpath/xpath_functions.asp

四、XPth节点轴

4.1 XPth常用节点抽

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)
result = html.xpath('//li[1]/ancestor::div')
print(result)
result = html.xpath('//li[1]/attribute::*')
print(result)
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
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)

输出结果如下:
在这里插入图片描述
第一次选择时,我们调用了ancestor轴,可以获取所有祖先节点。其后需要跟两个冒号。然后是节点的选择器,这里我们直接使用*,表示匹配所有节点,因此返回结果是第一个li节点的所有祖先节点,包括html、body、div和ul

第二次选择时,我们又加了限定条件,这次在冒号后面加了div,这样得到的结果就只有div这个祖先节点了

第三次选择时,我们调用了attribute轴,可以获取所有属性值,其后跟的选择器还是*,这代表获取节点的所有属性,返回值就是li节点的所有属性值

第四次选择时,我们调用了child轴,可以获取所有直接子节点。这里我们又加了限定条件,选择href属性为的link1.html的节点

第五次选挥时,我们调用了descendant轴,可以获取所有子孙节点。这里我们又加了限定条件获取span节点,所以返回的结果只包含span节点而不包含a节点

第六次选择时,我们调用了following轴,可以获取当前节点之后的所有节。这里我们虽然使用的是*匹配,但又加了索引选择,所以只获取了第二个后续节点

第七次选择时,我们调用了following-sibling轴,可以获取当前节点之后的所有同级节点。这里我们使用*匹配,所以获取了所有后续同级节点

4.2 总结

我们基本上把可能用到的XPath选择器介绍完了。XPath选择器功能十分强大,内置函数非常多,熟练之后,可以大大提升HTML信息的提取效率

更多节点轴的用法可以参考链接:
https://www.w3school.com.cn/xpath/xpath_axes.asp

更多XPath的用法可以查看链接:
https://www.w3school.com.cn/xpath/index.asp

更多lxml库的用法可以查看官网:
https://lxml.de/


五、原文阅读

本人原创公众号原文链接:阅读原文

原创不易,如果觉得有点用,希望可以随手点个赞,拜谢各位老铁!

六、作者Info

作者:小鸿的摸鱼日常,Goal:让编程更有趣!

原创微信公众号:『小鸿星空科技』,专注于算法、爬虫,网站,游戏开发,数据分析、自然语言处理,AI等,期待你的关注,让我们一起成长、一起Coding!

转载说明:本文禁止抄袭、转载 ,侵权必究!

猜你喜欢

转载自blog.csdn.net/qq_44000141/article/details/121526788
今日推荐