Python lxml 库与 XPath 语法

news/2024/11/7 23:58:04/

lxml 库是 C 库 libxml2libxslt 的 Python 绑定, 可处理 XML 和 HTML 文档, 支持 XPath 语法

解析 HTML

分两种情况:

  1. 解析 HTMl 文件

当前目录新建 simple.html 文件

<html lang="en">
<head><title>XPath</title>
</head>
<body><h1>Web</h1><ul class="front"><li id="begin">HTML5</li><li>CSS3</li><li class="base js">JavaScript</li></ul><ul class="backend"><span>Go</span><li id="begin" class="base">Python</li></ul>
</body>
</html>

推荐使用 HTML 解析器(HTMLParser)而不是使用默认的解析器, 这样即便 HTML 文档不完整(例如只有开始标签而没有结束标签)也不会报错

>>> from lxml import etree>>> tree = etree.parse("./simple.html", etree.HTMLParser())
>>> type(tree)
<class 'lxml.etree._ElementTree'>>>> root = tree.getroot()
>>> type(root)
<class 'lxml.etree._Element'>

返回的 tree 是一个 ElementTree 对象, 可调用 getroot() 方法获取 HTML 的根元素对象

根元素以及使用 XPath 查找到的元素都是 Element 对象

  1. 解析 HTML 字符串
# 第一种方法
root = etree.fromstring("xxx", etree.HTMLParser())# 第二种方法
root = etree.HTML("xxx")

与 HTML 文件不同, 解析 HTML 字符串时, 直接返回根元素对象 root, 无论是 tree 还是 root, 均可调用 XPath 从根元素开始查找

获取元素详情

对于 Element 对象, 可以获取元素的以下内容:

  • 标签名称 root.tag
  • 文本内容 root.text
  • 元素属性 root.attrib['xxx'] 或者 root.get('xxx')
  • 直接子元素 root[0]
# 获取标签名称
>>> root.tag
'html'# 获取文本内容
>>> root.text
'\n\n'# 所有属性名称
>>> root.attrib.keys()
['lang']
# 获取元素属性
>>> root.get('lang')
'en'
>>> root.attrib['lang']
'en'# 子元素数量, html 有 head 和 body 两个子元素
>>> len(root)
2# 第一个直接子元素, 即 head 元素
>>> head = root[0]
>>> etree.tostring(head)
b'<head>\n    <title>XPath</title>\n</head>\n'

使用 XPath 查找元素

调用 XPath

  • x.find() 返回首个匹配的元素
  • x.path()x.findall() 均返回一个列表, 其中包含所有匹配的元素

其中 x 为 ElementTree 对象或者 Element 对象

XPath 语法

路径

  • . 表示当前元素, .. 表示上一级元素
  • / 仅匹配直接子元素, // 递归匹配所有的子元素
<body><h1>Web</h1><ul class="front"><li id="begin">HTML5</li><li>CSS3</li><li class="base js">JavaScript</li></ul><ul class="backend"><span>Go</span><li id="begin" class="base">Python</li></ul>
</body>
  1. 获取 h1 元素
>>> from lxml import etree
>>> tree = etree.parse("./simple.html", etree.HTMLParser())# . 表示 html 元素, 可省略为 tree.xpath('body/h1')
>>> tree.xpath('./body/h1')
[<Element h1 at 0x7f098a28f740>]# 使用 findall() 方法, 与 xpath 一样返回匹配元素的列表
>>> tree.findall('body/h1')
[<Element h1 at 0x7f098a28f740>]# 使用 find() 方法, 返回第一个匹配到的元素
>>> tree.find('body/h1')
<Element h1 at 0x7f098a28f740>
  • 使用 ElementTree 对象调用 XPath, 从根元素开始解析路径
  • 使用 Element 对象调用 XPath 时, 从当前元素开始解析

Element 对象在调用 etree.parse() 时就生成了, 使用 XPath 查找同一个元素, 返回相同地址的 Element 对象

  1. 获取所有 ul 元素下的 li 直接子元素
# 使用 Element 对象调用 XPath
>>> root = tree.getroot()
>>> root.xpath("//ul/li")
[<Element li at 0x7f098a28f8c0>, <Element li at 0x7f098a28f900>, <Element li at 0x7f098a28f940>, <Element li at 0x7f098a28f980>]

根据地址的增量, 一个 Element 对象占用 64 字节的空间

谓语

置于方括号 [] 中, 用于修饰待查找的对象

  • 属性相关
    • 有无属性: [@id] 表示具有 id 属性的元素
    • 属性匹配: [@id='xxx'] 表示 id 属性为 xxx 的元素
    • 属性包含: [contains(@class, 'xxx')] 表示 class 属性包含 xxx 的元素

  • 下标: [index] 表示第几个子元素, 下标从 1 开始, 最后一个使用 [last()]

  • 匹配直接子元素, 包括 [tag] 和 [tag=‘text’]
    • [li] 表示包含标签名为 li 直接子元素的元素
    • [span='Go'] 表示包含标签名为 span 且文本内容为 Go 的直接子元素的元素
<body><h1>Web</h1><ul class="front"><li id="begin">HTML5</li><li>CSS3</li><li class="base js">JavaScript</li></ul><ul class="backend"><span>Go</span><li class="base">Python</li></ul>
</body>
  1. 存在 id 属性的 li 元素
>>> root.xpath('//li[@id]')
[<Element li at 0x7f098a28f940>]
  1. 元素 class 属性等于 base 的 li 元素
>>> root.xpath('//li[@class="base"]')
[<Element li at 0x7f098a28f7c0>]

JavaScript 所在 li 元素的 class 属性具有两个值, 视为不匹配

  1. 元素 class 属性包含 base 的 li 元素
