根本原因是lxml默认按XML规则解析原始HTML,而浏览器会自动修正不规范HTML结构;动态内容还需区分API直取或用Selenium/Playwright渲染后提取。

为什么直接用 lxml.etree.xpath() 会漏掉节点
常见现象是:明明浏览器里能用 XPath 定位到元素,Python 里却返回空列表。根本原因不是 XPath 写错了,而是网页 HTML 结构被浏览器自动修正过,而 lxml 解析的是原始 HTML —— 比如缺失闭合标签、自闭合标签写成非自闭合( 被当成 )、或嵌套违反规范()。lxml 默认按 XML 规则解析,遇到不合规 HTML 就截断或丢弃后续内容。
解决办法是换用 HTML 解析器:
- 不要用 etree.fromstring(html)
- 改用 html.fromstring(html)(来自 lxml.html)
- 或显式指定解析器:etree.HTMLParser(recover=True)
如何处理动态渲染页面中的 XPath 失效
如果目标节点由 JavaScript 渲染(比如 Vue/React 生成的列表、滚动加载的内容),原始 HTML 里压根没有这些节点,XPath 当然查不到。这时 requests + lxml 组合完全失效,必须模拟浏览器行为。
实操建议:
- 优先检查 Network 面板,看目标数据是否来自 API 接口(XHR/Fetch),直接请求该接口更稳定、更快
- 确认必须走渲染路径时,用 selenium 或 playwright 启动无头浏览器,等 document.readyState === 'complete' 或显式等待目标节点出现后再执行 driver.execute_script("return document.documentElement.outerHTML") 获取完整 HTML
- 避免用 driver.page_source,它可能返回未完全渲染的中间状态
xpath 表达式里哪些写法在 Python 中特别容易出错
和浏览器控制台不同,lxml 对 XPath 1.0 支持严格,不支持部分常用语法:
常见陷阱:
- //div[contains(@class, "item")] ✅ 可用
- //div[contains(@class, "item active")] ❌ 错误:class 值含空格时,应改用 contains(concat(" ", @class, " "), " item ")
- //button[text()="提交"] ❌ 可能失败:按钮文字常含换行或空格,改用 //button[normalize-space(text())="提交"]
- //*[starts-with(@id, "user_")]//span ✅ 但性能差,尽量缩小前缀范围,比如 //div[starts-with(@id, "user_")]//span
- 属性值含单引号时,XPath 字符串必须用双引号包裹,反之亦然;混用需转义,不如统一用 f'//div[@data-id="{value}"]' 拼接
提取多级嵌套结构时怎样避免 None 报错
典型场景:想取商品标题、价格、评分三字段,但某些商品缺评分节点,直接链式调用 node.xpath(".//span[@class='score']/text()")[0] 会触发 IndexError。
安全写法:
- 永远对 xpath() 返回结果做长度判断,不要默认非空
- 用 next(iter(node.xpath('.//span/text()')), None) 替代索引取值
- 更推荐封装工具函数:def safe_xpath(el, path): return el.xpath(path)[0] if el.xpath(path) else None
- 如果结构极复杂(如带条件分支的卡片),先用 .getparent() 或 .getprevious() 回溯定位上下文,比硬写超长 XPath 更可靠
真正难的不是写出能跑的 XPath,而是理解目标网站的 HTML 生成逻辑和 DOM 生命周期。同一套规则,在服务端渲染、CSR、SSR、微前端下表现完全不同,得先判断渲染模式,再决定用解析器还是驱动器。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











