影刀RPA 正则表达式实战:文本提取的高级武器

影刀RPA 正则表达式实战:文本提取的高级武器

作者:林焱


什么情况用

影刀内置了「提取文本」「匹配文本」等节点,但遇到以下场景就抓瞎了:

  • 从一段HTML里提取所有图片的src地址
  • 一段自由文本里提取出所有金额(¥3,580.00、1580元、$299)
  • 从100条日志中过滤出包含ERROR且后面跟着堆栈信息的行
  • 判断用户输入的身份证号格式是否正确

这些不是简单的「找某个关键词」或「按分隔符切分」能搞定的——得用正则表达式。在影刀的Python节点里,正则三行代码能顶二十个内置节点的效果。

核心场景:需要从非结构化文本中提取复杂模式的数据。


怎么做

第一步:正则速查——30个最常用模式

拼多多店群自动化报活动上架!

importre# ===== 最常用的正则模式 =====PATTERNS={# 1. 手机号(中国大陆)"phone":r'1[3-9]\d{9}',# 2. 身份证号(18位)"id_card":r'\d{17}[\dXx]',# 3. 邮箱"email":r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',# 4. 网址URL"url":r'https?://[^\s<>"{}|\\^`\[\]]+',# 5. IP地址"ip":r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}',# 6. 日期(YYYY-MM-DD)"date_iso":r'\d{4}-\d{2}-\d{2}',# 7. 日期(YYYY年MM月DD日)"date_cn":r'\d{4}年\d{1,2}月\d{1,2}日',# 8. 时间(HH:MM:SS)"time":r'\d{2}:\d{2}:\d{2}',# 9. 金额(带¥符号或"元"结尾)"amount":r'[¥¥]\s*[\d,]+\.?\d*|[\d,]+\.?\d*\s*元',# 10. 百分比"percent":r'\d+\.?\d*\s*%',# 11. 中文字符"chinese":r'[\u4e00-\u9fff]+',# 12. 英文字母(大小写)"english":r'[a-zA-Z]+',# 13. 数字"digits":r'\d+',# 14. 小数"decimal":r'\d+\.\d+',# 15. 邮政编码"zip_code":r'\d{6}',# 16. 车牌号"plate":r'[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤川青藏琼宁][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]',# 17. HTML标签"html_tag":r'<[^>]+>',# 18. HTML img标签的src"img_src":r'<img[^>]+src=["\']([^"\']+)["\']',# 19. Markdown链接 [text](url)"md_link":r'\[([^\]]+)\]\(([^)]+)\)',# 20. 统一社会信用代码(18位)"credit_code":r'[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}',}# 快速测试deftest_pattern(pattern,text):matches=re.findall(pattern,text)returnmatchesprint(test_pattern(PATTERNS["phone"],"联系:13812345678,备用15987654321"))# ['13812345678', '15987654321']print(test_pattern(PATTERNS["amount"],"总价¥3,580.00,优惠200元"))# ['¥3,580.00', '200元']print(test_pattern(PATTERNS["img_src"],'<img class="pic" src="https://img.com/1.jpg" alt="图">'))# ['https://img.com/1.jpg']

第二步:Pattern写不出来的三个信号

很多人卡在写Pattern上,但有些场景根本不需要自己写:

信号1:你在搜「XX正则表达式怎么写」→ 直接用上面的速查表,或者让AI写。

信号2:同一个Pattern改了5次还没匹配上 → 用在线工具调试,推荐 regex101.com,可视化显示匹配结果。

信号3:Pattern越来越长、越来越像乱码 → 停下来,可能你不需要正则,用字符串的split()find()startswith()更简单更可读。

第三步:实战——从HTML中提取所有图片链接

defextract_all_images_from_html(html):"""从HTML中提取所有图片URL"""# 匹配 img 标签的 src 属性img_urls=re.findall(r'<img[^>]+src=["\']([^"\']+)["\']',html)# 也匹配 background-image: url(...)bg_urls=re.findall(r'background-image:\s*url\(["\']?([^"\')\s]+)["\']?\)',html)# 去重 + 过滤掉base64内嵌图片all_urls=set()forurlinimg_urls+bg_urls:ifurlandnoturl.startswith('data:image'):# 排除base64all_urls.add(url)returnlist(all_urls)

第四步:实战——日志文件解析

defparse_nginx_log(log_line):"""解析Nginx访问日志"""pattern=r'(\S+) - - \[([^\]]+)\] "(\S+) (\S+) \S+" (\d+) (\d+) "([^"]*)" "([^"]*)"'match=re.match(pattern,log_line)ifmatch:return{"ip":match.group(1),"timestamp":match.group(2),"method":match.group(3),"path":match.group(4),"status":int(match.group(5)),"size":int(match.group(6)),"referer":match.group(7),"user_agent":match.group(8)}returnNone# 测试log='192.168.1.1 - - [26/Jun/2024:14:30:00 +0800] "GET /api/products HTTP/1.1" 200 1234 "https://google.com" "Mozilla/5.0"'print(parse_nginx_log(log))

