首页Python4.请求库——lxml

4.请求库——lxml

通过lxml库,可以利用XPath对HTML进行解析操作。

1.lxml概述:

lxml库是基于libxml2XML解析库的Python库,该模块使用C语言编写,使用它可以轻松处理XML和HTML文件,这个库的主要优点是易于使用,在解析大型文档时速度非常快,归档的也非常好,并且提供了简单的转换方法来将数据转换为Python数据类型,从而使文件操作更容易。
lxml库利用Xpath语法可以快速地定位特定的元素或节点
使用pip install lxml命令安装。
lxml库中大部分功能都位于lxml.etree模块中,导入lxml.etree模块的常见方式如下:

from lxml import etree

2.XPath概述:

XPath(XML Path Language)即XML路径语言,最初用于搜索XML文档,但是同样适用于HTML文档。XPath提供了非常简洁明了的路径选择表达式。另外,还提供了100多个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。

XPath常用规则有以下几种:

表达式说明
nodename选择此节点的所有子节点
/从当前节点选取直接子节点
//从当前节点选取子孙节点
.选取当前节点
..选取当前节点的父节点
@选取属性
*通配符,选择所有元素节点与元素名
@*选取所有属性
[@attrib]选取具有给定属性的所有元素
[@attrib='value']选取给定属性具有给定值的所有元素
[tag]选取所有具有指定元素的直接子节点
[tag='text']选取所有具有指定元素并且文本内容是text节点

例如,匹配规则//title[@lang='eng']表示选择所以名称为title,同时属性lang的值为eng的所有子孙节点。

3.lxml使用:

以如下html为例(缺少body、html的闭合标签):

