Python 使用 XPath 查找元素基本用法实战指南
前言
在爬虫、网页数据提取场景中,解析HTML常用两种方案:CSS选择器、XPath。
XPath 功能更强大,支持父子层级、属性模糊匹配、文本匹配、多条件筛选,是 Python 爬虫主流解析方式。
很多同学刚开始使用 XPath 经常遇到路径写错、找不到元素、匹配规则失效等问题。
本文基于lxml(最常用库)讲解 XPath 在 Python 中的基础语法、常用案例、避坑要点,所有代码可直接运行。
说明:本文使用XPath 1.0,lxml 仅支持 XPath 1.0,部分 XPath2.0 函数无法使用。
一、环境准备
安装依赖
pip install lxml基础载入模板
from lxml import etree html = """ <html> <body> <div id="box"> <h2 class="title">Python XPath教程</h2> <div class="item">文章1</div> <div class="item">文章2</div> <div id="c_001">ID以c开头的区块</div> <div id="d_001">普通区块</div> <a href="https://www.baidu.com">百度</a> </div> </body> </html> """ # 构建解析对象 tree = etree.HTML(html)二、绝对路径 vs 相对路径
1. 绝对路径(不推荐)
从根节点一层层往下写,结构一旦变动直接失效。
# 查找h2文本 res = tree.xpath('/html/body/div/h2/text()') print(res)2. 相对路径(日常开发首选)
//标签名:全局搜索,任意位置查找该标签.//标签名:在当前节点内部搜索(重点!)
# 全局查找所有h2 res = tree.xpath('//h2/text()') # 先拿到box节点,再在box内部查找所有div box_node = tree.xpath('//div[@id="box"]')[0] items = box_node.xpath('.//div/text()') print(items)⚠️ 高频踩坑:在节点对象上调用xpath,如果不加
.,//依旧会全局搜索,不会限定在当前节点!
三、获取文本、属性、节点对象
1./text()获取标签内直接文本
# 获取h2内部文本 title = tree.xpath('//h2/text()')[0] print(title)2.@属性名获取属性值
# 获取a标签href链接 href = tree.xpath('//a/@href')[0] print(href)3. 不使用/text(),直接获取元素对象
想要打印元素完整HTML源码时,必须拿到节点对象,不能提取文本
h2_node = tree.xpath('//h2')[0] # 输出完整标签HTML html_str = etree.tostring(h2_node, encoding="utf-8").decode("utf-8") print(html_str)四、属性筛选语法(最常用)
4.1 属性精确匹配
//div[@id="box"] //h2[@class="title"]python示例:
node = tree.xpath('//div[@id="box"]')坑点:class 内存在多余空格时,精确匹配会失败!例如
class="element-header "
4.2 模糊匹配(三大核心函数)
lxml支持XPath1.0内置三个函数:
starts-with(@属性, "开头字符")属性以指定内容开头
# 查找id以c开头的div c_divs = tree.xpath('//div[starts-with(@id, "c")]')contains(@属性, "关键词")属性包含关键词
# class包含item的div item_divs = tree.xpath('//div[contains(@class, "item")]')ends-with()不支持!属于XPath2.0,lxml无法使用。
4.3 多条件组合and / or
# class=item 并且文本包含文章 res = tree.xpath('//div[@class="item" and contains(text(), "文章")]/text()')五、层级关系筛选
/直接子节点
//div[@id="box"]/h2只匹配box下直接子元素h2,孙子层级不会匹配
//后代所有节点(子、孙、曾孙)
//div[@id="box"]//div- 匹配第N个元素
[position()]
# 第一个class为item的div first_item = tree.xpath('//div[@class="item"][1]/text()') # 最后一个 last_item = tree.xpath('//div[@class="item"][last()]/text()')六、文本匹配技巧
匹配标签内文本
# 文本完全等于 res = tree.xpath('//h2[text()="Python XPath教程"]/text()') # 文本包含关键词 res = tree.xpath('//h2[contains(text(), "XPath")]/text()')注意:标签存在换行、空格时,
text()精确匹配容易失败,优先使用contains
七、实战通用模板
模板1:在指定节点内部循环提取元素
# 拿到父节点 box = tree.xpath('//div[@id="box"]')[0] # 在父节点内查找所有item item_list = box.xpath('.//div[@class="item"]') for item in item_list: text = item.xpath('./text()')[0] print(text)模板2:获取元素完整HTML
def get_html(elem): return etree.tostring(elem, encoding="utf-8").decode("utf-8") item_node = tree.xpath('//div[@class="item"][0]')[0] print(get_html(item_node))八、高频踩坑清单
- 在节点对象使用xpath忘记加
.box.xpath('//div')全局搜索,box.xpath('.//div')才是内部搜索 - class带有多余空格,直接使用
@class="xxx"精确匹配失败,改用contains - 想打印HTML,却写了
/text(),拿到字符串而不是元素对象 - 混淆
/和//,需要后代却只写直接子节点/ - 试图使用
ends-with()、lower-case()函数,lxml不支持XPath2.0 - xpath匹配结果为空列表,直接
[0]取值触发报错
建议增加判断:
result = tree.xpath('//h2/text()') if result: print(result[0])九、补充:Selenium / DrissionPage 兼容说明
如果你使用 DrissionPage、Selenium,XPath语法完全通用:
# DrissionPage示例 items = page.xpath('.//div[starts-with(@id,"c")]')区别:selenium获取文本使用.text属性,不再依赖/text()。
总结
.//xxx限定当前节点内部查找,//xxx全局查找;- 精确匹配用
@属性="值",模糊匹配优先starts-with、contains; - 需要HTML源码就不要加
/text(),保留元素对象; - 优先使用相对路径,避免脆弱的绝对路径;
- lxml只支持XPath1.0,不要使用XPath2.0专属函数。
掌握以上基础用法,可以覆盖80%网页数据提取场景。复杂爬虫筛选需求,都可以在这套基础语法上进行组合扩展。