>>> root.xpath('//li[contains(@class, "base")]')
[<Element li at 0x7f098a28f8c0>, <Element li at 0x7f098a28f7c0>]
  1. class 属性为 front 的 ul 元素的第二个 li 元素
# find() 和 findall() 方法中不能以 // 开头, 需要使用相对路径 .//
>>> li_list = root.findall('.//ul[@class="front"]/li[2]')
>>> li_list
[<Element li at 0x7f098a28fc00>]
>>> li_list[0].text
'CSS3'
  1. 包含 span 且文本内容为 Go 的直接子元素的 ul 元素
>>> ul = root.find('.//ul[span="Go"]')
>>> ul
<Element ul at 0x7f098a28f9c0>
>>> ul.get('class')
'backend'

获取文本

  • 文本内容 text()
  • 属性内容 @attrib
<body><h1>Web</h1><ul class="front"><li id="begin">HTML5</li><li>CSS3</li><li class="base js">JavaScript</li></ul><ul class="backend"><span>Go</span><li class="base">Python</li></ul>
</body>
  1. 获取所有 ul 元素下 li 元素的文本内容
>>> tree.xpath("//ul/li/text()")
['HTML5', 'CSS3', 'JavaScript', 'Python']
  1. 获取所有 ul 元素的 class 属性值
>>> tree.xpath("//ul/@class")
['front', 'backend']

注意 find() 和 findall() 不支持文本节点的获取

通配符

  • * 匹配所有子元素
  • @* 匹配所有属性
<body><h1>Web</h1><ul class="front"><li id="begin">HTML5</li><li>CSS3</li><li class="base js">JavaScript</li></ul><ul class="backend"><span>Go</span><li class="base">Python</li></ul>
</body>
  1. 获取所有 ul 元素下的直接子元素
>>> tree.findall("//ul/*")
[<Element li at 0x7f238d4c99c0>, <Element li at 0x7f238d4c9a00>, <Element li at 0x7f238d4c9a40>, <Element span at 0x7f238d4c9980>, <Element li at 0x7f238d4c9a80>]

结果中不仅包含 li 元素, 还有 span 元素

  1. 获取所有 ul 元素下直接子元素的文本内容
>>> tree.xpath("//ul/*/text()")
['HTML5', 'CSS3', 'JavaScript', 'Go', 'Python']
  1. 获取所有 li 元素的属性值
>>> tree.xpath("//li/@*")
['begin', 'base js', 'base']

http://www.ppmy.cn/news/56412.html

相关文章

【Redis】关于哨兵模式

连接步骤 一.客户端遍历所有的 Sentinel 节点集合,获取一个可用的 Sentinel 节点. 二.客户端向可用的 Sentinel 节点发送 get-master-addr-by-name 命令,获取Redis Master 节点. 三.客户端向Redis Master节点发送role或role replication 命令,来确定其是否是Master节点,并且能…

HR website

2023年最新招聘渠道一览表序号大 类小 类网 站 名网 址1综合类综合类企业总部人才网www.xzqrc.cn2前程无忧www.51job.com3智联招聘www.zhaopin.com4597招聘网www.597.com5中华英才网www.chinahr.com6一览英才网www.job1000.com7今日招聘招聘求职找工作_上今日招聘网站_找工作…

利用Bridge创建虚拟网络

利用Bridge创建虚拟网络 1 安装网桥模块。 # yum install -y bridge-utils(利用系统镜像文件作为本地YUM源&#xff0c;创建本地YUM源文件) 2 查询网桥模块。 # modinfo bridge ilename: /lib/modules/3.10.0-327.el7.x86_64/kernel/net/bridge/bridge.ko alias: …

【每日一题】4978:宠物小精灵之收服

目录 一&#xff1a;题目 二&#xff1a;分析 三&#xff1a;代码实现 一&#xff1a;题目 一天&#xff0c;小智和皮卡丘来到了小精灵狩猎场&#xff0c;里面有很多珍贵的野生宠物小精灵。小智也想收服其中的一些小精灵。然而&#xff0c;野生的小精灵并不那么容易被收服…

ATTCK v12版本战术介绍——防御规避(四)

一、引言 在前几期文章中我们介绍了ATT&CK中侦察、资源开发、初始访问、执行、持久化、提权战术理论知识及实战研究、部分防御规避战术&#xff0c;本期我们为大家介绍ATT&CK 14项战术中防御规避战术第19-24种子技术&#xff0c;后续会介绍防御规避其他子技术&#xf…

【网课平台】Day16.项目优化:压测、加缓存优化与分布式锁

文章目录 一、压力测试1、优化需求2、性能指标3、安装Jmeter4、压力测试5、优化日志 二、缓存优化1、给接口加Redis缓存2、缓存穿透3、解决缓存穿透4、缓存雪崩5、缓存击穿 三、分布式锁1、本地锁的问题2、IDEA一个项目启动多个实例3、分布式锁4、Redis NX实现分布式锁5、Redis…

spring事务传播机制

目录标题 事务的传播机制&#xff08;7种&#xff09;REQUIRED举个例子 REQUIRES_NEW举个例子 SUPPORTS举个例子 NOT_SUPPORTED举个例子 MANDATORY举个例子 NEVER举个例子 NESTED举个例子 PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别 事务的传播机制&#xff08;7种&…

2的幂次方

2的幂次方 判断一个数是否为2的幂次方? 我们可以参考如下链接&#xff1a; 判断一个数是否为2的N次方 借鉴文中的分析&#xff0c;我认为&#xff1a; 其实一个数n&#xff0c;如果是2的幂次方数&#xff0c;则n的二进制原码中一定只有一个1&#xff0c;且在最高位&#xf…