news 2026/10/13 2:48:54

Jmeter正则表达式提取器如何一次匹配多个内容:Match No. -1与ForEach实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jmeter正则表达式提取器如何一次匹配多个内容:Match No. -1与ForEach实战

1. 正则提取器做接口关联,先理解这几个底层逻辑

1.1 关联到底在解决什么问题

写接口测试用例时,最常遇到的一种依赖是上一个接口的响应数据要作为下一个接口的入参。比如登录接口返回一个会话令牌,后续每个业务请求都要带上这个令牌;或者订单列表接口返回一批订单号,接下来要用这些订单号去查详细数据。

这种“取出响应中的某个值,保存下来,供后续请求使用”的操作,在Jmeter里就叫关联。Jmeter提供了很多关联手段,比如JSON提取器、CSS选择器提取器、边界提取器,以及今天要重点聊的正则表达式提取器。它们统称为后置处理器。所谓后置,就是请求发出并拿到响应之后,再对响应做一次“二次处理”。

有人会问,既然有JSON提取器,为什么还要研究正则表达式提取器?因为不是所有响应都是JSON格式。有些老系统的接口返回HTML页面片段,有些返回纯文本,有些返回自定义的XML,甚至有的接口响应头里就带着关键信息。这时候正则表达式提取器几乎是通用的兜底方案。只要你能用正则描述出目标格式,就能把内容提出来。所以它不是“过时的老古董”,而是“工具箱里永远不能少的一把螺丝刀”。

1.2 为什么是正则表达式提取器

正则提取器最大的优势是适用范围广。你不需要关心响应是JSON还是HTML,只要目标内容周围有稳定特征字符,就能匹配。

举个例子,一个接口返回了一段不太规范的数据:id=101,name=苹果;id=102,name=香蕉;id=103,name=橘子。JSON提取器看到这种格式会直接挠头,但正则表达式提取器只要写id=(\d+),就能把三个ID都抓出来。

反过来,正则也有它的“脾气”:如果响应结构变化频繁,正则写得太死就容易失效。所以这里有个重要原则:不要贪多,只要抓住目标值附近最稳定的特征字符即可。比如提取token时,写"token":"([^"]+)",比写.*token.*"([^"]*)"要可靠得多。

正则提取器另外一个容易被忽略的优点,就是它可以一次提取多个匹配结果。这也是这篇博文的核心。设置一个参数,就能把响应中所有符合条件的内容全部存入一组变量,配合后续的循环逻辑,完全可以用来做“批量取数据、批量发请求”的场景。

1.3 匹配多个内容的核心:Match No. = -1

在正则表达式提取器的面板里,有一个“Match Numbers”(中文版也叫“匹配编号”)字段。这个字段的值决定了提取几条结果:

  • 空或者0:表示随机取一个匹配值。注意,如果你填了0,每次运行时取的匹配项是随机的,这在某些性能测试场景中可能有意为之,但正常情况下要小心。
  • 填1:表示取第一个匹配项。这是大多数用户默认的配置,也是“只会取一条”的根源。
  • 填-1:表示取所有匹配项。为了实现“匹配多个内容”,必须把这个值改成-1。

当设置为-1时,Jmeter会生成一组带后缀的变量。假设引用名称填的是goodsId,那么:

  • goodsId_1:第一个匹配到的ID
  • goodsId_2:第二个匹配到的ID
  • goodsId_3:第三个匹配到的ID
  • goodsId_matchNr:一共匹配到了几个

很多新手死记硬背,把Match No.填成-1后,直接引用${goodsId},结果发现取不到值。那是因为当写成-1时,原始变量名goodsId本身并不会被赋值为第一个匹配值,Jmeter默认把每一个结果都放进带下标的变量里。这个细节特别容易踩坑,后面我会在实战部分再演示一遍。

2. 界面参数逐项拆解,正则提取器每个字段怎么填

2.1 响应字段选择:从哪里取数据

在正则表达式提取器的面板里,第一块是“Apply to”,第二块是“Response Field to Test”。多数情况下,我们使用默认值:“Main sample only”和“Body”。

但有几个场景需要特别注意:

  • 如果目标数据在重定向后的URL中,选择“URL”或“Response Headers”。
  • 如果目标数据在接口的响应头里,比如Set-Cookie中的会话ID,选择“Response Headers”。
  • 如果接口响应是后端渲染的HTML页面,数据夹杂在大量标签里,通常选“Body”即可。
  • “Body as a Document”模式适合需要从富文本或PDF中提取内容,但是性能开销相对大,而且需要额外插件,普通接口测试基本用不到。

这里建议你,在写正则之前先用查看结果树看看响应体到底是什么样。不要凭感觉猜,而是看真实的返回片段。正则提取器的所有逻辑,都建立在你对响应格式的理解上。

2.2 正则表达式和捕获组:匹配内容的关键

正则表达式这一栏是整个提取器的灵魂。它必须包含至少一个捕获组,也就是括号()。提取器会把括号里的内容保存到引用变量里。

举例来说,响应体是:

"goodsId": 101, "goodsId": 102

如果你想提取goodsId后面的数字,正则至少有两种写法:

  • "goodsId": (\d+)
  • "goodsId": \d+,但没括号就提取不到值。

原因很好理解:正则提取器最终获取的是捕获组(…)中的内容,而不是完整匹配的文本。如果要提取多个内容,就通过多个捕获组来控制。

一个捕获组的时候,模板通常填$1$。如果有两个捕获组,比如想同时拿到id和name,正则写成"id":(\d+),"name":"([^"]+)",模板可以填$1$或者$1$-$2$,甚至可以填$1$$2$。模板是生成变量值的拼接规则,相当于把捕获组重新排列组合。

