做接口测试的人几乎都绕不开这样一个需求登录接口返回一长串 JSON里面有 token、userId、用户角色甚至还有权限列表后面一堆接口都要拿着这些动态值去拼参数。手动复制粘贴一次两次还行压测脚本里一百个线程同时跑每个用户拿到的值都不一样这时候就轮到 JMeter 的关联功能上场也就是后置处理器里的 Regular Expression Extractor正则表达式提取器。这篇文章不打算从零讲正则语法而是聚焦一个最容易卡住人的问题怎样用正则提取器一次性匹配多个内容。单个值谁都会写但一个响应要取好多个字段同一种格式的值要全部取出来多个正则混在一起这些情况很多人就开始翻车了。我会按实际项目里的用法把控件参数、三种多值提取姿势、完整案例和踩坑经验都写清楚适合刚接触 JMeter 关联的测试同学也适合写过几个脚本但总在正则这里栽跟头的朋友。1. 先想清楚关联需求到底从哪来提取器又该放在哪1.1 动态数据不能写死只能用规则去取接口测试里有个很基础但很重要的设计原则不要相信上一个环境的返回值更不要把它写死在脚本里。比如某登录接口每次返回的 accessToken 都不同你把 A 环境抓到的 token 直接填到 B 环境的脚本里第一个请求必挂。常规做法是把这种动态值关联出来。关联这件事本身不是 JMeter 独有的概念而是接口测试里对动态数据传递的通用处理方式从上一个接口的响应里按照某个规则把需要的值取出来存成变量下一个接口再引用这个变量。正则表达式提取器做的就是其中制定规则、取出值、存成变量这一步。举个最常见的例子。登录接口返回{token:7f8c1e2a9b0d,userId:9527,role:admin}后面查询用户信息的接口要传 userId下单接口要在请求头里带 token操作日志接口要根据 role 判断权限。这些值都是动态的每次登录都不一样必须从登录响应里提取。1.2 提取器是后置处理器一定要放在对应的取样器下面Regular Expression Extractor 在 JMeter 里属于后置处理器Post Processor添加路径是在某个取样器通常是 HTTP 请求上右键 - 添加 - 后置处理器 - 正则表达式提取器。执行顺序是HTTP 请求把请求发出去、收到响应之后紧接着执行它下面的后置处理器把响应数据拿给提取器做正则匹配。所以它天然适合从最近一次响应里取数据。你把提取器放在 A 请求下面它拿到的就是 A 请求的响应跟 B 请求没关系。这里有个小细节容易被忽略如果请求发生重定向或者页面里有嵌入资源主请求的响应里可能没有你想要的内容真正的内容在子请求里。这时要在提取器面板上的应用范围里选 Main sample and sub-samples而不是默认的 Main sample only。反正我的习惯是一开始先用 Main sample only 调试如果找不到值再去检查是不是子请求响应里才有。还有一点想提前说提取出来的变量默认只在当前线程内有效。JMeter 一个线程就是一个虚拟用户线程之间的 JMeter 变量默认是不共享的。单线程组脚本里不用操心多线程组之间想传值那是另一个话题这里先不展开。2. 逐个拆解提取器控件真正影响结果的就是这五个字段2.1 引用名称先决定变量叫什么引用名称Reference Name就是你给这个变量起的名字也是后面所有引用的基础。比如叫 token后面用${token}引用。命名建议用英文字母、数字、下划线别用中文也别带点号否则在 JMeter 属性、命令行传参、循环控制器里都容易出奇怪问题。配合匹配数字为 -1 的场景变量名还会自动生成带下标的变量这部分在下一节讲这里先建立概念引用名称是整个提取结果的前缀。2.2 正则表达式关键不是会写正则而是知道括号加在哪提取器的正则表达式字段最重要的要求是必须包含括号捕获组。没有括号的正则只能判断匹不匹配得上取不到内容。比如响应里有title测试报告/title你填title(.*?)/title括号里的.*?就是被捕获的部分也就是我们真正要的测试报告。很多人写正则喜欢从网上抄一大串结果括号加了一堆编号全乱。这里强调一个原则每个括号都会参与编号$1$、$2$、$3$ 是按括号出现的先后顺序排的。如果你为了分组语义随手加括号小心后面模板取错组。在接口测试里我建议针对 JSON 字符串直接按文本匹配而不是强行解析。比如token:([^])意思是从token:后面开始抓取所有非引号字符一直到下一个引号为止。[^]比.*?更稳因为它明确排除了引号不会把后面的内容也吃进去。如果响应里可能带空格可以宽松一点token\s*:\s*([^])2.3 模板决定提取出来的值长什么样模板字段默认是$1$意思是用第一个捕获组的值作为最终结果。如果正则里只有一个括号你基本不用改它。当正则里有多个括号时模板就成了编排工具。比如要取userId和userName两个字段正则写成userId:(\d),userName:([^])模板填$1$|$2$最终变量值就是1024|tester01。你也可以写$1$$2$这时两个值会直接拼接中间没有分隔符。如果只想取第二个括号的值模板直接写$2$。还有一个不太常用的$0$ 代表整个正则匹配到的完整文本偶尔需要连前缀带内容一起要的时候有用。2.4 匹配数字1、0、-1 的含义完全不同这个字段是提取多个内容时最容易搞错的地方。空着或者填 1取第一个匹配到的结果这是最常见的情况。填 0随机取一个匹配到的结果注意是随机不是全部。填 -1取所有匹配到的结果变量会按名称_1、名称_2、名称_3的方式存放另外还会生成一个名称_matchNr记录总共匹配了几个。填大于 1 的数字比如填 2就是取第二个匹配到的结果。我见过不少同事把 0 当成全都要结果压测时每个线程拿到的值都不一样数据全乱。填 -1 才是全量提取这个一定要记牢。2.5 缺省值匹配失败时留个案底缺省值Default Value是在正则没匹配到内容时赋给变量的值。如果这里留空变量就不会被创建后面用到${token}的地方会被替换成空字符串问题很隐蔽。建议调试阶段给每个提取器填一个明显的标记比如NOT_FOUND或提取失败。这样一旦提取有问题后续接口的参数里就会带上这个标记在断言和日志里一眼就能发现。3. 匹配多个内容的三种姿势从简单到进阶3.1 姿势一一个正则里放多个括号用模板拼装适用于几个字段离得近、顺序固定的响应。比如返回一段 HTMLform input nameuserId value1024 input namerole valueadmin input namemenuId valueM3 /form要一次取出 userId、role、menuId正则可以写成nameuserId value(\d).*?namerole value(\w).*?namemenuId value(\w)模板填$1$|$2$|$3$引用名称设为 userInfo那么${userInfo}就是1024|admin|M3。这种一取多的方式只需要一次匹配效率高脚本也干净。但要注意如果字段之间的内容经常变化、顺序也不固定用这种方式会比较脆。比如中间多了一个字段后面的.*?可能匹配错位置。这种情况下别硬写一个超长正则拆成多个提取器更稳。另外当某个正则里确实有多个捕获组而且你想分别拿到每组的值JMeter 5.x 还会顺带生成变量名_g1、变量名_g2这样的分组变量可以直接单独引用。很多教程不提这个但实际调试很有用。3.2 姿势二匹配数字填 -1把同格式的全部值批量取出来适用于响应里有好多条同样格式的数据每条都要用到的场景。最典型的就是查询列表后要把列表里的 ID 全部提取出来然后循环发起详情请求。假设接口返回order id1001nameOrderA/name/order order id1002nameOrderB/name/order order id1003nameOrderC/name/order正则写成order id(\d)引用名称填 orderId匹配数字填 -1。匹配完成后JMeter 自动生成orderId_1 1001orderId_2 1002orderId_3 1003orderId_matchNr 3配合 JMeter 的 ForEach 控制器可以很优雅地遍历这些值。ForEach 控制器的输入变量前缀填 orderId勾选 Add underscore输出变量名填 oid它就会自动按照 orderId_1 到 orderId_matchNr 的范围内循环循环体里的 HTTP 请求直接用${oid}引用即可。这种做法在压测先查列表再逐个查看详情的链路里非常好用。注意一点match number 为 -1 时不要赌基础变量${orderId}也存了值老老实实用${orderId_1}这种带下标的引用。3.3 姿势三多正则提取器以及多个普通提取器方案有些响应里要提取的字段分布很散、格式差异大。比如一个响应里既有 token又有金额还有一段列表。这时强行合并成一个正则会写出又臭又长、维护起来想哭的表达式。比较推荐的做法有两种。第一种同一个取样器下叠加多个普通正则提取器。每个提取器各管一个字段变量名各不冲突互不干扰。比如一个提取器提取 token一个提取 userId一个提取 role。这种方式很简单缺点是提取器多了以后光看控件列表有点乱但好处是每个规则独立出问题好排查。第二种用 JMeter 5.5 以后自带的 Multiple Regex Extractor中文界面一般叫多正则表达式提取器也是后置处理器的一种。它用表格形式把多个规则集中放在一起每一行配置一组变量名、正则表达式、模板和匹配数字本质上跟放多个普通提取器效果一样但界面集中规则多的时候维护更方便。如果版本低于 5.5普通提取器叠加使用完全够用不一定要为了这个功能升级环境。3.4 三种姿势到底怎么选场景推荐方案原因一个响应里几个字段挨得近、顺序稳定一个正则多括号 模板一次匹配性能最好同一种格式的值反复出现要全部取出来匹配数字 -1 ForEach 循环原生支持批量变量天然配合遍历字段分散、格式差异大多正则提取器或多个普通提取器规则独立互不干扰好维护返回是标准 JSONJSON Extractor JSONPath不需要抠引号和反斜杠最不容易错需要复杂解析、拼接、判断JSR223 Groovy想怎么处理就怎么处理很多人一开始就想学最复杂的其实 80% 的关联需求用前两种就能解决。真正要上复杂方案的往往是响应结构本身就很恶心的老系统。4. 一个能直接复制的实战案例登录返回多个字段后续接口全部引用4.1 模拟返回体与正则设计假设某个后台管理系统的登录接口 POST /api/login 返回{ code: 0, message: success, data: { accessToken: 8f3a9c21-6b7e-4d2a-9f10-2c8d4e6a7b10, userId: 1024, userName: tester01, role: operator, permissions: [order:view, order:create, order:batch] } }我们要从登录响应里提取 accessToken、userId、userName、role以及 permissions 里的三个权限值供后续下单查单查询权限等接口使用。用 Multiple Regex Extractor 配置如下几行规则变量名正则表达式模板accessTokenaccessToken\s*:\s*([^])$1$userIduserId\s*:\s*(\d)$1$userNameuserName\s*:\s*([^])$1$rolerole\s*:\s*([^])$1$perm1permissions\s*:\s*\[([^]),([^]),([^])$1$perm2同上$2$perm3同上$3$注意permissions的正则里数组的中括号需要转义成\[因为方括号在正则里是字符类标记。JSON 数组元素之间的逗号直接照抄即可。4.2 后续 HTTP 请求和请求头里的变量引用在登录请求之后新增一个创建订单HTTP 请求给它添加一个 HTTP Header Manager把登录获取到的 accessToken 加到请求头里Header Name: AuthorizationHeader Value:Bearer ${accessToken}请求体我用 Body Data 传 JSON{ userId: ${userId}, operator: ${userName}, permission: ${perm1} }JMeter 在执行时会把${userId}、${userName}、${perm1}替换成登录响应里提取出来的实际值。只要提取规则没问题这个请求在任何环境、任何用户下都能正常跑。要注意一个小坑如果变量值本身可能包含双引号、反斜杠这类 JSON 特殊字符直接拼进 JSON 字符串里会把请求体弄坏。我们这个例子里权限值是order:view这种安全格式所以没问题如果遇到用户昵称带特殊字符建议用 JSR223 生成 JSON 字符串而不是纯文本拼接。4.3 用 Debug Sampler 验证所有变量都取对了调试关联脚本时Debug Sampler 是我的标准操作。在 Thread Group 下加一个 Debug Sampler勾选 JMeter variables 字段运行一次后在查看结果树里找到 Debug Sampler 的响应数据就能看到当前线程所有 JMeter 变量及其值。比如验证时会看到这样一段accessToken8f3a9c21-6b7e-4d2a-9f10-2c8d4e6a7b10 userId1024 userNametester01 roleoperator perm1order:view perm2order:create perm3order:batch如果变量没出现在列表里说明提取失败如果变量值是NOT_FOUND之类的缺省值说明正则没匹配上需要回去检查正则和响应原文。Debug Sampler 会额外产生一条采样记录还有一定开销所以只在调试阶段用压测前记得删掉或禁用。5. 实战里最常见的几个坑按排查链路一个个排5.1 贪婪匹配.*把不该吃的都吃了这是正则提取多个内容时最经典的问题。假设响应是nameapple/nameprice10/pricenamebanana/nameprice20/price你想取第一个name里的内容正则写了name(.*)/name。结果捕获组会一直吃到最后一个/name取出来是apple/nameprice10/pricenamebanana明显不是你要的。原因很简单.*是贪婪的会尽可能多地匹配。解决办法是用非贪婪写法.*?也就是遇到第一个结束标记就停。正则改成name(.*?)/name后取到的就是apple。在接口测试里我习惯用([^])而不是.*?来提取引号内的内容因为[^]天然不会跨过引号比非贪婪更不容易出错。5.2 特殊字符转义方括号、引号、换行提取 JSON 数组、HTML 属性时特殊字符的转义最容易出问题。JSON 数组的中括号在正则里必须转义比如要匹配[a,b]起码要写成\[([^])否则方括号会被当成字符类。反斜杠也要小心在 JMeter 界面的正则字段里要匹配一个普通的反斜杠字符本身通常需要写两个反斜杠\\但像\d、\w这种预定义字符类直接写一个反斜杠就行。另一个高频坑是换行。JMeter 默认情况下.是不匹配换行的。如果接口返回的是一个格式化过的多行 JSON正则里用.*很可能匹配不到跨行的内容。这时候可以在正则最前面加一个内联标记(?s)data:\{name:([^])(?s)让.可以匹配包括换行在内的所有字符。很多脚本明明单行能匹配一到多行就取不到值就是这个原因。5.3 匹配数字 0 和 -1 搞反前面已经强调过match number 填 0 是随机取一个匹配项不是全量提取。压测时如果这个变量要用来做数据区分填 0 会导致每个线程拿到不同的数据脚本行为不可预期数据对不上都查不出原因。填 -1 才是全量提取。用 -1 以后注意引用方式是${变量_1}、${变量_2}并可以结合${变量_matchNr}获取数量。如果你发现自己引用${id}取不到值先想想是不是匹配数字填了 -1。5.4 变量旧值残留第二次取不到用的居然是上一次的值这个坑很隐蔽。JMeter 变量在一个线程内是长期存在的。第一次循环提取到了 token 并更新了变量第二次循环如果正则没匹配上而缺省值又留空变量不会自动清空线程内保存的还是第一次的 token。脚本不会报错接口可能也返回 200但数据已经不对了。所以缺省值一定不要留空。我习惯统一填NOT_FOUND然后在断言或者后续请求里能看到这个标记。更进一步可以在关键提取器后面加一个简单的 JSR223 Assertion判断变量值是不是NOT_FOUND是就直接断言失败让脚本快速提醒你。5.5 一条完整的排查链路如果你发现关联值取不到别瞎改按这个顺序一步步排除先用查看结果树确认响应原始文本确实包含目标内容注意有些接口返回的 JSON 会被浏览器渲染成对象实际网络报文长得不太一样。把响应原文复制到本地正则工具或者写个临时脚本先验证正则本身对不对。检查提取器是否挂在了正确的取样器下面有没有因为重定向导致目标内容在子请求里。检查括号、模板、匹配数字是否对应。模板写 $1$ 但正则里没括号必空。加 Debug Sampler 看变量名和下标是否正确重点看有没有_1、_g1、_matchNr这些后缀。最后检查缺省值确认是不是旧值残留导致看起来取到了但其实是上一次的。这套链路我用了很久基本上能解决九成以上的关联提取问题。6. 别只会正则JSON Extractor 和 JSR223 什么时候更好用6.1 标准 JSON 响应优先上 JSON Extractor如果你的接口返回的是标准 JSONJsonPath 通常是比正则舒服得多的选择。JMeter 自带 JSON Extractor添加路径是右键取样器 - 添加 - 后置处理器 - JSON Extractor。它最大的优势是支持一个控件里配置多行规则。比如登录响应的 example可以直接配三行Names of created variablesJSON Path expressionsMatch NumbersDefault ValuesaccessToken$.data.accessToken1NOT_FOUNDuserId$.data.userId1NOT_FOUNDrole$.data.role1NOT_FOUND不用再纠结引号转义、配对括号和字段顺序路径写对了就一定能取到。想取 permissions 里的全部值可以写$.data.permissions[*]配合 Match Numbers 填 -1同样会生成permissions_1、permissions_2配合 ForEach 也一样好用。6.2 复杂取值逻辑用 JSR223 Groovy 一锅端有些响应结构嵌套很深或者要做的不是取一个值而是按条件筛选几个值、拼装成新结构这种情况正则和 JSONPath 都会写得很别扭。JSR223 后处理器配 Groovy 就很灵活。典型的脚本长这样import groovy.json.JsonSlurper def response prev.getResponseDataAsString() def json new JsonSlurper().parseText(response) vars.put(accessToken, json.data.accessToken) vars.put(userId, json.data.userId.toString()) vars.put(permCount, json.data.permissions.size().toString())这里prev是取样器结果对象vars是 JMeter 变量池。脚本里想循环、想判断、想格式化全都自由。有一个性能提示在压测场景里JSR223 脚本每次执行都有成本脚本里能不开源库就别乱开能少写循环就少写。JMeter 的 JSR223 配置窗口里如果有 Cache compiled script 这样的选项务必勾上让脚本编译结果被缓存复用。6.3 我的选型建议我现在写 JMeter 脚本时有个习惯先看返回格式再决定工具而不是拿到标题就默认用正则。返回是标准 JSON优先 JSON Extractor响应是 HTML、纯文本、或者老系统的自定义报文才考虑正则提取器需要做判断、循环、拼接这类逻辑直接上 JSR223。正则表达式提取器的优势在于纯文本世界里它没有替代品但代价是表达式本身可读性差、容易踩转义和贪婪的坑。在团队协作的项目里脚本是给别人看的优先选择别人一眼能看懂的方案比炫技重要得多。回到匹配多个内容这件事我的体会是先分清楚到底属于哪种需求是一次取多个字段还是取一串同类型数据。这两个方向对匹配数字和模板参数的配置影响非常大。方向定对了工具选顺手了正则提取器在 JMeter 关联里就再也不是麻烦只是工具箱里一个普通工具。