一、XPath 核心概念
XPath(XML Path Language) 是一种用于在 XML/HTML 文档中定位节点的查询语言,通过路径表达式精准定位元素,支持属性过滤、文本提取和函数计算。
应用场景:网页爬虫、自动化测试(Selenium)、XML 数据解析。
二、Python 环境配置
安装依赖库
pip install lxml requests # 基础解析库
pip install selenium # 动态页面支持
解析文档的三种方式
from lxml import etree
# 1. 解析本地文件
tree = etree.parse('demo.html')
# 2. 解析字符串
html_str = "<div><p>Hello</p></div>"
tree = etree.HTML(html_str)
# 3. 解析网络请求
import requests
response = requests.get('https://example.com')
tree = etree.HTML(response.content) # 推荐使用 content 避免编码问题
三、XPath 语法详解(附 Python 代码)
1. 节点定位
表达式 | 说明 | Python 代码示例 | 输出 |
---|---|---|---|
//a | 所有 <a> 标签 | tree.xpath('//a/text()') | 链接文本列表 |
/html/body | 根节点下的绝对路径 | tree.xpath('/html/body/div') | body 下的 div 节点 |
.//span | 当前节点下的所有 span | div_element.xpath('.//span') | 子 span 列表 |
//a/@href | 提取 href 属性 | tree.xpath('//a/@href') | 链接地址列表 |
2. 属性过滤
# 精确匹配属性
tree.xpath('//input[@id="search"]') # id 为 search 的输入框
# 模糊匹配(动态属性)
tree.xpath('//div[contains(@class, "nav")]') # class 包含 "nav"
tree.xpath('//a[starts-with(@href, "https")]') # href 以 https 开头
# 多属性组合
tree.xpath('//input[@type="text" and @name="user"]')
3. 文本提取
# 获取直接文本(可能含空白)
tree.xpath('//p/text()') # 返回文本列表(含换行符)
# 获取节点内所有文本(规范化)
tree.xpath('string(//div)') # 返回合并后的连续文本
# 按文本内容筛选节点
tree.xpath('//a[text()="登录"]') # 精确匹配
tree.xpath('//div[contains(text(), "热门")]') # 模糊匹配
4. 函数与轴定位
# 常用函数
tree.xpath('count(//li)') # 统计节点数量
tree.xpath('//book[price>30]') # 价格大于 30 的书
# 轴定位(父子/兄弟关系)
tree.xpath('//div/child::p') # 子节点 <p>
tree.xpath('//span/parent::div') # 父节点 <div>
tree.xpath('//td[text()="价格"]/following-sibling::td') # 右侧相邻单元格
四、Python 实战:爬取小说章节
目标网站结构示例
<div class="cate-list">
<ul>
<li>
<a href="/chapter/1.html">第一章 北灵院</a>
<a href="/chapter/2.html">第二章 被踢出灵路的少年</a>
</li>
</ul>
</div>
完整代码
import requests
from lxml import etree
url = 'http://book.example.com/novel.html'
headers = {'User-Agent': 'Mozilla/5.0'}
response = requests.get(url, headers=headers)
response.encoding = 'utf-8' # 解决中文乱码
tree = etree.HTML(response.text)
# 定位所有章节链接和标题
chapters = tree.xpath('//div[@class="cate-list"]/ul/li/a')
for chap in chapters:
title = chap.xpath('string(.)') # 获取标签内完整文本
link = chap.xpath('@href')[0]
print(f"{title}: {link}")
# 输出示例:
# 第一章 北灵院: /chapter/1.html
# 第二章 被踢出灵路的少年: /chapter/2.html
五、注意事项
-
动态属性陷阱
避免使用//div[@id="js-123"]
(数字会变),改用模糊匹配:
//div[contains(@id, "js-")]
。 -
跨框架定位失效
在 Selenium 中需切换 iframe:driver.switch_to.frame("frame_name") element = driver.find_element(By.XPATH, '//button')
-
性能优化策略
- 优先用 ID 或稳定属性(比 class 快 10 倍)
- 减少
//
使用范围:div_element.xpath('.//p')
- 编译复用表达式:
get_links = etree.XPath('//a/@href') links = get_links(tree)
-
浏览器复制路径不可靠
浏览器生成的 XPath 可能冗长(如/html/body/div[2]/div[5]/span
),需优化为语义化路径。
六、XPath vs CSS 选择器
特性 | XPath | CSS 选择器 |
---|---|---|
父节点定位 | ✅ 支持(.. 或 parent:: ) | ❌ 不支持 |
文本节点筛选 | ✅ //a[text()="登录"] | ❌ 不支持 |
函数支持 | ✅ contains() , starts-with | ⚠️ 部分伪类(如:contains ) |
浏览器原生支持 | ⚠️ 需 document.evaluate | ✅ 主流支持 |
七、总结
最佳实践:
- 优先使用
id
、data-
等稳定属性; - 动态内容用
contains()
或starts-with()
处理; - 复杂文档用轴定位提高精度;
- 调试工具推荐 Chrome 插件 XPath Helper 或控制台
$x('//xpath')
。