![01873bdff66d22f6d8fafa9fd5b1aaf2.png](https://i-blog.csdnimg.cn/blog_migrate/83cc9a4404b07d982a94fc3638f564dd.jpeg)
第一部分 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](https://i-blog.csdnimg.cn/blog_migrate/105ac4d447bfee2ebaf7ac0182aa0119.jpeg)
可以看到,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](https://i-blog.csdnimg.cn/blog_migrate/628c9655bddb3e5945117149f32d6a9e.jpeg)
属性值包含相同的字符串
![473517f4ddf9a0de756a2b2fbc2f963c.png](https://i-blog.csdnimg.cn/blog_migrate/c6fec8df98ddb63014e46b6a8fe35fb1.jpeg)
对XPath返回的对象执行XPath
如果XPath语句用于查找节点, 那么返回的就是一个HtmlElement对象的列表,既然是HtmlElemtn对象,我们就还可以对其调用xpath()方法
![06144e3f300f45c02b47bddbf1c70699.png](https://i-blog.csdnimg.cn/blog_migrate/09d5df6ff9a972b11f85a6fa56222158.jpeg)
提取标签的文本
我们希望获取下面这段html中所有的文字
![06b8b6dee858b50b4f805b70340b9a42.png](https://i-blog.csdnimg.cn/blog_migrate/31f5ed1ad04bb8a079477f355b2f2c12.jpeg)
我们发现返回的列表中没有我们想要的信息, 这是因为真正属于<div>标签的文字信息只有换行和空格. XPath并不会自动把子标签的文字提取出来
这种情况下, 我们应该先获取<div>这个节点, 接着对这个节点再使用一次xpath('string(.)'), 提取整个节点里面的字符串
![9054014c0d8e96a27d1fb43745a6b787.png](https://i-blog.csdnimg.cn/blog_migrate/5ae84e88abd8785b4c2900179086b01a.jpeg)
我们看到返回的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](https://i-blog.csdnimg.cn/blog_migrate/c1ac89d467248dc6a4f39287997f7764.jpeg)
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](https://i-blog.csdnimg.cn/blog_migrate/4d2161c7a6e444c63215937c44cf69d8.jpeg)
![512ec44829aa7f2c413d2f6348cbaab6.png](https://i-blog.csdnimg.cn/blog_migrate/f5ed26ed764cd4eaedfeb01e4f5feedf.jpeg)
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>]
'''