xpath比css选择器更易定位动态id节点,因其支持文本、位置、父子兄弟关系等灵活匹配;css仅依赖静态属性,遇随机类名或id易失效。

为什么 XPath 比 CSS 选择器更容易定位动态 ID 或属性变化的节点
因为 XPath 支持基于文本、位置、父/兄弟关系的灵活表达,而 CSS 选择器只能靠类名、标签、属性值等静态特征匹配。当网页用 id="post-12345" 或 class="item__content--v2" 这类带随机后缀的结构时,CSS 很容易失效。
实操建议:
- 优先用
//div[contains(@class, "content") and .//h2]而不是div.content__wrapper—— 它能绕过类名拼写变异 - 用
text()函数抓纯文本内容://span[@class="price"]/text(),避免把空格、换行符一起带进来 - 注意
//(任意层级)和/(严格父子)的区别:写成/html/body/div[2]/ul/li极易因 DOM 微调崩掉,应改用//ul[@id="list"]/li - Chrome 控制台里右键元素 → “Copy XPath”,得到的是绝对路径,要手动简化成相对路径再用
requests + lxml 中 etree.HTML() 和 etree.fromstring() 别混用
前者接受字符串(含乱码处理逻辑),后者要求输入必须是合法 XML 字节流;网页 HTML 常有未闭合标签或编码错误,直接喂给 fromstring() 会静默丢节点甚至抛 XMLSyntaxError。
常见错误现象:
- 用
etree.fromstring(r.text)后查不到明明存在的<div class="main"> <li>返回结果为空列表,但打印 <code>tree.getroot()显示只有 - 一律用
etree.HTML(r.content),传入bytes更稳(r.content自动处理 charset 声明) - 如果必须用
fromstring(),先确保编码已解码为 UTF-8 字符串,并加parser=etree.HTMLParser() - 别依赖
r.text—— 它可能被 requests 错误解码,尤其遇到charset=gb2312的老站 - 目标是标准博客列表?用
doc.cssselect("article.post h2.title a")更清爽 - 要取“第三个
td里不带span的纯文本”?只能写tr[2]/td[3][not(span)]/text() - 需要根据兄弟节点判断(如“标题下紧跟的
p,且后面没有hr”)?XPath 的following-sibling::是唯一解 - 性能上,CSS 在 lxml 中略快(约 10–15%),但对百条以内数据几乎无感;别为这点速度牺牲可维护性
- 先用
re.sub(r"[\s\u200b\u00a0]+", " ", text)把所有空白符归一为单空格 - 再用
.strip()去首尾空格 - 对价格、数字类字段,额外用
re.sub(r"[^\d.]", "", s)清除非数字字符(小心负号、千分位) - 遇到
<script></script>插入的 JSON 数据?别硬爬 DOM,直接用re.search(r'var data = (.*?);', html)提更准
正确做法:
用 select()(CSS)还是 xpath()?看这三点选
CSS 选择器快、易读,XPath 表达力强、容错高。实际选哪个,取决于目标结构是否“规整”。
使用场景判断:
提取后怎么安全清理文本?别只用 .strip()
网页文本常混着 、\u200b(零宽空格)、多层嵌套 <br>、前后不可见 Unicode 字符,光靠 .strip() 会漏掉大量脏数据。
推荐组合清理步骤:
真正麻烦的从来不是定位,而是 HTML 本身就不规范——同一个站点不同页面用两套模板、JS 动态补全关键字段、反爬插入干扰 class 名。这些没法靠选对 XPath 就解决,得结合网络请求上下文一起看。











