影刀RPA 正则表达式入门:文本提取的利器
作者:林焱
做RPA绕不开文本处理,而正则表达式是文本处理里最强大的工具。提取手机号、清洗网页文本、验证邮箱格式、解析URL参数……这些用字符串方法写几十行代码的事,正则一行搞定。
但正则也是很多人的噩梦——一堆符号看不懂、写了匹配不上、匹配上了但多匹配了。这篇文章用RPA实战场景讲正则,不讲理论只讲用法。
一、什么情况用什么
| 场景 | 正则表达式 | 说明 |
|---|---|---|
| 提取手机号 | \d{11} | 11位数字 |
| 提取邮箱 | [\w.]+@[\w.]+ | 简单邮箱匹配 |
| 提取URL | https?://[\w./?-]+ | HTTP/HTTPS链接 |
| 提取金额 | ¥\d+\.?\d* | 人民币金额 |
| 提取日期 | \d{4}-\d{2}-\d{2} | YYYY-MM-DD |
| 验证是否纯数字 | ^\d+$ | 全数字 |
| 替换HTML标签 | <[^>]+> | 匹配所有HTML标签 |
| 分割复杂文本 | [,;\s]+ | 逗号/分号/空格分割 |
| 提取括号内容 | \(([^)]+)\) | 提取圆括号里的内容 |
二、怎么做
2.1 正则基础速查
先记住这些最常用的符号:
\d → 匹配数字 (0-9) \w → 匹配字母数字下划线 (a-zA-Z0-9_) \s → 匹配空白字符 (空格/制表符/换行) . → 匹配任意字符(除换行) + → 前面的出现1次或多次 * → 前面的出现0次或多次 ? → 前面的出现0次或1次 {n} → 前面的出现恰好n次 {n,m} → 前面的出现n到m次 [video(video-CwdASn1q-1784395628028)(type-csdn)(url-https://live.csdn.net/v/embed/525010)(image-https://v-blog.csdnimg.cn/asset/f4faa587144cb7070f19e8b36813806b/cover/Cover0.jpg)(title-店群矩阵自动化突破运营极限!)] ^ → 开头 $ → 结尾 [] → 字符集合,匹配其中任意一个 () → 分组,提取匹配的部分 | → 或 \ → 转义特殊字符2.2 在影刀中使用正则
用Python的re模块(在Python代码节点里):
importre# ===== 提取 =====# 提取所有手机号text="联系方式:13812345678,备用:15987654321,客服:400-123-4567"phones=re.findall(r'1[3-9]\d{9}',text)print(f"手机号:{phones}")# ['13812345678', '15987654321']# 提取邮箱text="邮箱:test@example.com 和 admin@company.cn"emails=re.findall(r'[\w.]+@[\w.]+\.\w+',text)print(f"邮箱:{emails}")# ['test@example.com', 'admin@company.cn']# 提取金额text="商品A ¥99.5,商品B ¥1,299.00,商品C ¥50"prices=re.findall(r'¥([\d,]+\.?\d*)',text)print(f"金额:{prices}")# ['99.5', '1,299.00', '50']# 提取日期text="发布日期:2026-06-15,更新日期:2026-06-20"dates=re.findall(r'\d{4}-\d{2}-\d{2}',text)print(f"日期:{dates}")# ['2026-06-15', '2026-06-20']# 提取URLtext='访问 https://www.example.com/path?id=123 或 http://test.cn 获取详情'urls=re.findall(r'https?://[\w./?=&-]+',text)print(f"URL:{urls}")2.3 提取带分组的数据
用括号()可以提取匹配结果中的特定部分:
importre# 提取键值对text="name=张三 age=25 city=北京"pattern=r'(\w+)=(\S+)'matches=re.findall(pattern,text)result=dict(matches)print(result)# {'name': '张三', 'age': '25', 'city': '北京'}# 提取链接和文本html='<a href="https://example.com">示例链接</a><a href="https://test.cn">测试</a>'pattern=r'<a href="([^"]+)">([^<]+)</a>'links=re.findall(pattern,html)forurl,textinlinks:print(f"链接:{url}, 文本:{text}")# 提取括号内的内容text="商品名称(红色)和 商品名称(蓝色)"pattern=r'(([^)]+))'contents=re.findall(pattern,text)print(f"括号内容:{contents}")# ['红色', '蓝色']2.4 替换文本
importre# 替换敏感信息text="手机号13812345678,身份证110101199001011234"# 手机号中间4位替换为*masked=re.sub(r'(1[3-9]\d)\d{4}(\d{4})',r'\1****\2',text)print(masked)# 手机号138****5678,身份证110101199001011234# 身份证中间替换为*masked_id=re.sub(r'(\d{6})\d{8}(\d{4})',r'\1********\2',text)print(masked_id)# 手机号13812345678,身份证110101********1234# 去除HTML标签html='<p>这是<b>加粗</b>的<i>文本</i></p>'clean_text=re.sub(r'<[^>]+>','',html)print(clean_text)# 这是加粗的文本# 去除多余空白text=" 多个 空格 和\n\n换行 "cleaned=re.sub(r'\s+',' ',text).strip()print(cleaned)# 多个 空格 和 换行2.5 验证格式
importredefvalidate_phone(phone):"""验证手机号格式"""pattern=r'^1[3-9]\d{9}$'returnbool(re.match(pattern,phone))defvalidate_email(email):"""验证邮箱格式"""pattern=r'^[\w.]+@[\w.]+\.\w+$'returnbool(re.match(pattern,email))defvalidate_id_card(id_card):"""验证身份证号格式(18位)"""pattern=r'^\d{17}[\dXx]$'returnbool(re.match(pattern,id_card))defvalidate_url(url):"""验证URL格式"""pattern=r'^https?://[\w./?=&%-]+$'returnbool(re.match(pattern,url))# 测试print(validate_phone("13812345678"))# Trueprint(validate_phone("12345678901"))# False(不是1开头)print(validate_email("test@example.com"))# Trueprint(validate_email("test@example"))# False(没有完整域名)2.6 实战:网页文本清洗
采集网页数据时,文本里经常有各种杂乱字符:
importredefclean_web_text(text):"""清洗网页文本"""ifnottext:return""# 去除HTML标签text=re.sub(r'<[^>]+>','',text)# 去除HTML实体text=text.replace(' ',' ')text=text.replace('&','&')text=text.replace('<','<')text=text.replace('>','>')text=text.replace('"','"')text=re.sub(r'&#(\d+);',lambdam:chr(int(m.group(1))),text)# 去除多余空白text=re.sub(r'\s+',' ',text)# 去除首尾空白text=text.strip()returntext# 测试raw=" <p>商品 名称</p> \n <b>价格</b>: ¥99 "clean=clean_web_text(raw)print(f"清洗前: '{raw}'")print(f"清洗后: '{clean}'")# 商品 名称 价格: ¥992.7 实战:解析结构化文本
importredefparse_log_line(log):"""解析日志行"""# 格式: [2026-06-15 09:30:45] [INFO] [flow_name] 消息内容pattern=r'\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(\w+)\] \[([^\]]+)\] (.+)'match=re.match(pattern,log)ifmatch:return{'timestamp':match.group(1),'level':match.group(2),'source':match.group(3),'message':match.group(4)}returnNonelog="[2026-06-15 09:30:45] [INFO] [商品采集] 开始采集第1页"parsed=parse_log_line(log)print(parsed)# {'timestamp': '2026-06-15 09:30:45', 'level': 'INFO', 'source': '商品采集', 'message': '开始采集第1页'}defparse_url_params(url):"""解析URL参数"""# 提取查询字符串match=re.search(r'\?(.+)$',url)ifnotmatch:return{}query_string=match.group(1)params={}# 分割参数对forpairinquery_string.split('&'):if'='inpair:key,value=pair.split('=',1)params[key]=valuereturnparams url="https://example.com/search?keyword=rpa&page=3&sort=price_desc"params=parse_url_params(url)print(params)# {'keyword': 'rpa', 'page': '3', 'sort': 'price_desc'}三、有什么坑
坑1:贪婪匹配导致多提取
正则默认是贪婪的——会尽可能多地匹配:
importre text='<div>内容A</div><div>内容B</div>'# 贪婪匹配(错误)result=re.findall(r'<div>(.+)</div>',text)print(result)# ['内容A</div><div>内容B'] ← 多匹配了!# 非贪婪匹配(正确)result=re.findall(r'<div>(.+?)</div>',text)print(result)# ['内容A', '内容B'] ← 正确解决:在+或*后面加?变成非贪婪模式:+?*?
坑2:特殊字符没转义
正则里有特殊含义的字符(. * + ? ^ $ { } [ ] ( ) | \ /),如果要在文本中匹配这些字符本身,需要转义:
importre# 错误:直接匹配文件路径中的.text="file.txt"result=re.findall(r'file.txt',text)print(result)# ['file.txt'] 但也会匹配 'fileXtxt'# 正确:转义.[video(video-wE0Q4zpn-1784395634709)(type-csdn)(url-https://live.csdn.net/v/embed/524992)(image-https://v-blog.csdnimg.cn/asset/b59aed2f01d4fe8583467562aaf4dcfd/cover/Cover0.jpg)(title-temu店群自动化报活动案例)]result=re.findall(r'file\.txt',text)print(result)# ['file.txt']# 更简单的方法:用re.escapepattern=re.escape("file.txt")result=re.findall(pattern,text)坑3:中文匹配问题
\w默认不匹配中文。要匹配中文需要特殊处理:
importre# \w不匹配中文text="商品A价格99"result=re.findall(r'[\w]+',text)print(result)# ['A', '99'] ← 中文没匹配到# 方法一:显式指定中文范围result=re.findall(r'[\u4e00-\u9fff]+',text)print(result)# ['商品', '价格']# 方法二:使用re.UNICODE标志result=re.findall(r'\w+',text,re.UNICODE)print(result)# ['商品A价格99'](Python3中\w默认包含中文)# 匹配中文+英文+数字result=re.findall(r'[\u4e00-\u9fff\w]+',text)print(result)坑4:匹配速度慢
某些正则表达式在长文本上匹配极慢(正则灾难):
importreimporttime# 灾难性正则(回溯爆炸)bad_pattern=r'(a+)+b'text='a'*30# 30个astart=time.time()re.match(bad_pattern,text)elapsed=time.time()-start print(f"耗时:{elapsed:.3f}秒")# 可能要好几秒# 优化后的正则good_pattern=r'a+b'start=time.time()re.match(good_pattern,text)elapsed=time.time()-startprint(f"耗时:{elapsed:.3f}秒")# 几乎瞬间解决:避免嵌套量词(如(a+)+),用更简单的写法替代。
坑5:跨行匹配失败
默认情况下.不匹配换行符,多行文本匹配会失败:
importre text="""第一行 第二行 第三行"""# 默认不跨行result=re.findall(r'第一行.第二行',text)print(result)# [] ← 匹配失败# 方法一:使用re.DOTALL标志result=re.findall(r'第一行.第二行',text,re.DOTALL)print(result)# ['第一行\n第二行']# 方法二:用\s匹配换行result=re.findall(r'第一行\s第二行',text)print(result)# ['第一行\n第二行']总结
正则表达式的核心要点:
先测再用于生产——用在线工具(如regex101.com)测试正则
用非贪婪模式——提取内容时加
?避免多匹配特殊字符要转义——用
re.escape()处理用户输入中文用
\u4e00-\u9fff——\w对中文的支持不一定可靠避免灾难性正则——别用嵌套量词,能简化就简化
多行用
re.DOTALL——跨行匹配时别忘了加标志