这里有一个小技巧:如果你只关心某个捕获组,模板就写那个捕获组的序号。如果你想把多个捕获组拼接起来,中间加任意分隔符都可以,实际使用中我经常用$1$-$2$来生成“订单号-状态”这样的复合值。

2.3 模板和引用名称:如何变成变量

引用名称就是变量名,不能用中文,尽量以字母开头,建议用有意义的英文单词。后面所有取值的引用都依赖这个名字。要特别注意“名称#序号”的使用规则。比如引用名称是goodsId,匹配编号为-1时,后续用${goodsId_1}、${goodsId_2}这种方式引用。如果匹配编号为1,则直接用${goodsId}即可。

模板的$0$表示完整匹配的文本,也就是正则表达式整体匹配到的字符串,而不仅仅是某个括号里的内容。比如正则id="(\d+)"匹配到的完整文本是id="101",$0$就是id="101",$1$才是101。所以大多数人会用$1$。

默认值这一栏千万别留空。如果接口响应里没有匹配到任何内容,变量会被赋值为默认值。留空会导致变量名变成空字符串,后续请求的参数就可能缺失。我一般会填一个NOT_FOUND之类的标记,这样在调试日志里能一眼看到问题。

2.4 匹配编号:单条、随机还是全量

这部分我再说细一点,因为这是“匹配多个内容”和“只匹配一个内容”的分水岭。

  • 0:随机取一个。每次运行时,从所有匹配结果中随机选择一个。看似随机,但在批量数据处理场景下会造成“上次取到101,这次取到103”的不确定性,如果后续有参数依赖,会导致脚本不稳定。
  • 1:取第一个匹配项。最常用。
  • 2、3…:取第几个匹配项。比如你想要响应中第二个订单号,就填2。
  • -1:取所有匹配项,并生成变量名_N、变量名_matchNr。这个模式正是标题所说“匹配多个内容”的关键配置。

这里顺便提一个我真实遇到过的例子。某公司的订单系统,一个创建订单的接口会返回多个配送单号,后续每个配送单都要调用物流查询接口。一开始使用Match No.=1,只取了第一个配送单号,导致其他配送单完全没有覆盖。改成-1之后,再配合循环控制器,一次跑完所有单号的查询。这就是“匹配多个内容”的实际价值。

3. 一次搞定多个匹配值的完整实操

3.1 模拟场景:批量商品ID提取

假设现在有这样一个接口场景:/goods/list返回下面这段JSON数组:

[ {"id": 101, "name": "苹果"}, {"id": 102, "name": "香蕉"}, {"id": 103, "name": "橘子"} ]

后续需要依次调用/goods/detail?id=101、/goods/detail?id=102、/goods/detail?id=103来查看每个商品的详情。如果只有三个还好,手工复制三个请求也凑合;如果返回了三百个商品呢?这就是典型的“正则匹配多个内容”需求。

3.2 配置正则提取器提取所有ID

在/goods/list这个HTTP请求下添加后置处理器:正则表达式提取器。

配置如下:

  • Reference Name:goodsId
  • Regular Expression:"id": (\d+)
  • Template:$1$
  • Match No.:-1
  • Default Value:NOT_FOUND