html_text = """
<html>
    <head>
        <title>The Dormouse's story</title>
    </head>
    <body>
        <div class="story">
            <ul class="list_elements ul_first" id="list_eng" name="elements">
                <li class="list_1 list_active">
                    <a href='link1.html'><span>AAA</span></a>
                </li>
                <li class="list_2 list_two"><a href='link2.html'>BBB</a></li>
                <li class="list_3"><a href='link3.html'>CCC</a></li>
                <li class="list_4"><a href='link4.html'>DDD</a></li>
            </ul>
        </div>

3.1 读取并解析HTML

在解析HTML代码的时候,如果HTML代码不规范或者不完整,lxml解析器会自动修复或补全代码,从而提高效率。

from lxml import etree

html = etree.HTML(html_text)
# 调用tostring()方法即可输出修正后的html,但是属于bytes类型,调用decode('utf-8')方法将其转化为str类型
result = etree.tostring(html).decode('utf-8')

print(result)

运行结果如下:

<html>
    <head>
        <title>The Dormouse's story</title>
    </head>
    <body>
        <div class="story">
            <ul class="list_elements ul_first" id="list_eng" name="elements">
                <li class="list_1 list_active">
                    <a href="link1.html"><span>AAA</span></a>
                </li>
                <li class="list_2 list_two"><a href="link2.html">BBB</a></li>
                <li class="list_3"><a href="link3.html">CCC</a></li>
                <li class="list_4"><a href="link4.html">DDD</a></li>
            </ul>
        </div>
    </body>
</html>

另外,也可以直接读取文本文件进行解析。

from lxml import etree

# 读取文本文件,当标签有错误时,需要使用 etree.HTMLParser()
html = etree.parse('html_text.txt' , etree.HTMLParser())
# 将html内容序列化
result = etree.tostring(html).decode('utf-8')

print(result)

3.2 选取所有符合要求的节点

使用以//开头的XPath规则,选择所有符合要求的节点。
使用//加节点名称,获取指定的所有节点。

from lxml import etree

html = etree.HTML(html_text)

# 选取HTML中的所有节点
result_1 = html.xpath('//*')
print(result_1)
# 选取所有li节点
result_2 = html.xpath('//li')
print(result_2)

运行结果如下:

[<Element html at 0x17a7d46f848>, <Element head at 0x17a7d46f808>, <Element title at 0x17a7d46f788>, <Element body at 0x17a7d46f888>, <Element div at 0x17a7d46f8c8>, <Element ul at 0x17a7d46f948>, <Element li at 0x17a7d46f988>, <Element a at 0x17a7d46f9c8>, <Element span at 0x17a7d46fa08>, <Element li at 0x17a7d46f908>, <Element a at 0x17a7d46fa48>, <Element li at 0x17a7d46fa88>, <Element a at 0x17a7d46fac8>, <Element li at 0x17a7d46fb08>, <Element a at 0x17a7d46fb48>]
[<Element li at 0x17a7d46f988>, <Element li at 0x17a7d46f908>, <Element li at 0x17a7d46fa88>, <Element li at 0x17a7d46fb08>]

3.2 选取所有符合要求的子节点或子孙节点

使用///规则,选择所有符合要求的子节点或子孙节点。

from lxml import etree

html = etree.HTML(html_text)

# 所有li节点的所有直接子节点a
result_1 = html.xpath('//li/a')
print(result_1)
print('----------')
# 所有ul节点的所有直接子节点a
result_2 = html.xpath('//ul/a')
print(result_2)
print('----------')
# 所有ul节点的所有子孙节点a
result_3 = html.xpath('//ul//a')
print(result_3)

运行结果如下:

[<Element a at 0x1854f2df8c8>, <Element a at 0x1854f2df848>, <Element a at 0x1854f2df948>, <Element a at 0x1854f2df988>]
----------
[]
----------
[<Element a at 0x1854f2df8c8>, <Element a at 0x1854f2df848>, <Element a at 0x1854f2df948>, <Element a at 0x1854f2df988>]

3.3 选取所有符合要求的父节点

使用..parent::规则,选择所有符合要求的父节点。

from lxml import etree

html = etree.HTML(html_text)

# 选取href属性为"link1.html"的a节点的父节点
result_1 = html.xpath('//a[@href="link1.html"]/..')
print(result_1)
print('----------')
# 选取href属性为"link1.html"的a节点的父节点
result_2 = html.xpath('//a[@href="link1.html"]/parent::*')
print(result_2)
print('----------')
# 选取href属性为"link1.html"的a节点的父节点的clas属性
result_3 = html.xpath('//a[@href="link1.html"]/../@class')
print(result_3)

运行结果如下:

[<Element li at 0x1f37fc5f848>]
----------
[<Element li at 0x1f37fc5f848>]
----------
['list_1 list_active']

3.4 属性匹配

使用@实现节点属性匹配。

from lxml import etree

html = etree.HTML(html_text)

# 获取class属性为"list_4"的li节点
result = html.xpath('//li[@class="list_4"]')
print(result)

运行结果如下:

[<Element li at 0x1c763631788>]

3.5 属性多值匹配

当某个节点的某个属性有多个值时,上面属性匹配方法就无法使用。
此时,需要使用contains()方法。

from lxml import etree

html = etree.HTML(html_text)

# 属性匹配方法无法使用
result_1 = html.xpath('//li[@class="list_2"]')
print(result_1)
# 使用contains()方法
result_2 = html.xpath('//li[contains(@class , "list_2")]')
print(result_2)

运行结果如下:

[]
[<Element li at 0x268fc66f848>]

3.6 多属性匹配

有时需要根据多个属性来选取节点,即需要同时匹配多个属性,此时,就需要使用到运算符。

from lxml import etree

html = etree.HTML(html_text)

# 获取class属性包含"ul_first",并且name属性为"elements"的ul节点
result_1 = html.xpath('//ul[contains(@class , "ul_first") and @name="elements"]')
print(result_1)
print('----------')
# 获取class属性包含"list_1",或者包含"list_2"的li节点
result_2 = html.xpath('//li[contains(@class , "list_1") or contains(@class , "list_2")]')
print(result_2)

运行结果如下:

[<Element ul at 0x1e3797bf888>]
----------
[<Element li at 0x1e3797bf808>, <Element li at 0x1e3797bf908>]

可用在 XPath 表达式中的运算符有以下几种(来源W3school):

运算符描述实例返回值
|计算两个节点集//book | //cd返回所有拥有 book 和 cd 元素的节点集
+加法6 + 410
-减法6 – 42
*乘法6 * 424
div除法8 div 42
=等于price=9.80如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。
!=不等于price!=9.80如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
<小于price<9.80如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
<=小于或等于price<=9.80如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
>大于price>9.80如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
>=大于或等于price>=9.80如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。
orprice=9.80 or price=9.70如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
andprice>9.00 and price<9.90如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
mod计算除法的余数5 mod 21

3.7 获取文本

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

from lxml import etree

html = etree.HTML(html_text)

# text()方法前为/,表示获取li节点的直接子节点a,而文本在a节点内,因此无法成功获取
result_1 = html.xpath('//li[@class="list_3"]/text()')
print(result_1)
print('----------')
# 先选取li节点的直接子节点a,然后再获取文本
result_2 = html.xpath('//li[@class="list_3"]/a/text()')
print(result_2)
print('----------')
# 使用//获取li节点内部的文本
result_3 = html.xpath('//li[@class="list_3"]//text()')
print(result_3)

运行结果如下:

[]
----------
['CCC']
----------
['CCC']

3.8 获取属性值

使用@获取节点属性。

from lxml import etree

html = etree.HTML(html_text)

# 获取所有li节点的class属性值
result_1 = html.xpath('//li/@class')
print(result_1)
print('----------')
# 获取所有li节点的直接子节点a的href属性值
result_2 = html.xpath('//li/a/@href')
print(result_2)

运行结果如下:

['list_1 list_active', 'list_2 list_two', 'list_3', 'list_4']
----------
['link1.html', 'link2.html', 'link3.html', 'link4.html']

3.9 按序选择

在使用某些属性选取节点时,可能同时匹配到多个节点,此时,可以通过索引的方式来获取特定次序的节点。

from lxml import etree

html = etree.HTML(html_text)

# 选择第一个li节点,需要注意的是,序号是从1开始
result_1 = html.xpath('//li[1]/a/span/text()')
print(result_1)
print('----------')
# 选择最后一个li节点
result_2 = html.xpath('//li[last()]/a/text()')
print(result_2)
print('----------')
# 选择位置小于4的li节点,即第1、2、3三个节点
result_3 = html.xpath('//li[position()<4]/a//text()')
print(result_3)
print('----------')
# 选择倒数第二个节点
result_4 = html.xpath('//li[last()-1]/a/text()')
print(result_4)

运行结果如下:

['AAA']
----------
['DDD']
----------
['AAA', 'BBB', 'CCC']
----------
['CCC']

3.10 节点轴选择

  • 祖先节点

调用ancestor轴,获取祖先节点。

from lxml import etree

html = etree.HTML(html_text)

# 获取第一个li节点的所有祖先节点
result_1 = html.xpath('//li[1]/ancestor::*')
print(result_1)
print('----------')
# 获取第一个li节点的所有祖先节点中的ul节点
result_2 = html.xpath('//li[1]/ancestor::ul')
print(result_2)

运行结果如下:

[<Element html at 0x1d8fa34f8c8>, <Element body at 0x1d8fa34f888>, <Element div at 0x1d8fa34f808>, <Element ul at 0x1d8fa34f908>]
----------
[<Element ul at 0x1d8fa34f908>]
  • 子孙节点

调用child轴,获取所有直接子节点。

from lxml import etree

html = etree.HTML(html_text)

# 获取第一个li节点的直接子节点
result_1 = html.xpath('//li[1]/child::*')
print(result_1)
print('----------')
# 获取第一个li节点的子节点中href属性值为"link1.html"的a节点
result_2 = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result_2)

运行结果如下:

[<Element a at 0x2856c53f8c8>]
----------
[<Element a at 0x2856c53f8c8>]

调用descendant轴,获取所有子孙节点。

from lxml import etree

html = etree.HTML(html_text)

# 子孙节点
result_5 = html.xpath('//li[1]/descendant::*')
print(result_5)

运行结果如下:

[<Element a at 0x1c06b4117c8>, <Element span at 0x1c06b411748>]
  • 当前节点之后的所有节点
from lxml import etree

html = etree.HTML(html_text)

# 第一个li节点之后的所有节点
result = html.xpath('//li[1]/following::*')
print(result)

运行结果如下:

[<Element li at 0x1d7fa531788>, <Element a at 0x1d7fa531708>, <Element li at 0x1d7fa531808>, <Element a at 0x1d7fa531848>, <Element li at 0x1d7fa531888>, <Element a at 0x1d7fa531908>]
  • 当前节点之后的所有同级节点
from lxml import etree

html = etree.HTML(html_text)

# 第一个li节点之后的所有同级节点
result = html.xpath('//li[1]/following-sibling::*')
print(result)

运行结果如下:

[<Element li at 0x219f9f71748>, <Element li at 0x219f9f71848>, <Element li at 0x219f9f71888>]
  • 获取节点的所有属性值
from lxml import etree

html = etree.HTML(html_text)

# 获取ul节点的所有属性值
result = html.xpath('//ul/attribute::*')
print(result)

运行结果如下:

['list_elements ul_first', 'list_eng', 'elements']
RELATED ARTICLES

欢迎留下您的宝贵建议

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments