我刚开始学正则表达式时,感觉就像在看外星密码——^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ 这玩意儿真的是给人写的吗?但用了这么多年后,我发现它其实是程序员最值得投资的技能之一。正则表达式能让你在几秒钟内完成别人花半小时才能搞定的文本处理任务。
今天这篇教程,我打算换个思路:不讲枯燥的语法大全,而是从实战出发,配合三款我在工作中反复使用的在线工具,带你快速掌握正则表达式的核心。你不需要记住所有符号,只需要理解几个关键概念,然后学会怎么用工具去验证和调试你的表达式。
核心概念:你只需要记住这5个
字面字符与元字符
正则表达式由两种字符组成:字面字符(就是普通字符,比如字母、数字)和元字符(有特殊含义的符号,比如 .、*、+)。
举个栗子:hello 就是一个简单的正则,它会匹配字符串中的 “hello”。但 . 这个点可不是”点”的意思,它代表”任意单个字符”。
踩过的坑:如果你想匹配一个真实的点(比如邮箱地址中的点),必须用反斜杠转义:\.。我刚开始时经常忘记这一点,结果匹配出来的结果完全不对。
5个必会元字符
| 元字符 | 含义 | 示例 |
|——–|——|——|
| . | 匹配任意单个字符(除换行符) | h.t 匹配 “hat”, “hot”, “hit” |
| * | 匹配前一个字符0次或多次 | ho*t 匹配 “ht”, “hot”, “hoot” |
| + | 匹配前一个字符1次或多次 | ho+t 匹配 “hot”, “hoot”,但不匹配 “ht” |
| ? | 匹配前一个字符0次或1次 | ho?t 匹配 “ht”, “hot” |
| ^ 和 $ | 分别匹配字符串的开始和结束 | ^hello 只匹配开头的 “hello” |
字符类:快速匹配一组字符
用方括号 [] 定义一个字符集合,匹配其中的任意一个字符:
[abc]:匹配 a、b 或 c[a-z]:匹配任意小写字母[0-9]:匹配任意数字[^abc]:匹配除 a、b、c 之外的字符(注意这里的^表示”否定”)
我用的发现:[0-9] 可以简写为 \d,[a-zA-Z0-9_] 可以简写为 \w。这些简写能大大减少你的代码量。
量词与分组:控制重复次数
量词用花括号 {} 精确控制重复次数:
{n}:恰好 n 次{n,}:至少 n 次{n,m}:n 到 m 次
分组用圆括号 (),可以捕获匹配的内容供后续使用:
“python
提取邮箱中的用户名和域名
import re
email = "user@example.com"
match = re.search(r'^(.+)@(.+)$', email)
if match:
print(f"用户名: {match.group(1)}") # 输出: 用户名: user
print(f"域名: {match.group(2)}") # 输出: 域名: example.com
`
在线调试工具:边学边练
推荐工具1:Regex101
这是我用得最多的正则调试工具,没有之一。地址:https://regex101.com/
核心功能:
实战操作:
假设你要从一堆文本中提取所有邮箱地址:
``
我的邮箱是 user@example.com,备用邮箱是 admin@test.org,还有 support@company.co.uk
你会看到三个邮箱地址全部被高亮,右下角的解释面板告诉你这个正则的含义:
- [a-zA-Z0-9._%+-]+
:匹配一个或多个字母、数字、点、下划线等 - @
:匹配字面量 @ - [a-zA-Z0-9.-]+
:匹配域名部分 - \.
:匹配点(转义后的) - [a-zA-Z]{2,}
:匹配顶级域名(至少2个字母)
建议先:用 Regex101 的"快速参考"面板学习元字符,比看文档直观多了。
推荐工具2:RegExr
地址:https://regexr.com/
这个工具的特点是社区贡献的常用正则库,你可以在"社区"选项卡找到很多现成的正则表达式,比如 URL、IP 地址、日期等。
踩过的坑:RegExr 默认使用 JavaScript 风格的正则,如果你在其他语言(比如 Python)中使用,要注意一些差异(比如 \d 在 Python 中匹配 Unicode 数字,但在 JavaScript 中只匹配 ASCII 数字)。
实战操作:
推荐工具3:iHateRegex
地址:https://ihateregex.io/
这个工具的名字就很诚实——"我讨厌正则"。它的特色是可视化图解,把复杂的正则表达式转换成流程图,让你一眼看懂匹配逻辑。
实战操作:

实战案例:从文本中提取电话号码
现在我们来做一个完整的实战案例,用 Python 配合正则表达式从一段文本中提取电话号码。
问题描述
假设你有以下文本:
``
姓名:张三,电话:138-1234-5678
姓名:李四,手机:010-8765-4321
姓名:王五,联系方式:021-1111-2222
你需要提取所有手机号码,并转换成统一格式:13812345678(去掉横线)。
解决方案
`python
import re
text = """
姓名:张三,电话:138-1234-5678
姓名:李四,手机:010-8765-4321
姓名:王五,联系方式:021-1111-2222
"""
正则:匹配3位数字-4位数字-4位数字
pattern = r'(\d{3})-(\d{4})-(\d{4})'
使用 findall 查找所有匹配
matches = re.findall(pattern, text)
print("原始匹配结果:", matches)
输出: [('138', '1234', '5678'), ('010', '8765', '4321'), ('021', '1111', '2222')]
用 sub 替换,去掉横线
cleaned = re.sub(pattern, r'\1\2\3', text)
print("\n清理后的文本:")
print(cleaned)
输出:
姓名:张三,电话:13812345678
姓名:李四,手机:01087654321
姓名:王五,联系方式:02111112222
`
我用的发现:re.sub 的替换模式中,\1、\2、\3 分别引用第一个、第二个、第三个括号捕获的内容。这个功能太实用了,可以用来格式化各种文本。
进阶:处理多种格式
实际场景中,电话号码可能有多种格式:
- 138-1234-5678
- 13812345678
- (010) 8765-4321
- +86 138-1234-5678
这时候需要用更灵活的正则:
`python
import re
text = """
张三: 138-1234-5678
李四: 13812345678
王五: (010) 8765-4321
赵六: +86 138-1234-5678
"""
匹配多种格式的电话号码
pattern = r'(\+?\d{1,3}[-\s]?)?\(?\d{3}\)?[-\s]?\d{4}[-\s]?\d{4}'
matches = re.findall(pattern, text)
print("匹配到的电话号码:")
for m in matches:
print(m)
`
踩过的坑:findall 在有分组时只返回分组的内容,而不是整个匹配。如果你想获取整个匹配,要么用 finditer,要么把整个正则用括号包起来。
性能优化:从3秒降到0.01秒
正则表达式写不好会非常慢。我之前处理一个 10MB 的日志文件,用了一个糟糕的正则,跑了整整 3 秒才出结果。优化后降到 0.01 秒。
优化技巧
时要小心,它会尽可能多地匹配,然后回溯。改用 [^…]* 可以避免不必要的回溯。`python
# 慢:可能产生大量回溯
pattern1 = r'
'
# 快:明确指定不匹配 > 符号
pattern2 = r'
'
`
,让匹配停止在最早的可能位置。`python
# 贪婪:匹配从第一个 < 到最后一个 >
pattern1 = r'<.*>'
# 非贪婪:匹配到第一个 > 就停止
pattern2 = r'<.*?>'
`
预编译它。`python
import re
import time
# 不预编译
start = time.time()
for i in range(10000):
re.search(r'\d+', "abc123def")
print(f"不预编译: {time.time() - start:.3f}s")
# 预编译
pattern = re.compile(r'\d+')
start = time.time()
for i in range(10000):
pattern.search("abc123def")
print(f"预编译: {time.time() - start:.3f}s")
# 输出示例:不预编译: 0.045s vs 预编译: 0.012s(约4倍提升)
`
常见错误与调试方法
错误1:忘记转义特殊字符
`python
错误:想匹配文本中的点号
pattern = r'www.example.com'
实际上匹配了 "wwwXexampleXcom"(X 是任意字符)
正确:转义点号
pattern = r'www\.example\.com'
`
错误2:贪婪模式导致的意外匹配
`python
text = "I said 'hello' and then 'world'"
贪婪模式
pattern = r"'(.*)'"
match = re.search(pattern, text)
print(match.group(1)) # 输出: hello' and then 'world(不是我想要的)
非贪婪模式
pattern = r"'(.*?)'"
match = re.search(pattern, text)
print(match.group(1)) # 输出: hello(正确!)
`
调试方法
查看所有匹配,确保覆盖所有预期情况总结与行动建议
正则表达式不是用来背诵的,而是用来解决问题的。核心思想就三点:
, *, +, ?, ^$`)立刻行动:
本文由AI辅助创作,仅供参考。