为什么正则写"id": (\d+)?因为响应是缩进格式的JSON,实际响应里"id":后面是一个空格,再跟数字。如果这里不空格,正则也要跟着调整。换句话说,正则必须和响应真实格式严格匹配,包括中间的空格、引号数量,一个都不能错。

提取完成后,查看结果树里看不到变量值,需要添加一个调试采样器(Debug Sampler)来看变量列表。打开调试输出后,你会看到Jmeter生成了这些变量:

goodsId_matchNr=3 goodsId_1=101 goodsId_2=102 goodsId_3=103

这就说明提取成功了。如果你只看到了goodsId_matchNr=3,却看不到goodsId_1,请检查提取器是不是被放在了错误的节点下,或者响应体确实不是这个格式。

3.3 配合ForEach控制器使用批量数据

提取到三个ID之后,怎么把它们逐个传给后面的请求?这里最推荐的方式是ForEach控制器。

添加方法:在测试计划中,把/goods/detail请求放到一个循环控制器下,但更规范的做法是使用逻辑控制器里的“For Each Controller”。它的配置项包括:

  • 输入变量前缀:goodsId
  • 开始下标:0
  • 结束下标:9(或者留空,让Jmeter自动判断)
  • 输出变量名:id

当“输入变量前缀”填goodsId时,ForEach控制器会遍历所有goodsId_1、goodsId_2、goodsId_3这样的变量,并把当前值赋给输出变量id。然后在循环体内的HTTP请求中,把请求参数写成${id}。

ForEach控制器的好处是它不依赖计数器,也不需要手动算循环次数。不管匹配到3个还是300个,它都能全部跑一遍。如果你用普通循环控制器,就需要先用goodsId_matchNr拿到数量,再配合计数器拼接变量名,比较繁琐。

3.4 调试验证变量列表

配置完后,最好在产品详情请求中加一个断言,或者在查看结果树里观察每次请求的URL。

我还有一个小习惯:在ForEach控制器外面再套一个“事务控制器”或“简单控制器”,然后用“调试采样器”把id变量打印出来,这样哪一次循环取到哪个ID一目了然。

如果发现ForEach控制器没有循环起来,先检查“输入变量前缀”有没有填错。前缀是goodsId而不是goodsId_,因为ForEach会自动补上下划线。另外,如果响应里一个都没匹配到,goodsId_matchNr会是0,ForEach自然不会执行循环体。

4. 匹配多个内容时的常见问题与避坑实录

4.1 只取到第一个怎么办

这是最经典的问题。很多人明明看到响应里有多个符合条件的内容,但后续请求永远只拿到第一个值。问题几乎都出在Match No.这一栏:要么是留空,要么是填了1。

请记住:想要匹配多个内容,Match No.必须填-1。填了-1之后,还要注意引用方式。此时原始变量名${goodsId}是没有值的,你必须引用${goodsId_1}、${goodsId_2}才行。如果脚本里到处都写着${goodsId},那就等于用了一个空变量。

如果你只是想取第二个,也可以直接填2,然后引用${goodsId}。但为了统一,我建议在批量场景下都使用-1,然后用带下标的变量。

4.2 变量引用总是默认值

当你发现变量取到的都是自己填的默认值,比如NOT_FOUND,说明正则表达式没有匹配到任何内容。这时候不要再去调试脚本逻辑,先打开查看结果树,肉眼确认响应体里到底有没有目标数据。

常见原因有三个:

  1. 正则写错了,比如引号方向、转义字符、空格不匹配。
  2. 响应体和预期编码不一致,中文乱码会导致正则中的中文字符匹配失败。
  3. 提取器作用域不对。如果提取器放在父级采样器上,而实际响应是从某个子采样器发出的,你的提取器可能跑在了错误的结果上。

排查方法是:把提取器先移动到正确的HTTP请求下面,临时把正则改成最简单、最宽泛的写法,比如\d+,看能不能匹配到数字。如果这样能匹配到,再逐步把正则边界收紧。

4.3 正则表达式太贪婪导致误匹配

正则里最折磨人的就是贪婪匹配。.*是贪婪的,会尽可能往右吞字符;.*?是懒惰匹配,匹配到满足条件的最短内容就停。

举个例子:响应是商品101描述内容商品102描述内容,正则商品(\d+)没问题,但如果我们想匹配“商品101描述内容”这种片段,写商品.*内容就会把“商品101描述内容商品102描述”整体都吞进去。因为.*会一直找到最后一个“内容”。

