Python XPath position() 完整使用指南,避坑合集(lxml适用)

前言

很多使用 lxml 做网页爬虫的开发者,都会踩中 position() 相关的经典坑:

  1. 混淆 XPath 下标从 1开始,和 Python 列表下标从0开始;
  2. 不清楚 [] 作用范围,不加括号直接使用 //div[position()=2] 导致匹配失败;
  3. 分不清 position() 匹配规则:相对于当前同级节点,不是全局序号;
  4. 尝试用 position() 配合 string() 批量提取文本,出现预期外结果。

本文结合真实爬虫场景,完整讲解 position() 语法、案例、常见错误、实用业务代码。

运行环境:Python + lxml.etree

一、基础语法规则

1. position() 核心定义

position() 返回当前节点在同级兄弟节点中的位置序号 ,序号从 1 开始,不存在 position()=0

语法格式:

复制代码
节点选择器[position() = N]

简写等价写法(日常最常用):

复制代码
节点选择器[N]

//p[2] 等价于 //p[position()=2]

2. 关键红线(90%的人踩坑)

复制代码
//div[@class="box"][2]        ❌ 错误理解
(//div[@class="box"])[2]     ✅ 全局匹配集合取第二个
原理拆解

//div[@class="box"][2]

含义:在每一个匹配到的div内部,寻找第二个子元素,不是「全局所有匹配元素里的第二个」。

(//div[@class="box"])[2]

先把所有匹配的元素打包成节点集合,再从集合中取第二个元素。

如果你的需求是全局筛选结果按序号取值,必须加括号

对应 position() 写法:

复制代码
(//div[@class="box"])[position()=2]

二、分层实战案例

测试HTML样本(沿用你爬虫真实结构)

复制代码
<td>
    <p>Capital Market Day 2015:<br><br>&#160;</p>
    <p>ANDRITZ GROUP, Wolfgang Leitner</p>
    <p>Schuler Group, Stefan Klebert</p>
    <p>Achievements and potentials, Martin Habert</p>
</td>

假设当前上下文节点:td_node(对应你代码里的 td_list[0]

案例1:选取第一个p标签

复制代码
# 两种写法等价
p1 = td_node.xpath('./p[position()=1]')
p1 = td_node.xpath('./p[1]')

# 提取内部全部文本
title = td_node.xpath('string(./p[position()=1])').strip()

案例2:选取第2个及以后所有p标签(业务高频需求)

爬虫经常需要:标题取第一个p,数据列表取剩余全部p

复制代码
./p[position() > 1]

Python完整代码:

复制代码
from lxml import etree

html = """
<td>
    <p>Capital Market Day 2015:<br><br>&#160;</p>
    <p>ANDRITZ GROUP, Wolfgang Leitner</p>
    <p>Schuler Group, Stefan Klebert</p>
    <p>Achievements and potentials, Martin Habert</p>
</td>
"""
tree = etree.HTML(html)
td_node = tree.xpath('//td')[0]

# 标题:第一个p
top_title = td_node.xpath('string(./p[position()=1])').strip()

# 数据列表:position>1 所有p
p_nodes = td_node.xpath('./p[position() > 1]')
data_list = [p.xpath('string()').strip() for p in p_nodes]

print("标题:", top_title)
print("名单列表:", data_list)

输出:

复制代码
标题: Capital Market Day 2015:
名单列表: ['ANDRITZ GROUP, Wolfgang Leitner', 'Schuler Group, Stefan Klebert', 'Achievements and potentials, Martin Habert']

案例3:全局匹配,取第二个class匹配元素

场景:页面存在多个 <div class="content-placement-wrapper">,取第二个

复制代码
# 正确写法
(//div[@class="content-placement-wrapper"])[position()=2]

# 简写
(//div[@class="content-placement-wrapper"])[2]

接着在内部查找子元素:

复制代码
(//div[@class="content-placement-wrapper"])[2]//div[@class="ci-col-1"]

案例4:position() 与 last() 搭配

last() 获取同级节点总数

选取最后一个p:

复制代码
./p[position() = last()]

选取倒数第二个p:

复制代码
./p[position() = last()-1]

三、高频错误汇总

错误1:使用 position()=0

复制代码
./p[0]
./p[position()=0]

❌ XPath序号从1开始,永远匹配不到任何节点,返回空列表。

很多新手把 Python 列表 [0] 的习惯直接带入 XPath,这是最常见bug。

错误2:不加括号,想用序号筛选全局结果

复制代码
//div[@class="content-placement-wrapper"][2]  ❌

解读:找到每个content-placement-wrapper内部第二个子节点,不是全局第二个div。

错误3:试图使用 string() 批量配合position直接返回列表

复制代码
# 错误写法,无法得到列表
text_list = td_node.xpath('string(./p[position()>1])')

string(节点) 只能接收单个节点,返回字符串;不能批量处理一组节点。

✅ 正确思路:先获取节点列表,再循环调用 string() 提取文本。

错误4:混淆「相对上下文position」和「全局序号」

复制代码
<div class="wrap">
    <p>文本1</p>
</div>
<div class="wrap">
    <p>文本2</p>
</div>

//div/p[1]

这里的 position()=1每个div内部p的位置,不是页面上所有p全局排序。

四、position() 实用拓展表达式速查表

XPath表达式 含义
./p[position()=1] 当前节点下第一个p
./p[position()>1] 当前节点下第2个及以后所有p
./p[position()<3] 当前节点下第1、2个p
./p[position()=2 or position()=3] 当前节点下第2、3个p
(//div[@class="box"])[3] 全局所有匹配div集合中第三个
./p[position()=last()] 同级最后一个p

五、爬虫通用模板(直接复制使用)

适用于你当前抓取表格 <td> 多行p标签场景

复制代码
import re
from lxml import etree

def clean_text(raw_text):
    """清理不间断空格、换行、多余空白"""
    if not raw_text:
        return ""
    return re.sub(r"\s+", " ", raw_text).strip()

html = """你的网页html"""
tree = etree.HTML(html)
td_list = tree.xpath("//td")

for td in td_list:
    # 标题:第一个p
    title_raw = td.xpath('string(./p[position()=1])')
    title = clean_text(title_raw)

    # 内容列表:排除第一个p,剩下全部
    p_node_list = td.xpath('./p[position() > 1]')
    content_list = [clean_text(p.xpath('string()')) for p in p_node_list]

    print("标题:", title)
    print("内容:", content_list)

六、总结

  1. XPath position() 起始序号 1 ,杜绝 [0]
  2. [] 筛选作用域很关键:全局筛选集合取值必须外层包裹 ()
  3. position()同级兄弟节点内排序,不是页面全局排序;
  4. 批量提取文本:先选出节点列表,循环执行 string(),不要试图一条xpath直接返回字符串列表;
  5. 业务中 position() > 1position() = last() 是表格、列表爬虫高频写法。
相关推荐
AndrewHZ1 小时前
图像处理入门009 | OpenCV 图像读取与显示:imread/imshow 全解析
图像处理·python·opencv·算法·计算机视觉·图像显示
晴天161 小时前
HarmonyOS 和 React 对比-Day22
前端·华为·harmonyos
卷无止境1 小时前
手写 SQL 在 Tortoise ORM 里到底能派上什么用场
后端·python·fastapi
海鸥两三1 小时前
【websocket合集2】websocket.js源码解析和页面调用示例
javascript·websocket·网络协议
龙虾PRO1 小时前
2026 DeepSeek Harness 部署完整教程:npx 一键启动至 Python SDK 全流程接入
开发语言·python
卷无止境1 小时前
FastAPI、Tortoise ORM 与 PostgreSQL 三件套 是否好用呢?
后端·python·fastapi
AlienZHOU8 小时前
DeepSeek Harness 插件:HTML 实时可视化编辑
前端·agent·deepseek
CTA量化套保10 小时前
近期零基础量化学习:先分阶段,再用 AI 检查缺口
人工智能·python
剪刀石头布啊10 小时前
javascript手动实现继承
前端