lXml与XPath的使用

什么是XPath?

XPath,全称为XML Path language,是一种用于在XML文档中查找节点的语言,广泛应用于网页元素的定位。
在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点,在使用传统的id与class进行匹配时往往会遇到动态的元素出现匹配不到的问题,由此一般使用xpath进行节点的精确匹配。

XPath初体验

在学习xpath之前,我们先了解一下xml的结构:

<?xml version="1.0" encoding="UTF-8"?>  
<bookstore>  
  <book>  
    <title lang="en">Harry Potter</title>  
    <author>J K. Rowling</author>  
    <year>2005</year>  
    <price>29.99</price>  
  </book>  
</bookstore>

在这个xml中,有这些节点:
bookstore (文档节点)
J K. Rowling (元素节点)
lang="en" (属性节点)
节点中的值我们一般称为基本值,节点中存在父子与同胞的对应关系,例如bookstore是book/title/year/author等标签的父标签,book是bookstore的子标签等等,根据层级还可以分为先辈、后代等关系,但在匹配中一般只关心父子关系。

Xpath基本语法

在上文我们已经了解了xml标签以及它的基本对应关系,依照对应关系我们来学习xpath的基本语法
XPath 使用路径表达式来选取 XML 文档中的节点或节点集。节点是通过沿着路径 (path) 或者步 (steps) 来选取的,下面是它的基本用法:

表达式 描述
节点名 选取此节点的所有子节点。
/ 从根节点选取(取子节点)。
. 选取当前节点
.. 选取当前节点的父节点
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置(取子孙节点)。
@ 选择对应属性的节点
谓语:类似于编程语言中的数组下标,用于选择返回匹配集合中的第n个元素节点,不同的是它可以执行一些运算,下面是一些简单的表达式:
表达式 描述
/父节点/子节点集合[1] 选取子节点集合中的第一个元素
/父节点/子节点集合[last()] 选取子节点集合的最后一个元素
/父节点/子节点集合[last()-1] 选取子节点集合的倒数第二个元素
/父节点/子节点[position()❤️] 选取子节点集合的前两个元素
//选取的标签名[@元素名] 选取所有带有元素名的对应标签元素
//选取的标签名[@属性名='属性值'] 选取所有属性名为对应属性值的对应标签元素
/父节点/子节点集合[子节点的子节点值>35.00] 选取所有子节点的子节点值大于35的元素
/父节点/子节点集合[指定子节点的子节点值>35.00]//指定元素 选取父节点元素中的子节点元素的所有指定元素,且其中的指定子节点元素的值须大于 35.00
通配符
通配符 描述
* 匹配任何节点
@* 匹配任何属性节点
node() 匹配任何类型节点
除了这些,xpath还可以进行计算,同时选取多种条件的节点如选取所有p与div标签:**//p //div**

快捷选取xpath

实际开发中,在网页代码过多在摸不清文档的节点时,我们还可以活用浏览器的开发者工具,通过定位元素功能选取元素代码,并使用右键复制XPath来获取对应元素的XPath

lxml:XPath活学活用

在初步了解了xpath的语法之后,我们开始结合python来使用它

先决准备

首先安装lxml,lxml是一个匹配库,用于对xml文档内容应用xpath语法

pip install lxml

然后,我们需要引入它的etree模块

#python lxml示例
from lxml import etree

etree是lxml专门用于解析xml与html的模块,带有常见的修正格式与应用xpath等功能。
为了使用xpath,我们还需要准备一个Html字符串用于匹配清洗:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>XPath 测试文档</title>
</head>
<body>
    <div id="app">
        <!-- 模块 1 -->
        <div id="module1" class="module" data-info="info1">
            <p class="title">我是模块 1</p>
            <ul>
                <li>列表项 1</li>
                <li>列表项 2</li>
                <li>列表项 3</li>
            </ul>
        </div>

        <!-- 模块 2 -->
        <div id="module2" class="module" data-info="info2">
            <p class="title">我是模块 2</p>
            <ul>
                <li>列表项 A</li>
                <li>列表项 B</li>
                <li>列表项 C</li>
            </ul>
        </div>

        <!-- 模块 3 (含条件判断) -->
        <div id="module3" class="module" data-info="info3">
            <p class="title">我是模块 3</p>
            <p class="description">描述文本</p>
            <a href="https://example.com" id="link">点击我</a>
        </div>

        <!-- 数据展示区域 -->
        <div id="datav">
            <div id="data">
                我是一段数据
                <span class="highlight">高亮内容</span>
            </div>
            <div id="additionalData" class="additional">
                额外的数据
            </div>
        </div>
    </div>

    <!-- 页脚区域 -->
    <footer>
        <p>&copy; 2025 测试公司</p>
        <p id="footer-note">这是页脚备注</p>
    </footer>
