Python使用XPath查找并定位没有特征的元素
作者:detayun
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" rel="external nofollow" >百度</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)
属性筛选语法(最常用)
1.属性精确匹配
//div[@id="box"] //h2[@class="title"]
python示例:
node = tree.xpath('//div[@id="box"]')
坑点:class 内存在多余空格时,精确匹配会失败!例如 class="element-header "
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无法使用。
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%网页数据提取场景。复杂爬虫筛选需求,都可以在这套基础语法上进行组合扩展。
Python如何使用XPath定位没有特征的元素
爬虫开发中经常遇到一类棘手场景:目标元素没有id、没有class、没有独特属性,无法直接通过属性筛选。
<div>
<div>标题</div>
<div>需要抓取的内容</div>
<div>备注信息</div>
</div>如上代码,所有标签完全一致,没有任何特征属性。很多人第一反应只能靠页面顺序硬写路径,页面微调一行代码爬虫直接失效。
本文基于 lxml(XPath1.0)整理一套无特征元素定位方案,包含层级索引、轴定位、相邻节点匹配、文本锚点等实战技巧,适用于静态网页解析,同时兼容Selenium、DrissionPage。
前置说明:lxml 仅支持 XPath 1.0,下文所有语法全部兼容 lxml。
环境基础模板
from lxml import etree
html = """
<div class="container">
<div>文章名称</div>
<div>Python XPath实战</div>
<div>发布时间</div>
<div>2026-08-14</div>
<div>内容摘要</div>
<div>无特征元素定位教程</div>
</div>
"""
tree = etree.HTML(html)
方案1:位置索引定位(最简单,慎用)
基础语法
[n] 代表同级第n个元素,XPath下标从1开始,不是0
//div[@class="container"]/div[2]
Python代码:
# 获取容器下第二个div
target = tree.xpath('//div[@class="container"]/div[2]/text()')
print(target)
缺点:
- 页面新增、删除任意同级标签,顺序发生变化,定位直接失效。
- 适合静态页面、结构永远不会变动的场景,不推荐作为首选方案。
拓展用法:
[last()] 匹配同级最后一个元素
//div[@class="container"]/div[last()]
position()>3 筛选位置大于3的元素
//div[@class="container"]/div[position()>3]
方案2:锚点文本定位(最推荐!业务首选)
核心思路:找到旁边有固定文本的元素作为锚点,再通过轴找到目标元素。
示例需求:已知文本文章名称,抓取紧跟其后的Python XPath实战。
1. following-sibling:: 后续同级元素
匹配当前节点后面同级兄弟标签
//div[text()="文章名称"]/following-sibling::div[1]
Python示例:
res = tree.xpath('//div[text()="文章名称"]/following-sibling::div[1]/text()')
print(res) # ['Python XPath实战']
2. preceding-sibling:: 前面同级元素
匹配当前节点前面同级兄弟标签
//div[text()="2026-08-14"]/preceding-sibling::div[1]
3. 兼容文本空格、换行问题
网页源码经常存在换行、空格,text()精确匹配容易失败,改用contains()
//div[contains(text(),"文章名称")]/following-sibling::div[1]
方案3:父子、祖先层级定位
1. parent:: 直接获取父节点
//div[contains(text(),"发布时间")]/parent::div
2. ancestor:: 向上查找任意祖先节点
适合多层嵌套,向上寻找指定父容器
//div[contains(text(),"2026-08-14")]/ancestor::div[@class="container"]
3. child:: 子节点(等价直接使用 /标签)
//div[@class="container"]/child::div[1]
方案4:组合多邻居条件,增强稳定性
页面结构复杂时,单一锚点容易匹配到多条结果,可以叠加条件过滤。
示例:找到「内容摘要」后面第一个div,并且父容器是container
//div[@class="container"]//div[contains(text(),"内容摘要")]/following-sibling::div[1]
方案5:多标签混合、嵌套场景实战
测试HTML结构(多层嵌套,无特征)
<div class="box">
<p>标题</p>
<div>
<span>无用信息</span>
<span>目标内容</span>
</div>
</div>需求:根据<p>标题</p>定位,找到后面div内部第二个span
//p[contains(text(),"标题")]/following-sibling::div//span[2]
常用XPath轴完整速查表
| 语法 | 作用 |
|---|---|
following-sibling::tag | 当前节点之后的同级兄弟 |
preceding-sibling::tag | 当前节点之前的同级兄弟 |
parent::tag | 直接父节点 |
ancestor::tag | 所有上层祖先节点 |
child::tag | 直接子节点 |
following::tag | 文档中出现在后面所有节点(不限层级) |
preceding::tag | 文档中出现在前面所有节点(不限层级) |
区分重点:following-sibling 只找同级;following 查找所有后代以外后续全部节点,范围更大,尽量优先使用 sibling,减少误匹配。
高频踩坑汇总
坑1:XPath下标从1开始,很多人习惯写[0]
# 错误,[0]匹配不到任何元素
tree.xpath('//div[0]')
坑2:文本存在换行、空格,直接text()完全匹配失败
❌ 错误写法
//div[text()="文章名称"]
✅ 推荐写法
//div[contains(text(),"文章名称")]
坑3:混淆following-sibling和following
sibling 仅限同级,范围更小,不容易匹配到页面其他位置元素,稳定性更高。
坑4:页面动态渲染(JS生成内容)
lxml只能解析静态HTML。如果元素由JS渲染,lxml无法获取节点,需要使用DrissionPage、Selenium加载页面后再执行XPath。
坑5:匹配结果为空列表直接下标取值
# 危险,找不到元素会直接报错
data = tree.xpath('xxx')[0]
# 规范写法
result = tree.xpath('xxx')
if result:
data = result[0]
实战完整示例(可直接复制运行)
from lxml import etree
html = """
<div class="info">
<div>用户名</div>
<div>张三</div>
<div>手机号</div>
<div>13800138000</div>
</div>
"""
tree = etree.HTML(html)
# 通过锚点文本,抓取后面无特征div
username = tree.xpath('//div[contains(text(),"用户名")]/following-sibling::div[1]/text()')
phone = tree.xpath('//div[contains(text(),"手机号")]/following-sibling::div[1]/text()')
print("用户名:", username[0] if username else "")
print("手机号:", phone[0] if phone else "")
方案选型建议
- 优先方案:锚点文本 + following-sibling / preceding-sibling:页面少量增减其他无关标签,只要锚点文本不变,定位依然有效,稳定性最强。
- 备选方案:位置索引
[n]:仅用于页面结构永久固定、不会改版的场景。 - 兜底方案:多层祖先路径组合:利用外层父容器特征,缩小查找范围,降低误匹配概率。
总结
面对没有id、class、独有属性的元素,不要直接硬编码顺序路径。
核心思想:利用页面上有稳定特征的周边元素作为锚点,借助XPath轴语法实现跨节点定位。
following-sibling::、preceding-sibling:: 是日常爬虫解决无特征元素最核心的两个语法。
掌握这套思路,绝大多数缺乏标记的网页节点都可以稳定定位,大幅提升爬虫鲁棒性,减少页面改版带来的维护成本。
以上就是Python使用XPath查找并定位没有特征的元素的详细内容,更多关于Python XPath查找定位元素的资料请关注脚本之家其它相关文章!
