“淘宝第一个程序员”蔡景现(花名多隆)已从阿里巴巴离职,结束25年任职生涯。作为淘宝初创核心工程师,他构建了淘宝交易系统,以技术实力闻名,曾以26亿身家登上胡润富豪榜,其阿里内外状态已显示为“退隐江湖”。
XPath的
contains()
方法,简单来说,就是用来判断一个字符串是否包含另一个特定的子字符串。它在处理那些内容不完全固定、但部分特征可识别的网页元素时,简直是神器。比如,你想找一个按钮,它的类名总是变,但里面肯定包含“submit”这个词,或者一个文本段落,它总是有“订单号”这几个字,但后面的数字每次都不同,这时候
contains()
就能派上大用场。
解决方案
contains()
方法的语法非常直观:
contains(string, substring)
。第一个参数是你想要检查的字符串(比如元素的文本内容或某个属性的值),第二个参数是你想要查找的子字符串。
举几个实际例子:
-
查找文本中包含特定内容的元素: 假设你想找到所有包含“最新消息”这几个字的
div
元素。
//div[contains(text(), '最新消息')]
这里,
text()
函数获取元素的文本内容,然后
contains()
判断其是否包含“最新消息”。
-
查找属性值中包含特定内容的元素: 如果有一个链接,它的
href
属性值总是包含“product”这个词,但后面可能跟着不同的ID。
//a[contains(@href, 'product')]
@href
表示获取
href
属性的值。
-
处理动态类名: 很多前端框架会生成动态的CSS类名,比如
btn-primary-dsf23k
,但核心部分
btn-primary
是不变的。
//button[contains(@class, 'btn-primary')]
这比用
@class='btn-primary-dsf23k'
这种完全匹配要灵活得多。
-
结合其他条件使用: 你也可以将
contains()
与其他XPath表达式结合起来,进行更精确的定位。
//div[contains(@id, 'item') and contains(text(), '详情')]
这会找到所有
id
包含“item”且文本内容包含“详情”的
div
元素。
XPath
contains()
contains()
与
starts-with()
、
ends-with()
有什么区别?
说实话,XPath这东西,用好了就是利器,但刚上手时,
contains()
、
starts-with()
、
ends-with()
这几个哥们儿确实容易让人混淆。它们都是字符串匹配,但侧重点完全不一样。
-
starts-with(string, substring)
:顾名思义,它只检查一个字符串是否以指定的子字符串开头。 比如,你有一堆ID是
user_1
、
user_2
、
admin_1
的元素,你想找所有用户相关的元素,用
starts-with(@id, 'user')
就非常精准。它不会匹配到
super_user_1
这种ID。
-
ends-with(string, substring)
:这个方法在XPath 2.0及更高版本中才支持,它检查一个字符串是否以指定的子字符串结尾。 如果你想找所有以
.png
结尾的图片链接,
//img[ends-with(@src, '.png')]
就很方便。在一些老旧的系统或者XPath 1.0的环境里,你可能得想别的办法,比如用
substring()
和
string-length()
组合来模拟。
-
contains(string, substring)
:而
contains()
就宽松多了,它不关心子字符串在原字符串的开头、结尾还是中间,只要有,它就认为匹配。 这就像是“模糊匹配”的王者。比如,一个元素的类名可能是
active-item
,也可能是
item-active
,或者
main-item-active-status
,只要里面有
item
,你用
contains(@class, 'item')
都能抓到。
什么时候用哪个?
- 当你明确知道目标字符串的前缀时,用
starts-with()
。
- 当你明确知道目标字符串的后缀时(且环境支持),用
ends-with()
。
- 当目标子字符串可能出现在任何位置,或者你只想进行一个宽泛的包含匹配时,
contains()
是你的首选。
contains()
contains()
方法在处理动态类名或文本时有哪些实战技巧?
很多时候,我们抓取网页或者自动化测试时,会遇到一些让人头疼的动态元素。它们的ID、类名或者文本内容,可能每次加载都变一部分。这时候,
contains()
的灵活就体现出来了。
-
动态类名处理的“万金油”: 设想一下,一个按钮,今天它的类名是
btn-primary-randomhash123
,明天可能变成
btn-primary-anotherhash456
。你不可能每次都去 inspect 元素拿到完整的类名。但核心的
btn-primary
通常是固定的。
//button[contains(@class, 'btn-primary')]
这招屡试不爽,能大幅提升你的XPath表达式的健壮性。甚至有些元素可能有多个类名,比如
<div class="item active selected">
,如果你只想找到所有“活跃”的项,而不管它是不是“选中”的,
//div[contains(@class, 'active')]
就能精准命中。
-
处理部分变化的文本内容: 网页上经常有这种文本:“您的订单号是:ABC123456789”,或者“当前库存:150件”。其中“ABC123456789”和“150”是动态变化的。
//p[contains(text(), '您的订单号是:')]
//span[contains(text(), '当前库存:') and contains(text(), '件')]
(这里用两个
contains
来确保匹配的精确性,避免只匹配到“当前库存”或“件”的意外情况) 这样,即使订单号或库存量变了,你的XPath依然能准确找到目标元素。
-
应对大小写不敏感的需求(XPath 1.0 局限):
contains()
是大小写敏感的。这意味着
contains(text(), 'hello')
不会匹配到“Hello”。在XPath 1.0中,要实现大小写不敏感,你需要借助
translate()
函数,将字符串中的大写字母转换为小写,然后再进行
contains()
判断。
//div[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'hello')]
这看起来有点长,但确实是XPath 1.0里比较通用的做法。如果你的环境支持XPath 2.0,那就可以用
lower-case()
函数,会简洁很多:
//div[contains(lower-case(text()), 'hello')]
。了解这些,能让你在遇到实际问题时,知道如何变通。
使用
contains()
contains()
时可能遇到哪些常见问题和性能考量?
contains()
虽然好用,但用起来也有些小坑和需要注意的地方。
-
大小写敏感性陷阱: 这是最常见的“坑”之一。很多人以为
contains()
会智能地忽略大小写,结果发现怎么也匹配不上。比如
contains(@class, 'active')
不会匹配到
Active
。遇到这种情况,要么老老实实写精确的大小写,要么就得用上面提到的
translate()
或者
lower-case()
来处理。
-
过度匹配的风险:
contains()
的“模糊”特性,有时也会带来过度匹配的问题。如果你想找一个
class
为
item-list
的元素,却写了
//div[contains(@class, 'item')]
,那么可能
item-detail
、
sub-item
等所有包含“item”的元素都会被匹配到,这显然不是你想要的。 解决办法是:尽可能结合其他更精确的定位方式来缩小范围,比如先用ID或更具体的父级路径,再使用
contains()
。 例如,
//div[@id='main-content']//li[contains(@class, 'product-item')]
就比
//li[contains(@class, 'product-item')]
更精准,因为它限定了只在
id
为
main-content
的
div
内部查找。
-
性能考量(虽然通常不是大问题): 对于现代浏览器和XPath引擎来说,
contains()
的性能通常不是瓶颈,但在极端情况下,比如处理非常庞大、复杂的XML文档,或者在循环中大量使用
contains()
,还是值得注意一下。 相比于直接的属性匹配(如
@id='someId'
)或
starts-with()
,
contains()
需要遍历整个字符串来查找子串,理论上会稍微慢一点点。不过,这种差异在大多数网页抓取或自动化场景中几乎可以忽略不计。 真正的性能杀手往往是那些过于宽泛的XPath,比如
//*[contains(text(), '某个词')]
。这种表达式会扫描整个文档树,效率会比较低。所以,尽量从一个更具体的父节点开始,缩小搜索范围,总是一个好习惯。
总的来说,
contains()
是一个非常实用的工具,掌握它的用法和特性,能让你在处理动态网页内容时更加游刃有余。
评论(已关闭)
评论已关闭