etree解析xml_【S01E01】lxml.html、lxml.etree和Xpath

01873bdff66d22f6d8fafa9fd5b1aaf2.png
第一部分 lxml.html和XPath

I. 解析HTML
II. XPath语法


第二部分 lxml.etree和XPath

I.    _Element对象和HtmlElement对象
II.   Xpath语法
III.  查找节点
     (1)所有节点
     (2)子节点
     (3)父节点
     (4)属性匹配

IV.   获取信息
      (1)获取文本
      (2)获取属性
V.    匹配有多个值的属性
VI.   根据多个属性确定一个点
VII.  按序选择节点VIII. 节点轴的选择

第一部分 lxml.html和XPath

lxml.html是用来处理HTML的Python专用库,它基于lxml的HTML parser, 但是为HTML元素提供了特殊的API和用于HTML处理的很多实用工具。

它主要的API是基于lxml.etree的,但是使用起来更方便。

I. 解析HTML

d812d4642be80811a460f09153a58bc8.png

可以看到,selector是一个HtmlElement元素

II. XPath语法

XPath是一种查询语言, 它能在XML和HTML的树状结构中寻找节点,在lxml.html中,我们通过对HtmlElement对象调用xpath()方法来应用具体XPath语句

核心思想:写XPath就是写地址;形象一点来说, XPath就是一种根据"地址"来"找人"的语言

获取文本: //标签1[@属性1="属性值1"]/标签2[@属性2="属性值2"]/.../text()  
获取属性: //标签1[@属性1="属性值1"]/标签2[@属性2="属性值2"]/.../@属性  

其中[@属性="属性值"]不是必须的, 它的作用是过滤相同的标签. 在不需要过滤相同标签的情况下可以省略.

标签1的选取

标签1可以直接从html的最外层标签开始:/html/body/div[@class="useful"]/ul/li/text()(当以html开头的时候, 它前面是单斜线)

但这样纯属多此一举, 从需要提取的内容倒着往上找标签, 找到一个拥有"标志性属性值"的标签, 以它作为开头即可

如前面的//div[@class="useful"]/ul/li/text()

哪些属性可以省略

  • 标签本身就没有属性
  • 标签有属性, 但这个标签的所有属性值都相同, 如
<li class="info">我需要的信息1</li>
<li class="info">我需要的信息2</li>
<li class="info">我需要的信息3</li>

属性值以相同字符串开头

d437e50a6433cb5298fbeee921a6605b.png

属性值包含相同的字符串

473517f4ddf9a0de756a2b2fbc2f963c.png

对XPath返回的对象执行XPath

如果XPath语句用于查找节点, 那么返回的就是一个HtmlElement对象的列表,既然是HtmlElemtn对象,我们就还可以对其调用xpath()方法

06144e3f300f45c02b47bddbf1c70699.png

提取标签的文本

我们希望获取下面这段html中所有的文字

06b8b6dee858b50b4f805b70340b9a42.png
直接获取div标签下的文本

我们发现返回的列表中没有我们想要的信息, 这是因为真正属于<div>标签的文字信息只有换行和空格. XPath并不会自动把子标签的文字提取出来

这种情况下, 我们应该先获取<div>这个节点, 接着对这个节点再使用一次xpath('string(.)'), 提取整个节点里面的字符串

9054014c0d8e96a27d1fb43745a6b787.png

我们看到返回的info是一个_ElementUnicodeResult,只需调用str(info)我们就可以将其转化为真正的字符串

第二部分 lxml.etree和XPath

I. _Element对象和HtmlElement对象

这两个对象是调用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>
 '''


#(1)
html = etree.HTML(text)  
#调用HTML类进行初始化,构造一个节点对象,一个XPath解析对象,(顺便也修正了HTML字符串)
#type(html):    <class 'lxml.etree._Element'>
#lxml 使用etree._Element和 etree._ElementTree来分别代表树中的节点和树


#(2)
html2 = etree.parse(r"C:UsersbyqpzDesktophtml.html",etree.HTMLParser()) 
#读取html文件进行解析
#构造一个节点树对象,一个XPath解析对象
#type(html2):   <class 'lxml.etree._ElementTree'>

'''
result = etree.tostring(html)  #调用tostring()方法可以输出修正后的HTML代码,但结果是bytes类型                               
                               #type(result):<class 'bytes'>

                             