</body>
</html>

接着我们复制这一段测试文档,加上三引号转义为字符串并转义为xpath可处理的elment对象:

from lxml import etree  
html_test="""  
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">    
    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>XPath 测试文档</title>  
</head>  
<body>  
    <div id="app">        <!-- 模块 1 -->        <div id="module1" class="module" data-info="info1">            <p class="title">我是模块 1</p>            <ul>                <li>列表项 1</li>                <li>列表项 2</li>                <li>列表项 3</li>            </ul>        </div>  
        <!-- 模块 2 -->        <div id="module2" class="module" data-info="info2">            <p class="title">我是模块 2</p>            <ul>                <li>列表项 A</li>                <li>列表项 B</li>                <li>列表项 C</li>            </ul>        </div>  
        <!-- 模块 3 (含条件判断) -->  
        <div id="module3" class="module" data-info="info3">            <p class="title">我是模块 3</p>            <p class="description">描述文本</p>  
            <a href="https://example.com" id="link">点击我</a>  
        </div>  
        <!-- 数据展示区域 -->        <div id="datav">            <div id="data">                我是一段数据  
                <span class="highlight">高亮内容</span>  
            </div>            <div id="additionalData" class="additional">                额外的数据  
            </div>        </div>    </div>  
    <!-- 页脚区域 -->    <footer>        <p>&copy; 2025 测试公司</p>  
        <p id="footer-note">这是页脚备注</p>  
    </footer></body>  
</html>  
  
"""  
html_elment=etree.HTML(html_test)  
print(html_elment)

在做好以上这些数据准备后,我们就可以来练习语法了

语法练习

选取所有div节点:

div=html_elment.xpath("//div")

选取所有div下的p节点:

p=html_elment.xpath("//div/p")

选取文档内第一个div节点:

first_div=html_elment.xpath("(//div)[1]")

选取所有class名为module的节点:

module=html_elment.xpath("//*[@class='moudle']")

选取所有id为data的节点:

id=html_elment.xpath("//*[@id='data']")

在打印执行了xpath的对象后,我们可以看到它仍然为一个elment对象,想要看到它的内容,我们可以使用text属性与tostring方法:

#获取所有div节点的内容and文本
div=html_elment.xpath("//div")
for index,d in enumerate(div):
#使用enumerate方法获取迭代索引迭代
    if d.text is not None and d.text.strip():
    #判空,需要使用strip方法去除空字符来判定空字符串  
        print(f"第{index}个元素的文本内容为:{d.text}")  
        print(f"第{index}个元素的内容为:\n{etree.tostring(d,encoding='unicode')}")
        #中文需要使用unicode编码,不然无法正常显示  
    else:  
        print(f"第{index}个元素的文本内容为:空")

在获取到内容后,我们可能还需要将内容保存起来:

扩展:内容存储

导入csv模块:

import csv

创建csv并同时创建写入流/写入器:

with open("保存示例.csv",mode='w',encoding='utf-8',newline='') as file:
#创建csv写入流
write=csv.writer(file)
#写入表头
write.writerow(['序号','内容'])

for迭代写入csv:

for index,d in enumerate(div):
xh=index
nr=d.text
write.writerow([xh,nr])

完整代码:

import csv 
#创建文件,写模式,编码为utf-8,
with open("保存示例.csv",mode='w',encoding='utf-8',newline='') as file:  
#创建csv写入流  
    write=csv.writer(file)  
#写入表头  
    write.writerow(['序号','内容'])  
    for index,d in enumerate(div):  
        xh=index  
        nr=d.text  
        write.writerow([xh,nr])

可以看到,保存的方法比较简单,只需要writer方法传入文件创建写入流,再通过wirterow方法写入表头与数据即可,在后面我们接触到pandas后,保存会更加简单快捷。
学习到这儿,应该能发现一个问题:获取到的这些节点内的数据杂七杂八的,我如何获取到指定阶段的数据呢?你可能会想到存储后使用excel来进行筛选,但如果数据量非常大的情况下,如何使用一个合适的工具来筛选它呢?

posted @ 2025-07-31 22:13  fhyxz1  阅读(21)  评论(0)    收藏  举报