优先选xpath:当需按文本内容、父/兄弟关系定位,或使用函数(如count、contains)及轴(如following-sibling)时;否则css更简洁易维护。

用 xpath 还是 CSS 选择器 解析 HTML,取决于你要抓的数据结构、目标站点的稳定性,以及你是否需要处理属性、文本、兄弟节点等复杂定位——不是语法越短越好,而是哪一种在当前场景下更稳、更易维护。
什么时候必须用 xpath
xpath 是唯一能直接按「文本内容」或「父/兄弟关系」精准定位的方案。比如页面里没有 class 或 id,只有“第 3 个 <td> 后面紧跟的 <code><span></span>”,CSS 就无能为力。
-
//td[text()="订单号"]/following-sibling::td[1]—— 定位指定文本后的相邻单元格 -
//div[contains(@class, "price")]/text()[last()]—— 提取 class 含 price 的 div 中最后一段纯文本(绕过子标签) -
count(//ul/li)或string-length(//h1)—— 需要计算或字符串操作时,只能靠 xpath 函数
什么时候优先选 CSS 选择器
CSS 选择器写起来快、可读性强,且在 BeautifulSoup(配合 lxml 或 html.parser)和 PyQuery 中支持良好。但注意:它不支持轴(如 following-sibling)、函数(如 contains()),也不能匹配文本节点本身。
-
div.product > h2.title—— 子元素关系清晰时,比//div[@class="product"]/h2[@class="title"]更简洁 -
a[href^="https://example.com"]—— 属性前缀匹配,lxml的 CSS 支持,但原生 BeautifulSoup(非 lxml)不支持伪类如:nth-of-type(2) -
input#search[name="q"][type="text"]—— 多属性叠加定位,直观且不易出错
lxml 下两者性能与兼容性差异
lxml 对 xpath 和 CSS 都支持,但底层实现不同:xpath 编译后执行,CSS 会被转换成等价 xpath 再执行。实测中,简单 CSS 选择器(如 div.class)和对应 xpath(如 //div[@class="class"])性能几乎一致;但含函数或轴的 xpath 无法被 CSS 替代,强行改写会导致逻辑错误或漏数据。
立即学习“Python免费学习笔记(深入)”;
- 使用
etree.HTML(html).xpath(...)时,确保字符串是合法 xpath 表达式,@不可省略([@id]≠[id]) -
etree.HTML(html).cssselect(...)仅在 lxml ≥ 4.0+ 可用,旧版本需用cssutils或退回到 BeautifulSoup - 遇到
lxml.etree.XPathEvalError,大概率是引号嵌套错误或用了 CSS 语法(如div:nth-child(2))当 xpath 用
常见混用陷阱与调试建议
别在同一个项目里随意切换解析方式——尤其当 HTML 结构稍有变动时,CSS 和 xpath 对空格、换行、注释的敏感度不同,容易出现「一个能取到,另一个返回空」的情况。调试时先用浏览器开发者工具验证表达式有效性。
- Chrome 控制台中:
$x('//span[@class="price"]')测试 xpath;$$('span.price')测试 CSS - Python 中打印
len(result)比直接看result[0].text更安全,避免IndexError - 如果目标元素由 JS 渲染,
requests+lxml拿不到,得换selenium或playwright,此时 xpath 在自动化工具中仍是主流定位方式
from lxml import etree
<p>html = '<div class="item"><span class="name">Apple</span><span class="price">¥5.2</span></div>'
tree = etree.HTML(html)</p><h1>✅ 推荐:xpath 精准取 price 文本(无视前后空白)</h1><p>price_text = tree.xpath('//span[@class="price"]/text()')[0].strip()</p><h1>⚠️ 注意:cssselect 返回的是 Element 对象列表,需再调 .text</h1><p>price_elem = tree.cssselect('span.price')
price_text_css = price_elem[0].text.strip() if price_elem else None真正麻烦的从来不是写对一句 xpath 或 CSS,而是当页面改版、class 动态生成、文本被零宽字符干扰、或者 <script></script> 里藏了真实数据时,你怎么快速判断该换哪种方式、要不要加 normalize-space()、甚至该不该放弃 HTML 解析直接正则捞——这些没法靠语法速查表解决,得靠多看源码、多试边界 case。