print(result.decode('utf-8'))  #这里用decode()方法将其转成str类型
                               #type(result.decode('utf-8')):<class 'str'>


#或 str()方法
print(str(result,encoding='utf-8'))

print(type(result))  #两种方法都不会改变result
'''


print(html.xpath('//li/@class'))  # ['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']

II. Xpath语法

nodename                    选取此节点的所有子节点
/                           选取直接子节点
//                          选取子孙节点
.                           选取当前节点
..                          选取当前节点的父节点
@                           选取属性

举例

578648361e52b71465e4b0a55b35de2d.png

III. 查找节点

(1)所有节点

from lxml import etree

html = etree.parse(r"C:UsersbyqpzDesktophtml.html",etree.HTMLParser())

result = html.xpath('//*')    # //开头的XPath规则选取所有符合要求的节点
                              #*代表匹配所有节点,整个HTML中的节点都会被获取,                              
                              #type(result)      <class 'list'>
                              #type(result[0])   <class 'lxml.etree._Element'>     

result2 = html.xpath('//li')   #选取所有的li节点,返回一个Element对象的列表

print(result2)  #返回一个Element对象的列表

print(result2[0])

'''
运行结果:
[<Element li at 0x3d68f58>,<Element li at 0x3d68f30>,
<Element li at 0x3d68f08>, <Element li at 0x3d68c60>,
<Element li at 0x3d68ad0>]


<Element li at 0x3d68f58>

'''

(2)子节点

from lxml import etree

html = etree.parse(r"C:UsersbyqpzDesktophtml.html",etree.HTMLParser())


result = html.xpath('//li/a')  #//li用于选中所有li节点,/a用于选中li节点的所有直接子节点a
                                #两者组合在一起即获取所有li节点的所有直接a子节点


result2 = html.xpath('//ul//a') #获取ul节点下所有的子孙a节点
                                 #/用于获取直接子节点,//用于获取子孙节点

print(result)

(3)父节点

from lxml import etree

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

html = etree.parse(r"C:UsersbyqpzDesktophtml.html",etree.HTMLParser())


result = html.xpath('//a[@href="link4.html"]/../@class')  
#先选中href属性为link4.html的a节点,再获取其父节点,然后再获取其class属性
#返回一个_ElementUnicodeResult对象的列表,而不是字符串的列表

result2 = html.xpath('//a[@href="link4.html"]/parent::*/@class')  
#用parent轴获取其父节点


print(result)    #运行结果: ['item-1']

(4)属性匹配

from lxml import etree

html = etree.parse(r"C:UsersbyqpzDesktophtml.html",etree.HTMLParser())

result = html.xpath('//li[@class="item-0"]') 
#用@符号进行属性过滤,选取class属性为item-0的li节点,返回一个Element对象的列表

print(result)  # [<Element li at 0x3839f08>, <Element li at 0x3839ee0>]

IV. 获取信息

(1)获取文本

#text()和@属性 得到的都是_ElementUnicodeResult对象的列表,而不是字符串的列表

from lxml import etree

t = '''
<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>
    </ul>
 </div>