正确写法应该是商品.*?内容,用懒惰匹配来限定最短范围。这个细节在处理多个内容时极其重要,因为一旦贪婪,你得到的可能不是“多个”而是一个“巨大”的匹配结果,后续所有变量都乱套。

如果记不住贪婪和懒惰,那就记住一条:跨越多段内容时,优先用.*?而不是.*。尤其是提取HTML、JSON这类嵌套型文本时,懒惰匹配的稳定性远远高于贪婪匹配。

4.4 跨线程组传递匹配结果

正则提取器的变量作用域默认是当前线程组。如果测试计划里有多个线程组,A线程组提取到的goodsId_1,B线程组里引用不到。

解决这个问题的方案有三种:

  • 使用__setProperty函数,把变量转为Jmeter属性,因为属性是跨线程的。
  • 在B线程组中重新发起一次请求,再提取一遍。
  • 使用“跨线程组提取器”之类的插件,但这会增加环境依赖。

我的建议是优先用Jmeter属性传递关键数据。比如把goodsId_1设置成属性,B线程组通过${__P(goodsId_1,)}引用。但要注意,多个ID如果都要跨线程组,就要把这组变量循环转换属性,代码量稍大。这时候可以换一种思路:让其他线程组从同一份原始数据中自行提取,避免来回传值。

5. 我平时积累的几个正则匹配模板示例

5.1 提取JSON字段值

即使响应是JSON,很多时候用正则也能快速提取,尤其是结构简单时。模板:

"token"\s*:\s*"([^"]+)" 提取token的值 "id"\s*:\s*(\d+) 提取数字ID

[^"]+的意思是匹配不是引号的一组字符,这样不会因为贪婪匹配而跨过字符串末尾。\s*用来兼容空格和换行。这种写法在响应格式稍微有变化时依然能稳定匹配。

5.2 提取HTML链接中的内容

如果响应是某个页面HTML片段,想提取所有a标签里的链接:

href="([^"]+)"[^>]*>([^<]+)

这个正则有两个捕获组,第一个是URL,第二个是链接文字。模板可以写成$1$ $2$,提取多个内容时,每个匹配项都会保存URL和标题。注意HTML属性顺序经常变化,如果链接上还有其他属性出现在href之前,这个正则就不适用,需要根据真实页面调整。

5.3 提取响应头中的token

某些系统把令牌放在响应头中,此时在Response Field to Test中选择“Response Headers”,正则按头部字段名提取:

X-Auth-Token:\s*([^;\s]+)

这里[^;\s]+用来匹配token值,一直到分号或空白字符为止。因为响应头字段名是大小写敏感的,最好先从查看结果树中复制真实的响应头再写正则。

其实正则模板不需要背太多,核心思想就是“找到目标值周围最稳定的边界,用括号把目标值圈起来”。边界越窄,正则越稳定;边界太宽,容易误匹配。

最后再分享一个我个人的习惯:每当写正则提取器前,我都会先在查看结果树里打开响应体,做一次真实数据的“肉眼正则演练”。在脑子里用正则从头到尾走一遍,再填到元件里。遇到匹配多个内容时,先小数据集验证matchNr,再放到大批量场景跑。这样做的效率,远远高于来回试错。

以上,就是我对Jmeter元件中Regular Expression Extractor匹配多个内容这个需求的全部分享,希望能给正在研究接口关联的你提供一点实在的帮助。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 2:48:30

使用 @theme-ui/tailwind 将 Theme UI 主题转换为 Tailwind 静态 CSS

前端UI组件设计系统 【免费下载链接】theme-ui Build consistent, themeable React apps based on constraint-based design principles 项目地址&#xff1a; https://gitcode.com/gh_mirrors/th/theme-ui 点击查看 免费下载 theme-ui/tailwind 是 Theme UI 仓库中一个专注于…

作者头像 李华
网站建设 2026/10/13 2:48:09

SpriteJS 3D 扩展之 Plane 平面元素:完整 API 解析与实战应用

图形学3D渲染 【免费下载链接】spritejs A cross platform high-performance graphics system. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/sp/spritejs 点击查看 免费下载 Plane 是 SpriteJS Next 3D 扩展库&#xff08;spritejs.ext3d&#xff09;中用于创建三维…

作者头像 李华
网站建设 2026/10/13 2:48:01

OAuth2.0认证体系搭建:Swagger与WireMock的RESTful API调试准备

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华