Python爬虫-解析库之XPath

XPath简介

XPath,全称为XML Path Language,即XML路径语言,它是一门在XML文档中查找信息的语言。最初是用来搜寻XML文档的,但同样适用于HTML文档的搜索。更多详细信息可以参考:https://www.w3.org/TR/xpath

XPath常用规则

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

示例如://title[@lang='eng'],这就是个XPath规则,它代表选择所有名称为title,同时属性lang的值为eng的节点。

示例引入

首先要确定安装lxml库,安装可以参考Python库的安装。

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-2"><a href="link3.html">third item</a></li>
<li class="item-3"><a href="link4.html">fourth item</a></li>
<li class="item-4"><a href="link5.html">fifth item</a>
</ul>
</div>
'''

html = etree.HTML(text)
result = etree.tostring(html)
#这里的result是bytes类型,用decode转化为str类型
#print(type(result))
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-2"><a href="link3.html">third item</a></li>
<li class="item-3"><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

#test.html的内容就是上述例子中的HTML代码
html = etree.parse('test.html', etree.HTMLParser())
result = etree.tostring(html)
#这里的result是bytes类型,用decode转化为str类型
#print(type(result))
print(result.decode('utf-8'))

输出结果多了一个DOCTYPE的声明,对解析无任何影响。

所有节点

  • //开头的XPath规则来选取所有符合要求的节点。用上述HTML为例:

    from lxml import etree
    
    #test.html的内容就是上述例子中的HTML代码
    html = etree.parse('test.html', etree.HTMLParser())
    #使用*代表匹配所有节点,也就是整个HTML文本中的所有节点都会被获取
    result = html.xpath('//*')
    print(result)
    

    结果:

    [<Element html at 0x2ca3bcaf148>, <Element body at 0x2ca3bcaf0c8>, <Element div at 0x2ca3bcaf088>, 
    <Element ul at 0x2ca3bcaf188>, <Element li at 0x2ca3bcaf1c8>, <Element a at 0x2ca3bcaf248>, 
    <Element li at 0x2ca3bcaf288>, <Element a at 0x2ca3bcaf2c8>, <Element li at 0x2ca3bcaf308>, 
    <Element a at 0x2ca3bcaf208>, <Element li at 0x2ca3bcaf348>, <Element a at 0x2ca3bcaf388>,
     <Element li at 0x2ca3bcaf3c8>, <Element a at 0x2ca3bcaf408>]
    

    可见,返回形式是一个列表,每个元素是Element类型,其后跟了节点的名称,如html、body、div、ul、li、a等,所有节点都包含在列表中。

  • 也可以匹配指定节点名称。如果想获取所有li节点,示例如下:

    from lxml import etree
    
    #test.html的内容就是上述例子中的HTML代码
    html = etree.parse('test.html', etree.HTMLParser())
    result = html.xpath('//li')
    print(result)
    #结果为Element对象,列表形式,直接用中括号加索引
    print(result[0])
    

    选取所有li节点,可以使用//,然后直接加上节点名称即可,调用时直接使用xpath()方法即可,结果如下:

    [<Element li at 0x23438b80088>, <Element li at 0x23438b80048>, <Element li at 0x23438b80148>, <Element li at 0x23438b80188>, <Element li at 0x23438b801c8>]
    <Element li at 0x23438b80088>
    

子节点

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

    from lxml import etree
    
    html = etree.parse('test.html', etree.HTMLParser())
    #通过追加/a即选择了所有li节点的所有直接a子节点
    #//li用于选中所有li节点,/a用于选中li节点的所有直接子节点a,结合一起就是所有li节点的所有直接a子节点
    result = html.xpath('//li/a')
    print(result)
    
  • 用//获取所有子孙节点。要获取ul节点下的所有子孙节点:

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

    这个运行结果是一样的,这里使用//ul/a,就无法获取任何结果了。因为/用于获取直接子节点,而在ul节点下没有直接的子节点,只有li节点,所以无法获取任何匹配结果。

父节点

  • ..可以用来查找父节点。

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

    运行结果如下:

    ['item-1']
    
  • 还可以通过parent::来获取父节点

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

属性匹配

@符号进行属性过滤。比如选取class为item-0的li节点:

from lxml import etree
	
html = etree.parse('test.html', etree.HTMLParser())
#加入[@class="item-0"]限制节点的class属性为item-0
result = html.xpath('//li[@class="item-0"]')
print(result)

文本获取

  • text()方法选取节点的文本。如要获取上述例子中li节点内部的文本有两种方式,一种是先选取a节点再获取文本,另一种就是使用//。

  • 方法1:先选取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']
    

    这里是逐层选取的,先选取了li节点,又利用/选取了其直接子节点a,然后再选取其文本,得到的结果恰好是符合预期的。

  • 方法2:使用//

    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标签,XPath自动填补,但是是加在下一行上,故而有个换行符。

  • 总而言之,如果想要获取子孙节点内部的所有文本,可以直接用//加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']

属性多值匹配

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

from lxml import etree

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

这里的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.parse('test.html', etree.HTMLParser())
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)

运行结果如下:

['first item']

可见,通过contains()方法,第一个参数传入属性名称,第二个参数传入属性值,只要属性中包含传入的属性值,就可以完成匹配了。

多属性匹配

还有一种情况,就是根据多个属性确定一个节点,这时就需要同时匹配多个属性。此时可以用and运算符来连接,示例如下:

from lxml import etree

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

运算符及其介绍

运算符描述实例返回值
orage=19 or age=20如果age是19则返回true。如果age是21,则返回false
andage>=19 and age<20如果age是20则返回true。如果age是18,则返回false
mod计算除法的余数5 mod 21
|计算两个节点集//book | //cd返回所有拥有book和cd元素的节点集
+6 + 410
-6 - 42
*6*424
div8 div 42
=等于age=19如果age是19则返回true。如果age是20,则返回false
!=不等于age!=19如果age是18则返回true。如果age是19,则返回false
<小于age<19如果age是18则返回true。如果age是19,则返回false
<=小于或等于age<=19如果age是19则返回true。如果age是20,则返回false
>大于age>20如果age是21则返回true。如果age是19,则返回false
>=大于或等于age>=20如果age是20则返回true。如果age是19,则返回false

按序选择

在选择的时候某些属性可能同时匹配多个节点,但是只想要其中的某个节点,如第二个节点或者最后一个节点,该怎么办呢?这时可以利用中括号传入索引的方法获取特定次序的节点,实例如下:

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.parse('test.html', etree.HTMLParser())
html = etree.HTML(text)
#选取第一个li节点,[1]即可
result1 = html.xpath('//li[1]/a/text()')
print(result1)
#选取最后一个li节点,[last()]即可
result2 = html.xpath('//li[last()]/a/text()')
print(result2)
#选取位置小于3的li节点,即位置序号为1和2的节点,[position()<3]
result3 = html.xpath('//li[position()<3]/a/text()')
print(result3)
#选取倒数第三个li节点,last()-2。因为last()是最后一个,所以last()-2就是倒数第三个。
result4 = html.xpath('//li[last()-2]/a/text()')
print(result4)

运行结果如下:

['first item']
['fifth item']
['first item', 'second item']
['third item']

在xpath中提供了100多个函数,包括存取、数值、字符串、逻辑、节点、序列的等处理功能,具体作用可以参考:http://www.w3school.com.cn/xpath/xpath_functions.asp

节点轴选择

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.parse('test.html', etree.HTMLParser())
html = etree.HTML(text)
#调用ancestor轴,可以获取所有祖先节点,其后需要跟两个冒号,然后是节点选择器,使用*表示匹配所有节点
#返回的结果是第一个li节点的所有祖先节点,包括html、body、div和ul
result1 = html.xpath('//li[1]/ancestor::*')
print(result1)

#加入限制条件,在冒号后面加div,得到的结果就只有div这个祖先节点了。
result2 = html.xpath('//li[1]/ancestor::div')
print(result2)
#调用attribute轴,可以获取所有属性值,其后跟的选择器*代表获取节点的所有属性,返回值就是li节点的所有属性值
result3 = html.xpath('//li[1]/attribute::*')
print(result3)
#调用child轴,可以获取所有直接子节点,加入限定条件,选取href属性为link.html的a节点
result4 = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result4)
#调用descendant轴,可以获取所有子孙节点,加入span限定条件,返回结果只包含span节点
result5 = html.xpath('//li[1]/descendant::span')
print(result5)
#following轴,可以获取当前节点之后的所有节点
result6 = html.xpath('//li[1]/following::*[2]')
print(result6)
#following-sibling轴,可以获取当前节点之后的所有同级别节点,其后使用*匹配,获取后续所有同级节点
result7 = html.xpath('//li[1]/following-sibling::*')
print(result7)

运行结果如下:

[<Element html at 0x1410ace0148>, <Element body at 0x1410ace00c8>, <Element div at 0x1410ace0088>, <Element ul at 0x1410ace0188>]
[<Element div at 0x1410ace0088>]
['item-0']
[<Element a at 0x1410ace0248>]
[<Element span at 0x1410ace0288>]
[<Element a at 0x1410ace0208>]
[<Element li at 0x1410ace0308>, <Element li at 0x1410ace0348>, <Element li at 0x1410ace0388>, <Element li at 0x1410ace03c8>]
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值