上周帮同事处理一张四千多行的订单备注表,备注栏长这样:“张三 138****1234 上海市浦东新区XX路1号 购买3件白色L码”。他原本打算用Excel自带的查找替换、分列、LEFT加MID函数去硬拆,搞了一个下午,拆到后面发现手机号后面还跟着“(收货)”,地址里面又嵌着逗号,整个表格严重错位。我打开文件写了十几行VBA,配合正则表达式,十秒不到,姓名、电话、地址、件数、颜色尺码全部拆进独立列里,同事愣住,问我是不是用了什么外挂。
这就是Excel VBA和正则表达式组合起来最典型的价值。正则表达式这套匹配文本的规则,解决的核心问题就是“字符串不规整”:你要从一段乱七八糟的文本里提取手机号、金额、日期、身份证号,要按照规则批量替换掉多余内容,要判断某段文字是否符合格式——这些活如果全靠手工或者纯函数硬凑,写出来的公式又绕又脆,而交给正则,往往一两行代码就结束了。这一章内容对两类人最有用:一类是已经会VBA基础循环和单元格操作、但遇到复杂字符串就头疼的人;另一类是写过一点正则、但不知道怎么在VBA环境里落地、总是遇到报错的人。前几章我们聊过数组、字典和ADO的数据处理思路,这一章换个方向,专门研究文本处理的终极武器。
1. 为什么正则这么“香”:Excel原生文本方案的三个硬伤
1.1 查找替换和分列解决不了“结构不统一”的文本
先说一个最现实的场景:你的表格里有一列“备注”,内容长这样:张三 13812341234 上海市浦东新区XX路1号 3件白色L码。这一列看起来好像有规律,但仔细一看,有的行用空格分隔,有的行用逗号分隔,有的行中间还混着“联系人:张三”这样的前缀。这时候你用Excel的“分列”功能,指定空格作为分隔符,结果第一行拆对了,第二行拆出来5列,第三行又变成2列,整个表直接乱套。
查找替换就更不用说了。查找替换的本质是“精确匹配”或“通配符匹配”,它能处理“把所有‘张三’改成‘李四’”这种单一规则,但处理不了“把这段文字里所有13开头的11位数字都找出来”这种结构性需求。通配符里的*和?能力太弱,*只能表示“任意一串字符”,你没法告诉它“必须是数字”“必须刚好11位”“前面不能跟其他数字”这样的约束。所以只要数据没有统一的格式,手工肉眼拆就是无底洞,几千行能折腾一整天。
1.2 工作表函数能应付一部分,但写起来像天书
有基础的朋友可能会说,用LEFT、MID、RIGHT、FIND、SUBSTITUTE这些函数组合一下不就行了?确实,简单的场景可以。比如固定格式的“订单号ORD12345”,你知道“ORD”后面跟6位数字,那用MID(A2,4,6)就能提出来。但问题在于,一旦条件不固定,公式就开始爆炸。
我见过有人写过一个提取金额的公式,大概是=IF(ISNUMBER(FIND("元",A2)),MID(A2,FIND("元",A2)-6,6),"")这种,还要嵌套IFERROR、要判断小数点在不在、要判断负数有没有括号,最后写出来一个粮票一样长的公式,维护的人看到就想离职。更关键的是,函数方案一次性只能解决“这一列”的问题,换一个文本格式,整个公式推倒重写。
正则表达式的思路完全不同。它不关心文本的“位置”,只关心文本的“结构”。比如你要提取金额,模式就是“可能有负号、后面跟数字、可能有小数部分”,这和你文本前面是“实付”还是“应付”还是“价格:”没有关系。用结构描述替代位置计算,是文本处理思维上的一次升级。
1.3 这套引擎是Windows自带的,不需要额外装插件
很多人以为在Excel里用正则要装什么插件或者加载项,其实完全不需要。VBA调用的是微软VBScript正则表达式引擎,这个组件是Windows系统自带的COM对象,注册名是VBScript.RegExp。从Excel 2007到Office 365,从32位到64位,基本都稳定可用。你在VBA里写一句CreateObject("VBScript.RegExp"),正则能力就直接到手了。
这个方案还有个隐藏优势:跨宿主程序复用。同一套正则逻辑,你在Excel的VBA里能用,在Access的VBA里也能用,在WPS的宏编辑器里大概率也能用(前提是WPS版本支持宏)。我曾经把一套文本清洗的VBA代码从Excel原封不动搬到Access里处理数据库字段,正则部分一行没改,效果完全一致。所以花时间学这一章,收益不只是“会处理Excel表格”,而是掌握了一套通用的文本处理能力。
2. 第一次跑通VBA正则:对象、属性和四步调用套路
2.1 前期绑定还是后期绑定:先决定代码的写法
在VBA里调用正则,有两种写法。第一种是“前期绑定”:打开VBA编辑器,点菜单“工具→引用”,勾选Microsoft VBScript Regular Expressions 5.5,然后代码里直接写Dim reg As New RegExp。这样写的好处是写代码的时候有自动提示,输入reg.会弹出属性方法列表,不容易拼错。坏处是:万一换了一台没有勾选这个引用的电脑,代码会直接报“用户定义类型未定义”,对新手不友好。
第二种是“后期绑定”:不勾选引用,直接用CreateObject创建对象。代码写成Dim reg As Object、Set reg = CreateObject("VBScript.RegExp")。这样写的坏处是失去了自动提示,好处是代码到哪里都能跑。我个人建议,写工具类的VBA代码,一律用后期绑定。特别是你想把工作簿发给同事用,或者你自己经常在不同电脑间切换,后期绑定能少一堆莫名其妙的兼容问题。
Sub CreateRegExp() ' 后期绑定创建正则对象 Dim reg As Object Set reg = CreateObject("VBScript.RegExp") ' 设置匹配模式 reg.Pattern = "\d+" ' 匹配连续数字 reg.Global = True ' 全局匹配,而不是只匹配第一个 reg.IgnoreCase = True ' 忽略大小写 ' 测试 End Sub这里有一个新手常踩的坑:reg.Pattern里面写反斜杠,比如"\d+",在VBA字符串里不需要写成"\\d+",因为VBA里的反斜杠没有转义作用。很多人从Python或者Java转过来,习惯性地双写反斜杠,结果正则匹配不到任何内容,还以为自己模式写错了。记住,VBA里\d就是\d,直接写。
2.2 五个属性和方法,覆盖所有正则操作
RegExp对象的完整接口不复杂,核心就三个属性加三个方法。
| 成员 | 类型 | 作用 |
|---|---|---|
Pattern | 属性 | 设置或返回正则表达式模式 |
Global | 属性 | 是否全局匹配。False时只匹配第一个结果,True时匹配所有结果 |
IgnoreCase | 属性 | 是否忽略大小写,默认False |
MultiLine | 属性 | 是否把字符串视为多行,让^和$匹配每行的开头和结尾 |
Test | 方法 | 测试字符串是否匹配,返回True或False |
Execute | 方法 | 执行匹配,返回MatchCollection集合 |
Replace | 方法 | 按模式替换字符串,支持$1等分组引用 |
Test适合做判断,比如“这个字符串里有没有手机号”;Execute适合做提取,比如“把这段文字里所有手机号捞出来”;Replace适合做清洗,比如“把日期格式统一成2024-01-05”。这三个方法各管一摊,80%的文本处理需求都能覆盖。剩下20%的复杂场景,基本就是Execute提取后配合数组、字典做二次加工。
2.3 第一个能跑的例子:从一行文本里抓出所有手机号
写一个实际可运行的例子。假设A列是订单备注,B列开始输出提取到的手机号。手机号用最简单的规则:1开头,第二位是3到9,后面再跟9位数字。正则模式就是1[3-9]\d{9}。
Sub ExtractPhone() Dim reg As Object Dim matches As Object Dim m As Object Dim r As Range Dim outRow As Long Dim strText As String ' 正则对象放循环外,避免重复创建 Set reg = CreateObject("VBScript.RegExp") reg.Pattern = "1[3-9]\d{9}" reg.Global = True outRow = 1 For Each r In Range("A1:A" & Range("A" & Rows.Count).End(xlUp).Row) strText = r.Value If reg.Test(strText) Then Set matches = reg.Execute(strText) For Each m In matches Cells(outRow, 2).Value = m.Value outRow = outRow + 1 Next m End If Next r End Sub这段代码里有两个关键细节。第一,Set matches = reg.Execute(strText)执行一次能拿到这个单元格里所有匹配项,所以要用For Each m In matches去遍历,m.Value就是匹配到的手机号字符串。第二,reg对象必须在For Each循环外创建,如果每循环一个单元格就创建一次正则对象,四千行跑下来性能会差好几倍。这个习惯一旦养成了,后面处理大数据量会少踩很多坑。
跑完这个例子,你可能会在“立即窗口”用Debug.Print reg.Replace("电话:13812341234", "[隐藏]")测试替换效果,也可以试试把Global改成False看看匹配结果差异,你会发现Global=False时,Execute只返回第一个手机号,这就是为什么我代码里习惯性把Global=True写上的原因。
3. VBA正则语法速查:这几张表够你应付八成场景
3.1 常用元字符和量词一览
这一节把最常用的正则语法过一遍。别被符号吓到,核心逻辑其实就三条:[]表示“这一个位置可以是哪些字符”,量词表示“前面那个字符出现多少次”,()表示“把这一段打包成一个整体”。理解了这三条,正则你就入门了。
| 语法 | 含义 | 示例 |
|---|---|---|
\d | 匹配一位数字 | \d\d\d匹配三位数字 |
\w | 匹配字母、数字、下划线,注意只匹配ASCII字符 | \w+匹配一串单词字符 |
\s | 匹配空白字符,包括空格、制表符、换行 | \s+匹配连续空白 |
. | 匹配除换行符外的任意字符 | a.c匹配abc、a1c |
^ | 匹配字符串开头 | ^\d表示以数字开头 |
$ | 匹配字符串结尾 | \d$表示以数字结尾 |
* | 前面的字符出现0次或多次 | ab*匹配a、ab、abb |
+ | 前面的字符出现1次或多次 | ab+匹配ab、abb |
? | 前面的字符出现0次或1次 | ab?匹配a、ab |
{n} | 前面的字符出现正好n次 | \d{4}匹配四位数字 |
{n,} | 前面的字符出现至少n次 | \d{2,}匹配两位以上数字 |
{n,m} | 前面的字符出现n到m次 | \d{1,3}匹配1到3位数字 |
[] | 字符类,匹配其中任意一个字符 | [abc]匹配a、b、c |
[^] | 否定字符类 | [^0-9]匹配非数字 |
() | 分组 | (ab)+匹配ab、abab |
| | 或 | a|b匹配a或b |
一个新手特别容易困惑的地方:.不匹配换行符。所以如果你在处理一段跨行的文本,想要“匹配任意字符”,不能用.*,要用[\s\S]*或者[\s\S]+。\s匹配空白,\S匹配非空白,两者合在一起就包含了所有字符。这个坑在抓取网页文本时特别常见,网页源码里到处都是\r\n换行,你写<div>.*</div>匹配不到,必须用<div>[\s\S]*</div>。
再补充一个中文匹配的关键点:\w在VBScript正则里不会匹配中文。你写\w+去匹配“张三李四”,结果是匹配不到的。要匹配中文,可以直接把起止字符写进字符类:[一-龥]+。这个范围的原理是中文字符在Unicode编码里是连续分布的,从一(U+4E00)到龥(U+9FA5)。这是VBA正则处理中文最实用的写法,建议直接背下来。
3.2 分组捕获和SubMatches:提取完整信息的钥匙
正则里的括号不仅是“打包”,还有一个重要能力叫“捕获分组”。比如要匹配“商品名称:白色T恤 价格:199元”里的名称和价格,可以写模式商品名称:(.+?)\s+价格:(\d+元),括号里捕获到的内容会按顺序存进SubMatches集合,SubMatches(0)对应第一个括号,SubMatches(1)对应第二个括号。
看一个实际提取身份证号出生日期例子。身份证号是18位,中间的8位是出生日期,比如110101199003077733。
Sub ExtractBirthday() Dim reg As Object Dim matches As Object Dim m As Object Dim strText As String Set reg = CreateObject("VBScript.RegExp") reg.Pattern = "(\d{6})(\d{4})(\d{2})(\d{2})\d{4}" reg.Global = True strText = "客户身份证号110101199003077733,请核对" Set matches = reg.Execute(strText) For Each m In matches Debug.Print "出生年份:" & m.SubMatches(1) Debug.Print "出生月份:" & m.SubMatches(2) Debug.Print "出生日期:" & m.SubMatches(3) Next m End Subm.SubMatches(1)取到的就是出生年份分组(\d{4})里的内容。注意SubMatches是一个从0开始索引的集合,第一个括号对应0,第二个对应1,跟数组的习惯一致。这个写法在处理“一条文本里夹着多个结构化信息”时特别高效,比你先用Mid数位置、再算偏移量要可靠得多。
3.3 VBScript正则的三个语法限制:避免“复制粘贴就报错”
如果你之前是在Python的re模块或者JavaScript里学的正则,转到VBA会碰到几个不支持的语法。这里集中说,免得你从网上复制一段现成正则过来直接报运行时错误5017。
第一,VBScript正则不支持字符串的\uXXXX写法。在Python里你可以写[\u4e00-\u9fa5]匹配中文,在VBA里这样写无效,必须直接写中文字符区间[一-龥]。同理,\p{L}这种Unicode属性写法也不支持。
第二,VBScript正则不支持后行断言,也就是(?<=...)这种结构。向前断言(?=...)和负向前瞻(?!...)是支持的,但“匹配前面必须是某字符”的能力没有。替代方案通常是连上下文一起匹配出来,再用SubMatches或者Replace把多余部分去掉。比如想提取价格:199元里的数字,写成价格:(\d+)元,取第一个分组,效果等价。
第三,VBScript正则不支持非捕获分组(?:...)和命名分组(?<name>...)。所有括号都会被当成捕获组参与编号。所以网上很多精细优化的正则,直接搬进来轻则不支持,重则理解错导致结果不对。我处理这类问题的方法是:拿到别人的正则先扫一遍有没有这三个语法,有就先简化重写,不要抱着侥幸心理测试。
4. 四个高频场景实战:提取、清洗、分类都能用正则
4.1 批量提取金额,带小数、带负号一次搞定
财务对账经常遇到“退款-12.50元”这种字样,要快速汇总金额。正则模式-?\d+(\.\d+)?可以覆盖“可选的负号、整数部分、可选的小数部分”。如果想连同千分位一起处理,比如“1,234.56”,模式改成-?\d{1,3}(,\d{3})*(\.\d+)?。
Sub ExtractAmount() Dim reg As Object Dim matches As Object Dim m As Object Dim r As Range Dim total As Double Dim i As Long Set reg = CreateObject("VBScript.RegExp") reg.Pattern = "-?\d+(\.\d+)?" reg.Global = True For Each r In Range("A1:A" & Range("A" & Rows.Count).End(xlUp).Row) Set matches = reg.Execute(r.Value) For Each m In matches ' 转为数值累加 total = total + Val(m.Value) i = i + 1 Next m Next r MsgBox "共提取到 " & i & " 笔金额,合计 " & total End Sub这里有一个隐藏细节:Val(m.Value)会自动处理字符串开头可能带的正负号,但如果金额是负数且用了括号表示,比如(12.50),Val函数不会把括号识别成负数,正则得预先处理。我一般先做一步reg.Replace把括号形式替换成负号形式,再跑提取。这类“先清洗、再提取、后计算”的三段式流程,是文本处理项目里的标准动作。
4.2 日期清洗:把各种不规整写法统一成标准格式
导出来的Excel日期经常五花八门:2024.1.5、2024-01-05、2024年1月5日、2024/01/05。如果直接用Excel的单元格格式去改,很多是文本字符串根本不认。用正则把它们统一成2024-01-05算是最经典的正则替换案例。
模式这样写:(\d{4})[年.\-/](\d{1,2})[月.\-/](\d{1,2})日?。拆解一下:第一个分组捕获4位年份,接下来匹配“年、点、横杠、斜杠”任意一个分隔符,第二个分组捕获1到2位月份,再匹配“月、点、横杠、斜杠”任意一个分隔符,第三个分组捕获1到2位日期,最后的日?表示“日”字可以出现也可以不出现。
Sub NormalizeDate() Dim reg As Object Dim r As Range Dim result As String Dim y As Long, m As Long, d As Long Set reg = CreateObject("VBScript.RegExp") reg.Pattern = "(\d{4})[年.\-/](\d{1,2})[月.\-/](\d{1,2})日?" reg.Global = True For Each r In Range("A1:A" & Range("A" & Rows.Count).End(xlUp).Row) If reg.Test(r.Value) Then ' 从第一次匹配的分组里取出年月日 y = CLng(reg.Execute(r.Value)(0).SubMatches(0)) m = CLng(reg.Execute(r.Value)(0).SubMatches(1)) d = CLng(reg.Execute(r.Value)(0).SubMatches(2)) r.Value = Format(DateSerial(y, m, d), "yyyy-mm-dd") End If Next r End Sub这里用DateSerial函数组装日期,比手工拼接字符串补零要稳妥。1月会被Format自动补成01,7日补成07。注意代码里我把reg.Execute(r.Value)(0)重复用了两遍,最理想的做法是先把匹配对象存进一个变量,减少重复调用,代码更流畅。我写这段是为了先讲清楚逻辑,实际项目里不会这样连写三遍Execute。
4.3 正则加字典:给备注文本打标签并快速分类统计
正则和VBA字典是黄金搭档。字典负责“键值对统计”,正则负责“从杂乱文本里识别标签”。比如一张工单表,备注里可能写着“客户要求加急”“订单备注:促销活动”“普通申请”等,你想统计各标签出现次数。
Sub CountTags() Dim reg As Object Dim matches As Object Dim m As Object Dim dict As Object Dim r As Range Set reg = CreateObject("VBScript.RegExp") reg.Pattern = "紧急|加急|普通|促销|退款" reg.Global = True Set dict = CreateObject("Scripting.Dictionary") For Each r In Range("A1:A" & Range("A" & Rows.Count).End(xlUp).Row) If reg.Test(r.Value) Then Set matches = reg.Execute(r.Value) For Each m In matches If dict.Exists(m.Value) Then dict(m.Value) = dict(m.Value) + 1 Else dict.Add m.Value, 1 End If Next m End If Next r ' 输出统计结果到D列 Dim i As Long Dim key As Variant i = 1 For Each key In dict.Keys Cells(i, 4).Value = key Cells(i, 5).Value = dict(key) i = i + 1 Next key End Sub这段代码最大的价值在于,词典的Keys()天然去重,统计结果直接写回单元格,比用CountIf一列一列去数不知道快多少。而且正则模式里的|表示“或”,你可以随时加新标签,比如加一个“投诉”,正则改成紧急|加急|普通|促销|退款|投诉就行。维护成本几乎为零,这在Excel函数方案里很难做到。
4.4 网页抓取数据后的文本清理:去标签、提取链接
很多朋友做网页数据下载时,把HTML源码直接粘进了单元格,里面全是<div>、<span>、class="..."这些标签。正则清理HTML是这个场景的看家本领。
最简单的清理HTML标签,模式就是<[^>]+>。意思是“一个小于号、后面跟一个或多个非大于号字符、最后加一个大于号”。Replace成空字符串,标签全没了。注意[^>]这个写法很关键,它保证了匹配到的是“最短的”标签,不会把整个HTML大段误删。
提取链接是另一个高频需求。假设网页源码里有<a href="http://example.com/page" title="某页面">,想提取href属性里的网址,模式写href="([^"]*)"。取SubMatches(0)就是链接地址。这里[^"]*的意思是“在双引号里面匹配任意多个非双引号字符”,这样链接里即使有空格也能完整捕获。
需要提醒的是,正则处理HTML只适用于简单、可控的页面结构。如果你要解析结构复杂、嵌套严重、属性顺序不固定的HTML,用正则硬写会非常痛苦,不如用XMLHTTP请求后换专业的解析工具,或者调用HTMLDocument对象去遍历DOM。VBA环境里有现成的MSHTML库可以解析HTML文档对象,那才是重型场景的正道。正则适合“快速清洗文本、抓取字段”,不必死磕到底。
5. 正则排查清单:报错、匹配不到、跑得慢都能快速定位
5.1 常见报错和结果不对的原因速查
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 运行时错误5017 | 正则语法错误 | 检查括号是否配对、[是否闭合、量词是否放到无意义位置 |
| 明明有内容却匹配不到 | 模式里用了\u或其他不支持的语法 | 改成[一-龥]这种VBA支持的写法 |
| 只匹配到第一个结果 | Global是False | 设置reg.Global = True |
| 替换结果把整段文字都删了 | 贪婪匹配导致匹配范围过大 | 用.*?或[^>]*这类非贪婪写法 |
| 从网页文本提取总是失败 | 文本里有换行,.不匹配换行 | 用[\s\S]*替代.* |
| 中文匹配不到 | \w和\d不覆盖中文 | 直接用[一-龥]范围匹配 |
Replace之后有$1没被替换 | 分组数量对不上 | 确认模式里的括号数量和$1的编号对应 |
关于贪婪匹配,多说两句。模式<.*>在匹配<a>123</a>时,因为.*会尽量吃更多字符,所以匹配到的结果是<a>123</a>整个字符串,而不是<a>。想让匹配停下来,要么用[^>]*限定“只能是非大于号字符”,要么用*?推进非贪婪匹配。两者在VBA正则里都有效,我实际项目中用[^>]*更多,因为意图更明确,避免某些场景下非贪婪的边界行为跟预想不一致。
5.2 宏被禁用、加载项被禁用的处理办法
正则写得再好,如果文件打开就提示“宏已被禁用”,代码根本跑不起来。Excel默认的安全策略是禁用所有宏,所以你需要把文件另存为xlsm格式,然后去“文件→选项→信任中心→信任中心设置→宏设置”里选择“启用所有宏”。自己写的工具,启用宏是没问题的,但要留意不要随意运行来路不明的工作簿宏。
如果你在Excel里遇到“加载项被禁用”的提示,多半是某个COM加载项启动时报错,被Excel自动禁用了。处理路径是:“文件→选项→加载项→管理COM加载项→转到”,在列表里重新勾选被禁用的项并确定。有时候加载项之间还会打架,表现为快捷键失灵,比如Ctrl+C和Ctrl+V用不了,或者工具栏选项消失。我遇到过装了一个PDF导入工具后,整个Excel的快捷键全失效的情况,禁用那个加载项后恢复正常。这类问题跟代码本身无关,但排查的时候容易耽误时间,知道路径能少走弯路。
还有一个小细节:修改VBA代码后,如果Excel提示“是否保存对VBA工程所做更改”,记得保存,并且该文件必须是xlsm格式才能保留宏代码。如果你另存为xlsx,代码会静默丢失,下次打开只剩数据。这个坑很多人碰到过一次就再也不会忘。
5.3 几千行数据跑得慢的优化技巧
正则本身执行速度是很快的,感觉慢通常是VBA的瓶颈。最常见的问题是在循环里反复操作单元格,频繁读写工作表。优化思路有三个。
第一,正则对象创建一次,不要放在循环里。一个CreateObject虽然开销不大,但几千行每行都创建一次,慢得肉眼可见。第二,把数据一次性读入数组,在内存里用正则处理完再批量写回。数组操作比单元格逐行读写快一个数量级,这个从我们前面聊VBA数组那章就强调过。第三,处理前保存Application.ScreenUpdating = False和Application.Calculation = xlCalculationManual,处理完在Finally或结尾恢复,界面不刷新、公式不重算,速度提升非常明显。
更要警惕的是“灾难性回溯”。如果你写的正则模式里出现了嵌套的重复,比如(.*)*、(\d+)+这样的组合,当匹配失败时,引擎会尝试指数级的回溯路径,小数据感觉不到,几千行文本可能直接卡死。遇到模式复杂、跑起来特别慢的情况,优先简化量词嵌套,把多个*、+压缩成单一的[\s\S]*或[^>]*,通常能救回来。
我在实际项目中处理文本类VBA任务,已经习惯了“先写一小段测试代码在立即窗口里验证正则模式,确认匹配结果无误,再套进正式数据处理循环”的工作方式。测正则时用最短的代表性字符串,跑通了再上完整数据,一次报错排查成本远低于在几千行数据里反复试错。这个小习惯,对我个人来说帮了大忙。
正则表达式这套东西,初次接触会觉得满屏幕符号像乱码,但一旦你习惯了“用结构描述文本”的思路,再回头用Excel的手工操作和公式拼接,就会有一种明显的降维打击感。很多时候我处理一张乱糟糟的导入表,前后不超二十分钟,一半时间花在理解字段逻辑上,另一半就是写正则加跑测试,真正到了清洗环节反而是最轻松的。下一章我准备聊聊VBA读写文件,其实文件解析、日志分析这些场景,正则依然是绝对主力。