影刀RPA 正则表达式实战文本提取与替换署名林焱什么情况用什么RPA采集网页文本后需要提取手机号、邮箱、金额等结构化数据日志文件中需要提取错误时间戳和错误码Excel单元格中混杂了文字和数字需要分离。这些都离不开正则表达式。场景推荐函数特点提取第一个匹配re.search返回Match对象提取所有匹配re.findall返回列表替换匹配文本re.sub支持回调函数分割文本re.split按模式分割怎么做一常用正则模式速查importre# 手机号phonere.search(r1[3-9]\d{9},text)# 邮箱emailre.search(r[\w.-][\w-]\.[\w.-],text)# 身份证号id_cardre.search(r\d{17}[\dXx],text)# 金额带千分位amountre.search(r[\d,]\.?\d*,text)# 日期datere.search(r\d{4}[-/年]\d{1,2}[-/月]\d{1,2},text)# URLurlre.search(rhttps?://[\w./?%-],text)# IP地址ipre.search(r\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3},text)# 中文chinesere.findall(r[\u4e00-\u9fa5],text)二提取数据店群矩阵自动化突破运营极限importre text订单号DD202406150001金额1,299.00下单时间2024年6月15日14:30:25# 提取订单号order_matchre.search(r订单号[:]\s*(\S),text)iforder_match:order_idorder_match.group(1)print(f订单号:{order_id})# 提取金额去掉和逗号amount_matchre.search(r金额[:][¥]\s*([\d,.]),text)ifamount_match:amountfloat(amount_match.group(1).replace(,,))print(f金额:{amount})# 提取日期时间datetime_matchre.search(r(\d{4}年\d{1,2}月\d{1,2}日)\s*(\d{1,2}:\d{2}:\d{2}),text)ifdatetime_match:datedatetime_match.group(1)timedatetime_match.group(2)print(f日期:{date}, 时间:{time})三批量提取importre text 联系方式 张三 13812345678 zhangsanemail.com 李四 15987654321 lisitest.com 王五 13711223344 wangwuexample.com # 提取所有手机号phonesre.findall(r1[3-9]\d{9},text)print(f手机号:{phones})# 提取所有邮箱emailsre.findall(r[\w.-][\w-]\.[\w.-],text)print(f邮箱:{emails})# 提取姓名手机号分组contactsre.findall(r([\u4e00-\u9fa5])\s(1[3-9]\d{9})\s([\w.-][\w.-]),text)forname,phone,emailincontacts:print(f{name}|{phone}|{email})四文本替换importre text手机号13812345678邮箱testemail.com身份证310101199001011234# 脱敏替换手机号中间4位变****maskedre.sub(r(1[3-9]\d)\d{4}(\d{4}),r\1****\2,text)print(masked)# 输出: 手机号138****5678邮箱testemail.com身份证310101199001011234# 脱敏替换身份证出生年月日变****maskedre.sub(r(\d{6})\d{8}(\d{4}),r\1********\2,masked)print(masked)# 输出: 手机号138****5678邮箱testemail.com身份证310101********1234# 脱敏替换邮箱用户名只保留首字符maskedre.sub(r([\w])[\w.-]*([\w.-]),r\1***\2,masked)print(masked)# 输出: 手机号138****5678邮箱t***email.com身份证310101********1234五复杂场景 - 日志解析importre log_text [2024-06-15 09:30:15] INFO - 用户登录成功 user_id1001 [2024-06-15 09:31:22] ERROR - 数据库连接失败 codeDB_TIMEOUT host192.168.1.100 [2024-06-15 09:32:10] WARN - 请求超时 codeREQ_TIMEOUT urlhttps://api.example.com/data [2024-06-15 09:35:00] ERROR - 文件不存在 codeFILE_NOT_FOUND path/data/report.xlsx # 提取所有错误日志error_patternr\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\sERROR\s-\s(.?)\scode(\w)errorsre.findall(error_pattern,log_text)fortimestamp,message,codeinerrors:print(f时间:{timestamp}, 错误:{message}, 代码:{code})# 提取IP地址ipsre.findall(rhost(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}),log_text)print(fIP地址:{ips})# 提取URLurlsre.findall(rurl(https?://[\w./?%-]),log_text)print(fURL:{urls})完整流程网页数据清洗提取importre# yd_input: raw_textraw_textyd_input.get(raw_text,)# 清洗HTML标签clean_textre.sub(r[^],,raw_text)# 去除多余空白clean_textre.sub(r\s, ,clean_text).strip()# 提取结构化数据result{phones:list(set(re.findall(r1[3-9]\d{9},clean_text))),emails:list(set(re.findall(r[\w.-][\w-]\.[\w.-],clean_text))),urls:list(set(re.findall(rhttps?://[\w./?%-],clean_text))),amounts:[],dates:[],id_cards:list(set(re.findall(r\d{17}[\dXx],clean_text))),}# 提取金额并转为数字amount_matchesre.findall(r[¥]?\s*([\d,]\.?\d*)\s*元,clean_text)foramtinamount_matches:try:result[amounts].append(float(amt.replace(,,)))except:pass# 提取日期date_matchesre.findall(r\d{4}[-/年]\d{1,2}[-/月]\d{1,2},clean_text)result[dates]date_matches# 清洗后的文本result[clean_text]clean_text yd_outputresult有什么坑坑一贪婪匹配导致多提取现象想提取div内容/div中的内容结果提取了从第一个div到最后一个/div之间的所有内容。原因.*默认贪婪匹配尽可能多匹配。解决用.*?非贪婪匹配importre htmldiv内容A/divspan中间/spandiv内容B/div# 错误贪婪匹配resultre.findall(rdiv(.*)/div,html)print(result)# [内容A/divspan中间/spandiv内容B] ❌ 多了# 正确非贪婪匹配resultre.findall(rdiv(.*?)/div,html)print(result)# [内容A, 内容B] ✓坑二特殊字符未转义现象想匹配文本中的价格100元但正则写价格100元匹配不到。temu店群自动化报活动案例原因正则中.匹配任意字符虽然这里恰好能匹配到但如果文本是价格?100元?是正则特殊字符。解决用re.escape转义importre# 要匹配的文本包含特殊字符keyword价格?(含税)escapedre.escape(keyword)# 自动转义特殊字符print(escaped)# 价格\?\(含税\)text价格?(含税)100元matchre.search(escapedr[:]\s*([\d,.]),text)ifmatch:print(match.group(1))# 100坑三中文匹配范围不准现象用\w匹配中文时有时成功有时失败。原因\w在默认模式只匹配[a-zA-Z0-9_]不匹配中文。需要加re.UNICODE标志Python3默认开启。解决用Unicode范围匹配中文importre texthello世界123# 匹配中文chinesere.findall(r[\u4e00-\u9fa5],text)print(chinese)# [世界]# 匹配中文英文数字all_wordsre.findall(r[\u4e00-\u9fa5\w],text)print(all_words)# [hello, 世界123]# 匹配中文标点punctuationre.findall(r[\u3000-\u303f\uff00-\uffef],text)坑四re.sub替换时的反向引用现象想把2024-06-15替换为2024/06/15但写re.sub(r\d-(\d)-(\d), r\d/\1/\2, text)不工作。原因替换字符串中\d是字面量不是正则。需要用分组引用。解决importre text日期2024-06-15# 正确用分组引用resultre.sub(r(\d{4})-(\d{2})-(\d{2}),r\1/\2/\3,text)print(result)# 日期2024/06/15# 常见分组引用# \1, \2, \3... 引用第N个分组# \g1, \g2 等价写法更清晰# 用回调函数做更复杂的替换defformat_date(match):yearmatch.group(1)monthmatch.group(2)daymatch.group(3)returnf{year}年{int(month)}月{int(day)}日resultre.sub(r(\d{4})-(\d{2})-(\d{2}),format_date,text)print(result)# 日期2024年6月15日