4、元素定位进阶:XPath 语法详解、CSS Selector 实战、相对路径与绝对路径选择策略
说实话,很多新手爬虫工程师最容易栽跟头的地方,就是元素定位。
你写了个看似完美的脚本,跑起来却总是报错「找不到元素」。为什么?说白了,就是定位策略没选对。今天这一章,我把压箱底的经验都掏出来,咱们把 XPath、CSS Selector 和路径选择策略彻底讲透。
4.1 XPath 语法详解:从入门到精通
XPath 的全称是 XML Path Language。别看名字带 XML,它在 HTML 文档里同样好用。我个人习惯,遇到复杂页面结构时,首选 XPath。
4.1.1 基础节点选择
先看几个最常用的表达式:
//div —— 选择所有 div 元素
/html/body/div —— 从根节点开始的绝对路径
//div[@id='content'] —— 选择 id 为 content 的 div
//a[@class='link'] —— 选择 class 为 link 的链接
我在项目中遇到过一个问题:用 //div[@class='item'] 死活选不到元素。后来发现,那个 class 后面还跟了个空格和另一个类名。嗯,这里要注意,XPath 的 @class 匹配的是完整属性值。
@class='item' 匹配失败,浪费了整整一下午。后来改用 contains(@class, 'item') 才搞定。
4.1.2 常用函数与操作符
XPath 的强大之处在于它的函数库。你想想看,如果页面结构稍微变一下,你的定位器就失效了,那多尴尬。用函数就能解决这个问题。
| 函数/操作符 | 示例 | 说明 |
|---|---|---|
contains() |
//div[contains(@class, 'btn')] |
属性值包含指定字符串 |
text() |
//span[text()='确认'] |
匹配文本内容 |
starts-with() |
//input[starts-with(@id, 'user_')] |
属性值以指定字符串开头 |
and / or |
//div[@class='a' and @id='b'] |
多条件组合 |
position() |
//ul/li[position()=3] |
选择第几个子元素 |
$x('//表达式') 快速测试 XPath,比反复运行脚本快多了。
4.1.3 轴(Axes)的使用
轴是 XPath 的高级特性。说白了,就是让你在 DOM 树里「上下左右」随便走。
//div[@id='main']/following-sibling::div —— 后面的兄弟节点
//div[@id='main']/preceding-sibling::div —— 前面的兄弟节点
//div[@id='main']/ancestor::div —— 所有祖先节点
//div[@id='main']/descendant::span —— 所有后代 span
我记得有一次,目标网站把商品价格放在了一个很深的嵌套结构里,用普通路径根本定位不到。后来我用 //span[contains(text(), '¥')]/ancestor::div[@class='item'],一下子就拿到了整个商品容器。这就是轴的威力。
4.2 CSS Selector 实战:又快又稳
CSS Selector 的语法比 XPath 简洁,执行速度也更快。我个人在写 Selenium 脚本时,如果页面结构不复杂,会优先用 CSS Selector。
4.2.1 基础选择器
div.item —— 类选择器,等价于 XPath 的 //div[@class='item']
#content —— ID 选择器,等价于 //*[@id='content']
div > p —— 子元素选择器
div p —— 后代元素选择器
input[type='text'] —— 属性选择器
4.2.2 高级用法
CSS Selector 也有一些很实用的「黑科技」:
ul li:nth-child(3) —— 第三个 li
ul li:first-child —— 第一个 li
ul li:last-child —— 最后一个 li
a[href^='https'] —— href 以 https 开头
a[href$='.pdf'] —— href 以 .pdf 结尾
div:not(.hidden) —— 不包含 hidden 类的 div
div.product:not(.ad) 就完美过滤掉了。你想想看,如果不用 :not(),你得写多复杂的 XPath?
4.3 相对路径与绝对路径选择策略
这是很多新手最容易纠结的地方。我直接说结论:能用相对路径,就别用绝对路径。
4.3.1 绝对路径的坑
绝对路径长这样:/html/body/div[2]/div[3]/div[1]/span
为什么我不推荐?因为只要页面结构稍微调整一下——比如加了个广告位、改了个布局——你的路径就全废了。我曾经接手过一个项目,里面全是绝对路径,每次网站改版都要重写一半的定位器,那叫一个痛苦。
4.3.2 相对路径的优势
相对路径的核心思想是:找到一个稳定的锚点,然后基于它去定位。
// 好的做法:基于 ID 或唯一属性
//div[@id='product-list']//span[@class='price']
// 更好的做法:结合文本内容
//div[contains(@class, 'item')]//button[text()='加入购物车']
4.3.3 我的选择策略
我一般按这个优先级来选:
- 优先用 ID —— 如果元素有唯一的 id,直接用
#id或//*[@id='id'] - 其次用唯一属性 —— 比如
data-*属性、name属性 - 再次用文本内容 —— 用
text()或contains(text()) - 最后用层级关系 —— 结合稳定的父元素,用相对路径往下找
//div[1] 这种索引定位,结果网站上线 A/B 测试,两个版本的页面结构不一样,索引全乱了。从那以后,我再也不依赖索引了。
4.4 实战对比:XPath vs CSS Selector
到底用哪个?我直接给你一个对比表:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 根据文本内容定位 | XPath | CSS Selector 不支持文本匹配 |
| 根据属性值部分匹配 | XPath | contains() 比 CSS 的 *= 更灵活 |
| 简单层级定位 | CSS Selector | 语法简洁,执行速度快 |
| 复杂 DOM 遍历 | XPath | 轴(Axes)功能无可替代 |
| 动态 ID 或 class | 两者皆可 | 用 contains 或 *= 匹配部分值 |
好了,这一章的内容就到这。记住一句话:定位器的稳定性,决定了爬虫的寿命。下一章咱们聊聊「动态加载数据的捕获与处理」,到时候会用到今天讲的这些定位技巧,别掉队。