python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python XPath查找定位元素

Python使用XPath查找并定位没有特征的元素

作者:detayun

这篇文章主要介绍了Python使用XPath查找并定位没有特征的元素,涵盖了如何在没有id/class等属性的情况下精确定位元素,文章包含完整的代码示例、轴语法速查表和常见踩坑指南,希望对大家有所帮助

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))

高频踩坑清单

  1. 在节点对象使用xpath忘记加 .box.xpath('//div') 全局搜索,box.xpath('.//div') 才是内部搜索
  2. class带有多余空格,直接使用 @class="xxx" 精确匹配失败,改用 contains
  3. 想打印HTML,却写了 /text(),拿到字符串而不是元素对象
  4. 混淆 ///,需要后代却只写直接子节点 /
  5. 试图使用 ends-with()lower-case() 函数,lxml不支持XPath2.0
  6. 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()

总结

  1. .//xxx 限定当前节点内部查找,//xxx 全局查找;
  2. 精确匹配用 @属性="值",模糊匹配优先 starts-withcontains
  3. 需要HTML源码就不要加 /text(),保留元素对象;
  4. 优先使用相对路径,避免脆弱的绝对路径;
  5. 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 "")

方案选型建议

  1. 优先方案:锚点文本 + following-sibling / preceding-sibling:页面少量增减其他无关标签,只要锚点文本不变,定位依然有效,稳定性最强。
  2. 备选方案:位置索引[n]:仅用于页面结构永久固定、不会改版的场景。
  3. 兜底方案:多层祖先路径组合:利用外层父容器特征,缩小查找范围,降低误匹配概率。

总结

面对没有id、class、独有属性的元素,不要直接硬编码顺序路径。

核心思想:利用页面上有稳定特征的周边元素作为锚点,借助XPath轴语法实现跨节点定位

following-sibling::preceding-sibling:: 是日常爬虫解决无特征元素最核心的两个语法。

掌握这套思路,绝大多数缺乏标记的网页节点都可以稳定定位,大幅提升爬虫鲁棒性,减少页面改版带来的维护成本。

以上就是Python使用XPath查找并定位没有特征的元素的详细内容,更多关于Python XPath查找定位元素的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文