</body></html>
'''

html = etree.HTML(t)  
#html = etree.parse(r"C:UsersbyqpzDesktophtml.html",etree.HTMLParser())
#修正没有闭合的节点这一步是在构造XPath解析对象时完成的,而不是调用etree.tostring()这一步,
#etree.tostring()只是输出了bytes型的HTML字符串



#I
result = html.xpath('//li[@class="item-0"]/text()')  
#这样获取的只是li节点内部的文本,返回一个_ElementUnicodeResult对象的列表

print(result)  # 运行结果:  ['n     ']   (因为修正后li节点尾标签换行了)

#II
result = html.xpath('//li[@class="item-0"]/a/text()')  
#这样获取的只是a节点内部的文本,返回一个_ElementUnicodeResult对象的列表

print(result)  # 运行结果:['first item', 'fifth item']


print(type(result[0]))  #运行结果为:<class 'lxml.etree._ElementUnicodeResult'>,


#III
result = html.xpath('//li[@class="item-0"]//text()') 
#这样获取的是li节点和a节点内部的文本,返回一个_ElementUnicodeResult对象的列表



print(result)  #运行结果为:['first item', 'fifth item', 'n     ']

(2)获取属性

#text()和@属性得到的都是_ElementUnicodeResult对象的列表,而不是字符串的列表

from lxml import etree

html = etree.parse(r"C:UsersbyqpzDesktophtml.html",etree.HTMLParser())

result = html.xpath('//li/a/@href') 
#获取所有li节点下所有a子节点的href属性,返回一个 _ElementUnicodeResult 对象的列表,而不是字符串的列表

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

print(type(result[0]))  #运行结果为:<class 'lxml.etree._ElementUnicodeResult'>

V. 匹配有多个值的属性

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()') 
#通过contains()方法,第一个参数传入属性名,第二个参数传入属性值
#只要此属性包含所传入的属性值,就可以完成匹配

print(result)  #返回一个lxml.etree._ElementUnicodeResult对象的列表  ['first item'] ,而不是字符串的列表

print(type(result[0])) #运行结果:<class 'lxml.etree._ElementUnicodeResult'>

VI. 根据多个属性确定一个点

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

print(result)

53f160c10e8a12d13643e4521968c2ab.png

512ec44829aa7f2c413d2f6348cbaab6.png
完整的XPath运算符

VII. 按序选择节点

#通过中括号传入索引获取特定次序的节点

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()')  #索引从1开始,选取第一个节点
print(result) #结果为:['first item']

result = html.xpath('//li[last()]/a/text()')  #中括号中传入last(),选取最后一个节点
print(result) #结果为:['fifth item']

result = html.xpath('//li[position()<3]/a/text()')
#选取位置小于3的li节点,也就是索引为1和2的节点,得到的结果就是前两个li节点
print(result) #结果为:['first item', 'second item']

result = html.xpath('//li[last()-2]/a/text()') 
#选取倒数第三个li节点,因为last()是最后一个,所以last()-2就是倒数第三个
print(result) #结果为:['third item']

VIII. 节点轴的选择

轴(axes):相对于当前节点的节点集

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

#ancestor轴
html = etree.HTML(text)
result = html.xpath('//li[1]/ancestor::*')  #ancestor轴获取当前节点的所有祖先节点
print(result)
'''
结果为:
[<Element html at 0x2ce8d28>, <Element body at 0x2d286e8>, <Element div at 0x2d286c0>, 
<Element ul at 0x2d28698>]
'''
result = html.xpath('//li[1]/ancestor::div')
print(result)
'''
结果为:
[<Element div at 0x2d286c0>]
'''

#attribute轴
result = html.xpath('//li[1]/attribute::*') 
#attribute轴获取当前节点的所有属性的属性值,返回一个_ElementUnicodeResult对象的列表
print(result) #运行结果为:['item-0']

print(type(result[0]))   #运行结果为:<class 'lxml.etree._ElementUnicodeResult'>

#child轴
result = html.xpath('//li[1]/child::*')  #child轴获取当前节点的所有直接子节点
print(result)
'''
结果为:
[<Element a at 0x2d286c0>]
'''

#descendant轴
result = html.xpath('//li[1]/descendant::span') #descedant轴获取当前节点的所有子孙节点
print(result)
'''
结果为:
[<Element span at 0x2d28698>]
'''

#following轴
result = html.xpath('//li[1]/following::*[2]') #following轴获取当前节点之后的所有节点
print(result)
'''
结果为:
[<Element a at 0x2d286e8>]
'''

#following-sibling轴
result = html.xpath('//li[1]/following-sibling::*') 
#following-sibling轴获取当前节点之后的所有同级节点
print(result)

'''
结果为:
[<Element li at 0x2d28698>, <Element li at 0x2d286c0>, <Element li at 0x2d369b8>, 
<Element li at 0x2d369e0>]
'''
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值