最直接方法是用xpath表达式//a/@href提取href属性值,返回python列表;属性含空格或多值时应使用contains(@class, 'xxx')而非@class='xxx',避免匹配失败。

用 lxml 的 xpath 提取标签属性最直接
别绕路写正则或层层 .find(),xpath 是 lxml 提取属性的首选。它原生支持属性选择器语法,比如 @href、@class,定位精准且执行快。
常见错误是把属性名当文本处理,例如写成 text()='login' 却想匹配 class="login" —— 实际该用 contains(@class, 'login')。
实操建议:
- 用
tree.xpath("//a/@href")直接拿到所有<a></a>的href值,返回 Python 列表,不是 Element 对象 - 带条件时加中括号:
//img[@alt and @src]表示同时存在alt和src属性的<img> - 属性值含空格或多个类名?用
contains(@class, 'btn')比@class='btn'更安全
处理 class 属性要小心空格和多值
class 是典型空格分隔的多值属性,直接用 @class='xxx' 几乎总失败。lxml 不会自动解析 class 列表,得靠字符串函数兜底。
使用场景:抓取带 class="btn btn-primary js-submit" 的按钮,只想要含 js-submit 的元素。
实操建议:
- 优先用
contains(@class, 'js-submit'),但注意它也会匹配js-submitting - 更准一点:用
concat(' ', @class, ' ') 与正则配合,但 lxml 1.x 不支持matches(),所以实际常用contains(concat(' ', @class, ' '), ' js-submit ') - 如果 class 值固定且简单(如只有
active),可改用 CSS 选择器:tree.cssselect("a.active"),再取.get('href')
为什么不用 .get() 而坚持用 xpath?
单个元素上,elem.get('href') 确实简单;但批量提取、带逻辑筛选、跨层级找属性时,xpath 表达力碾压 .get()。而且 .get() 不报错——属性不存在就返回 None,容易掩盖漏匹配问题。
性能差异明显:对 10k 行 HTML,xpath("//a/@href") 比循环调用 .findall() + .get() 快 3–5 倍(实测 lxml 4.9+)。
实操建议:
- 不要混合用:
tree.xpath("//div")[0].get('data-id')看似省事,一旦索引越界或无匹配,就抛IndexError或返回None,不如写全tree.xpath("//div/@data-id")[0] if tree.xpath("//div/@data-id") else None - 需要默认值?用 XPath 的
string()或 Python 层后处理,别依赖.get('attr', 'default')隐藏结构风险
中文属性名或命名空间下 xpath 失效怎么办
遇到 <div> 这种中文属性,或 XML 带命名空间(如 <code>xmlns:ns="http://abc.com"),原生 xpath 会解析失败或查不到。
根本原因:XPath 标准不支持非 ASCII 属性名,且默认忽略命名空间前缀。
实操建议:
- 中文属性:先用
etree.tostring()看原始字节,确认编码没乱;若确为 UTF-8,可用tree.xpath("//div/@数据来源")(lxml 4.6+ 支持,但需确保 parser 未强制转义) - 带命名空间:必须注册前缀,例如
tree.xpath('//ns:item/@id', namespaces={'ns': 'http://abc.com'}) - 实在搞不定命名空间?临时去掉它:
for elem in tree.iter(): elem.tag = etree.QName(elem).localname,再用普通 xpath
属性提取看着简单,但 class 多值、中文名、命名空间这三类情况,十次爬虫至少撞上两次。别等跑完才发现列表长度不对——在小样本上先把 xpath 表达式单独 print 出来验证一遍。