第五步:正则的性能优化——预编译+非贪婪

importtime# ❌ 差:每次调用都编译正则defbad_extract(texts):results=[]fortintexts:# 每次循环都重新编译一次found=re.findall(r'\d{4}-\d{2}-\d{2}',t)results.append(found)returnresults# ✅ 好:预编译,重复使用DATE_PATTERN=re.compile(r'\d{4}-\d{2}-\d{2}')defgood_extract(texts):![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7b13d9c2049641af877aea41c9c712a1.png#pic_center)results=[]fortintexts:found=DATE_PATTERN.findall(t)results.append(found)returnresults# 速度对比(10000次调用)text="今天是2024-06-26,昨天是2024-06-25"texts=[text]*10000# start = time.time(); bad_extract(texts); print(f"未预编译:{time.time()-start:.3f}s")# start = time.time(); good_extract(texts); print(f"预编译:{time.time()-start:.3f}s")# 差距约30-50%

非贪婪匹配——最容易出bug的点:

html="<div>内容1</div><div>内容2</div>"# ❌ 贪婪匹配——匹配了所有内容直到最后一个</div>greedy=re.findall(r'<div>(.*)</div>',html)print(greedy)# ['内容1</div><div>内容2'] —— 不是你想要的!# ✅ 非贪婪匹配——遇到第一个</div>就停lazy=re.findall(r'<div>(.*?)</div>',html)print(lazy)# ['内容1', '内容2'] —— 正确!

加个?就是从贪婪变非贪婪,这个小问号值千金。

第六步:正则与其他方法的组合拳

别试图用一个正则解决所有问题。最实用的做法是组合:

defextract_structured_info(text):""" 从随意格式的文本中提取结构化信息 策略:先用大范围匹配,再用正则精细化提取 """result={}# 1. 先分块——按「姓名」「电话」「地址」等关键词分割blocks=re.split(r'(姓名|电话|地址|邮箱|公司)[::]',text)# 2. 逐块用对应的正则提取foriinrange(1,len(blocks),2):label=blocks[i].strip()value=blocks[i+1].strip()ifi+1<len(blocks)else""if"电话"inlabel:phone=re.search(r'1[3-9]\d{9}',value)![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/04ccb46901ca489cbb772439e55ff2b5.png#pic_center)result["phone"]=phone.group()ifphoneelsevalueelif"邮箱"inlabel:email=re.search(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',value)result["email"]=email.group()ifemailelsevalueelif"地址"inlabel:result["address"]=value.strip()else:result[label]=value.strip()returnresult# 测试text="姓名:张三 电话:13812345678 地址:北京市海淀区中关村大街1号 邮箱:zhangsan@company.com"print(extract_structured_info(text))

有什么坑

坑1:.默认不匹配换行符

TEMU店群矩阵自动化运营核价报活动

Python的re模块默认.不匹配换行符\n

text="第一行\n第二行"# ❌ 没匹配到print(re.findall(r'第一行.*第二行',text))# []![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fe03b60501604c228a13ef3a8f77f1c2.png#pic_center)# ✅ 加上 re.DOTALL 标志print(re.findall(r'第一行.*第二行',text,re.DOTALL))# ['第一行\n第二行']

坑2:中文与\w的兼容性

\w默认只匹配[a-zA-Z0-9_],不匹配中文。

text="用户名:张三123"# ❌ \w 不包含中文print(re.findall(r'\w+',text))# ['123'] —— "张三"丢了# ✅ 加 re.A 或直接用字符类print(re.findall(r'[\u4e00-\u9fff\w]+',text))# ['用户名', '张三123']

坑3:正则回溯导致的性能灾难

一个看似简单的正则,遇到特定输入可能挂死。

# 灾难性回溯的正则dangerous=re.compile(r'(a+)+b')# 遇到 'aaaaaaaaaaaaaaaaaaaaac'(很多a但结尾不是b)# 这个正则可能跑几秒钟甚至超时![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/901271adab3049a68475b50033a9a647.png#pic_center)text='a'*30+'c'# dangerous.match(text) # 慎跑!

解决方法:避免嵌套的量词(x+)+,用(a+)b就够了。

坑4:贪婪与非贪婪的直觉陷阱

# 这段HTML里有多个spanhtml='<span class="price">¥99</span><span class="discount">-20</span>'# 直觉想到的Patternprices=re.findall(r'<span[^>]*>(.*)</span>',html)print(prices)# ['¥99</span><span class="discount">-20'] —— 错了!# 正确:加 ?prices=re.findall(r'<span[^>]*>(.*?)</span>',html)print(prices)# ['¥99', '-20'] —— 正确!

总结:正则是RPA文本处理的高级武器,但不要过度依赖。核心三板斧:保存常用Pattern速查、用在线工具调试(regex101.com)、优先用split/find等字符串方法处理简单场景。记住那个小问号——.*?.*安全一百倍。