正则表达式 – 使用总结(保姆级教程)

正则表达式 – 使用总结

在日常开发中,我们经常需要处理字符串,比如验证邮箱格式、提取手机号、清洗日志数据、替换敏感词等。这些场景背后,往往离不开一个强大又“神秘”的工具——正则表达式。它就像一把瑞士军刀,虽然外形简单,却能应对各种复杂的字符串操作任务。

如果你刚开始接触正则表达式,可能会觉得它像天书,符号一堆,看得一头雾水。但只要掌握了基本逻辑和常用模式,你会发现它其实非常有规律,甚至能让你的代码变得简洁高效。今天,我们就来系统梳理一下正则表达式的核心用法,帮助你从“看不懂”到“用得上”。


字符匹配基础:从简单到复杂

正则表达式最基础的功能,就是匹配特定的字符。比如你想检查一个字符串是否包含字母 a,就可以写 a。这看起来很简单,但它的威力在于可以组合使用。

举个例子:你想匹配“cat”这个词,可以直接写 cat。但如果想匹配“cat”或“cut”,可以用 cat|cut,这里的 | 就是“或”的意思。

再比如,你想匹配任意一个数字,可以写 [0-9]。这个方括号表示“字符类”,即从里面选一个字符。你也可以写成 \d,这是更简洁的写法,代表任意一个数字字符。

import re

text = "今天是2024年5月1日"
pattern = r'\d'  # \d 代表一个数字字符
matches = re.findall(pattern, text)
print(matches)  # 输出: ['2', '0', '2', '4', '5', '1']

注释:这里的 r'\d' 是原始字符串,避免反斜杠被转义。\d 是正则中预定义的字符类,等价于 [0-9]


元字符与量词:控制匹配次数

正则表达式真正强大的地方,在于它能精确控制“匹配多少次”。这主要靠“量词”来实现。

常见的量词有:

  • *:匹配 0 次或多次
  • +:匹配 1 次或多次
  • ?:匹配 0 次或 1 次
  • {n}:精确匹配 n 次
  • {n,}:至少匹配 n 次
  • {n,m}:匹配 n 到 m 次

举个实际例子:验证一个手机号是否合法(中国手机号,11位,以1开头,第二位是3-9)。

import re

phone = "13812345678"
pattern = r'^1[3-9]\d{9}$'


if re.match(pattern, phone):
    print("手机号格式正确")
else:
    print("手机号格式错误")

注释:^$ 是锚点,确保整个字符串完全匹配。如果没有它们,1[3-9]\d{9} 也可能匹配到“abc13812345678def”中的一部分,导致误判。


分组与捕获:提取关键信息

在实际项目中,我们不仅需要判断字符串是否符合格式,更常需要从中提取出有用的数据。这时,分组(())就派上用场了。

比如,从一段文本中提取日期(格式为 YYYY-MM-DD):

import re

text = "会议时间是2024-05-15,报名截止2024-06-30。"
pattern = r'(\d{4})-(\d{2})-(\d{2})'

matches = re.findall(pattern, text)

for year, month, day in matches:
    print(f"年份: {year}, 月份: {month}, 日期: {day}")

注释:(\d{4}) 表示匹配4位数字并作为一组,后面两个括号同理。re.findall 返回的是元组列表,每个元组对应一组捕获内容。

你也可以用 re.search 找到第一个匹配项,并访问分组:

match = re.search(r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
    year = match.group(1)  # 获取第一组
    month = match.group(2)  # 获取第二组
    day = match.group(3)    # 获取第三组
    print(f"提取到日期:{year}年{month}月{day}日")

注释:group(1)group(2) 等方法用于获取对应分组的内容,group(0) 是完整匹配的字符串。


常用场景:正则表达式在项目中的实战

验证邮箱格式

邮箱的格式比较复杂,但用正则可以高效处理。一个常见的邮箱正则如下:

import re

email = "user@example.com"
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'

if re.match(pattern, email):
    print("邮箱格式正确")
else:
    print("邮箱格式错误")

注释:^$ 锚定整个字符串。[a-zA-Z0-9._%+-]+ 表示用户名部分,可以包含字母、数字、点、下划线、百分号、加号、减号。@ 后是域名,最后是顶级域名,至少两个字母。

提取HTML标签中的内容

在爬虫或日志分析中,经常需要从HTML中提取标签内容。比如提取所有 <p> 标签中的文字:

import re

html = "<p>这是第一个段落</p><p>这是第二个段落</p>"
pattern = r'<p>(.*?)</p>'  # 非贪婪匹配

matches = re.findall(pattern, html)
for p in matches:
    print(p)

注释:.*? 是非贪婪模式,表示尽可能少地匹配字符,避免把多个标签的内容合并在一起。如果用 .*,可能会匹配到 <p>第一个</p><p>第二个</p>,导致结果错误。


常见陷阱与最佳实践

正则表达式虽然强大,但也有“坑”需要注意。以下是几个常见问题:

1. 懒惰匹配 vs. 贪婪匹配

默认情况下,正则使用贪婪匹配,即尽可能多地匹配。但有时我们希望“最少匹配”。

比如,想提取 a...b...a...b 中第一个 ab 之间的内容:

text = "a123b456b"
print(re.findall(r'a.*b', text))  # ['a123b456b']

print(re.findall(r'a.*?b', text))  # ['a123b']

注释:加 ? 后,量词变成懒惰模式,适合提取最短的匹配片段。

2. 转义特殊字符

正则中很多符号有特殊含义,比如 .*+?{} 等。如果你要匹配这些字符本身,必须用反斜杠转义。

比如匹配一个实际的点号:

text = "文件名: config.json"
pattern = r'\.json'  # 匹配 .json
print(re.findall(pattern, text))  # ['json']

注释:\. 表示匹配字面意义的点号,而不是“任意字符”。


总结与建议

正则表达式 – 使用总结,不是一朝一夕能掌握的,但它绝对是提升开发效率的重要技能。从最基础的字符匹配,到复杂的分组捕获、量词控制,再到实际项目中的验证与提取,每一步都有明确的应用场景。

建议初学者从简单开始,先掌握 ^$*+?()\d\w 等常用符号,然后逐步尝试写一些小练习,比如:

  • 验证身份证号(18位,最后一位可以是X)
  • 提取URL中的域名
  • 清洗日志中的错误码

随着实战经验积累,你会发现正则表达式不再神秘,反而成为你处理字符串问题的“利器”。

记住:正则表达式不是越复杂越好,而是越精确越高效。写完后,多测试几个边界 case,确保逻辑无误。

最后,别忘了:正则表达式 – 使用总结,不是终点,而是你编程能力进阶路上的一块重要基石。多练、多想、多查文档,你会越来越得心应手。