正则表达式的高效应用

你是否也经历过这些让人抓狂的瞬间?

  • 爬回来的评论里混着几十种格式的手机号,手动复制粘贴到眼花;
  • 办公室的课程名单用全角、半角逗号、分号甚至空格混着分隔,.split() 直接摆烂;
  • 聊天机器人日志里的敏感词需要一键替换成 *,还得忽略大小写。

如果有一把文本处理瑞士军刀,能让你用一套规则搞定所有复杂模式,你会不会心动?这把刀就是正则表达式(Regular Expression,简称 Regex)。在爬虫、日志清洗、自动化办公脚本里,它都是 Daoman Python AI 高频使用的核心工具。

接下来,我们不讲天书,只讲用得上的思路。


1. 先记住这 8 个元字符,80% 的场景都能搞定

正则表达式由普通字符(比如 a、5)和赋予魔力的元字符共同组成。新手完全不需要背诵所有符号,先拿下下面这张速查表,就能写出大部分实用规则:

符号释义示例匹配结果
.除换行外的任意单个字符b.tbatb1tb#t
\d数字,等价于 [0-9]\d{3}123955
\w字母、数字、下划线,等价于 [a-zA-Z0-9_]\w+python_3hello
\s空格、制表符、换行等空白字符love\syoulove youlove you
^ / $字符串开头 / 结尾^The匹配以 The 开头的行
[]字符集合,匹配其中任意一个[aeiou]aeo
* / +重复 0 次或多次 / 至少 1 次\d+11234
?重复 0 次或 1 次(也可开启非贪婪模式)https?httphttps

⚠️ 转义提醒:当你想匹配 .(* 这些特殊的“元字符”本身时,记得在前面加上反斜杠,例如 \.\(。否则它们会变成规则,而不是字面量。


2. 用好 Python re 模块的 6 个核心函数

Python 内置的 re 模块就是正则的执行引擎,无需安装第三方库。日常使用,牢记这 6 个“骨干”函数就足够了:

核心函数功能说明使用场景
re.compile(pattern)预编译正则,重复使用可以大幅提升效率循环里跑十万条日志时用
re.match()从字符串开头开始匹配验证格式,比如判断是不是邮箱开头
re.search()扫描整个字符串,返回第一个匹配对象找到文本里的第一个关键词
re.findall()返回所有匹配结果的列表批量提取手机号、邮箱
re.sub()替换所有匹配项敏感词过滤、统一时间格式
re.split()根据正则规则灵活拆分字符串处理多分隔符混合的文本

3. 三个即拿即用的实战案例

光讲理论太枯燥,直接上三个办公/爬虫/日常工作中一定会遇到的场景。

案例 1:忽略大小写的敏感词一键过滤

re.sub 配合标志位 re.IGNORECASE(可简写为 re.I),再结合 [] 处理谐音与形近字,就能快速脱敏:

import re

raw_comment = "Oh, Shit! 你是傻逼吗? Fuck you. 沙比东西"

# 模式:用 | 连接精确词,[] 收容变体
sensitive_pattern = r'fuck|shit|[傻煞沙][比笔逼叉缺雕]'

# 替换为 *,忽略大小写
clean_comment = re.sub(sensitive_pattern, '*', raw_comment, flags=re.I)
print(clean_comment)  # 输出:Oh, *! 你是*吗? * you. *东西

案例 2:从混乱信息里捞取手机号

假设我们有一堆掺杂姓名的日志,需要提取所有符合国内规则的手机号(1 开头、第二位 3-9、后面 9 位数字)。re.findall 一步搞定,千万别加 ^...$,那是用来验证整个字符串的:

import re

raw_log = "下单人:张三,电话13512345678;投诉人:李四,留的不是110,是15688889999"

phone_pattern = r'1[3-9]\d{9}'
phones = re.findall(phone_pattern, raw_log)
print(phones)  # 输出:['13512345678', '15688889999']

案例 3:一网打尽混合分隔符的拆分

当字符串中同时出现逗号、分号、竖线、全角字符、不定量空格时,普通 str.split() 就会罢工。而 re.split 用一个模式就能搞定所有分隔符:

import re

raw_course = "Python,Java;Go  C++|Rust,TypeScript"
# 分隔符模式:, ; 竖线 中文逗号 或 1 个以上空白
split_pattern = r'[,;;\|\s,]+'
courses = re.split(split_pattern, raw_course)
print(courses)  # 输出:['Python', 'Java', 'Go', 'C++', 'Rust', 'TypeScript']

4. 贪婪匹配 vs. 非贪婪匹配:新手最容易踩的坑

正则默认是贪婪的——它能多匹配就多匹配,直到撑满。加一个 ? 就能让它变成懒惰的,遇第一个结束标记就停。来看一个 HTML 提取的对比实验:

import re

html_text = "<div>苹果</div><div>香蕉</div>"

# 贪婪模式:从第一个 <div> 一直吃到最后一个 </div>
greedy_result = re.findall(r'<div>.*</div>', html_text)
print(greedy_result)  # 输出:['<div>苹果</div><div>香蕉</div>']

# 非贪婪模式:.*? 遇到第一个 </div> 立刻收手
lazy_result = re.findall(r'<div>.*?</div>', html_text)
print(lazy_result)  # 输出:['<div>苹果</div>', '<div>香蕉</div>']

记住:.*? 是数据抽取里的黄金搭档,很多看似复杂的需求,原因就是忘了加上这个 ?


最后:3 条立即上手的避坑指南

  1. 字符串用 r'' 原始标记
    Python 本身的 \n 会被解释成换行,而正则里需要匹配字面量 \n 就容易冲突。用 r'' 包裹模式,可以原样保留反斜杠,再也不怕转义错乱。

  2. 循环前务必预编译
    如果你要在 for 循环里把同一个正则用上千次,先用 compiled = re.compile(pattern) 存起来,再调用 compiled.findall()。实测效率能提升 30% ~ 50%,数据量越大收益越明显。

  3. 先在线调试,再写代码
    推荐使用 regex101.com,它不仅能彩色可视化匹配过程,还能一键生成 Python、Java、JavaScript 等语言的代码。边测边改,告别“写正则靠玄学”的痛苦。


(全文完,约 2000 字)