Python: 正则表达式
正则表达式(Regular Expression,简称 regex)是一种描述文本模式的特殊语言。用一行正则表达式,可以完成几十行手写代码才能做到的文本匹配和提取工作。它在数据清洗、日志分析、表单验证中无处不在。
1. 什么是正则表达式
正则表达式是用特殊符号描述"我想找什么样的字符串"。
PYTHON
import re
# 最简单的正则:直接匹配字符串
pattern = r"hello"
text = "hello world"
result = re.search(pattern, text)
if result:
print("找到了!") # 找到了!
print(result.group()) # hello
print(result.start()) # 0(起始位置)
print(result.end()) # 5(结束位置)
⚠️ 写正则时在字符串前面加
r(原始字符串)。 r"\n" 是反斜杠+字母 n,不是换行符。正则里大量用到反斜杠,不加 r 会很痛苦。
2. 常用方法
PYTHON
import re
text = "我的邮箱是 zhangsan@example.com,请发邮件。还有 lisi@test.com"
# search() —— 查找第一个匹配
result = re.search(r"\w+@\w+\.\w+", text)
print(result.group()) # zhangsan@example.com
# findall() —— 查找所有匹配
emails = re.findall(r"\w+@\w+\.\w+", text)
print(emails) # ['zhangsan@example.com', 'lisi@test.com']
# match() —— 从开头匹配
print(re.match(r"我的", text)) # 匹配(在开头)
print(re.match(r"邮箱", text)) # None(不在开头)
# sub() —— 替换
masked = re.sub(r"\w+@", "***@", text)
print(masked) # 我的邮箱是 ***@example.com,请发邮件。还有 ***@test.com
3. 元字符速查表
| 元字符 | 含义 | 示例 | 匹配 |
|---|---|---|---|
. |
任意单个字符(除换行) | h.t |
hat, hot, hit |
\d |
数字 | \d{3} |
123, 456 |
\w |
字母/数字/下划线 | \w+ |
hello, abc123 |
\s |
空白字符 | \s |
空格、Tab、换行 |
* |
前一个字符 0 次或多次 | ab*c |
ac, abc, abbc |
+ |
前一个字符 1 次或多次 | ab+c |
abc, abbc(不含 ac) |
? |
前一个字符 0 次或 1 次 | colou?r |
color, colour |
{n} |
重复 n 次 | \d{4} |
2026, 1990 |
{n,m} |
重复 n 到 m 次 | \d{2,4} |
23, 456, 2026 |
^ |
字符串开头 | ^Hello |
Hello... |
$ |
字符串结尾 | end$ |
...end |
[] |
字符集合 | [aeiou] |
任意元音字母 |
| ` | ` | 或 | `cat |
▶ 示例:电话号验证(难度⭐⭐)
PYTHON
import re
def is_valid_phone(phone):
"""验证中国大陆手机号(11位,1开头)"""
pattern = r"^1[3-9]\d{9}$"
return bool(re.match(pattern, phone))
phones = ["13800138000", "12345678901", "010-12345678", "1380013800a"]
for p in phones:
print(f"{p}: {'✅' if is_valid_phone(p) else '❌'}")
# 提取文本中的所有手机号
text = "联系电话:13800138000,备用:13912345678,座机:010-12345678"
phones = re.findall(r"1[3-9]\d{9}", text)
print(f"找到手机号:{phones}")
输出:
TEXT
📖 仅展示
# 函数定义成功
4. 分组捕获
用括号 () 可以把匹配到的内容分成多个部分提取:
PYTHON
import re
# 提取邮箱的用户名和域名
email = "zhangsan@example.com"
pattern = r"(\w+)@(\w+\.\w+)"
result = re.search(pattern, email)
if result:
print(f"完整匹配:{result.group(0)}") # zhangsan@example.com
print(f"用户名:{result.group(1)}") # zhangsan
print(f"域名:{result.group(2)}") # example.com
# 从日志中提取信息
log = "2026-06-23 15:30:45 ERROR 数据库连接超时"
pattern = r"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)"
result = re.search(pattern, log)
print(f"日期:{result.group(1)}") # 2026-06-23
print(f"时间:{result.group(2)}") # 15:30:45
print(f"级别:{result.group(3)}") # ERROR
print(f"消息:{result.group(4)}") # 数据库连接超时
▶ 示例:URL 解析器(难度⭐⭐⭐)
PYTHON
import re
def parse_url(url):
"""解析 URL,提取协议、域名、路径、查询参数"""
pattern = r"(https?)://([^/]+)(/[^?]*)?(?:\?([^#]*))?"
result = re.search(pattern, url)
if not result:
return None
return {
"protocol": result.group(1),
"domain": result.group(2),
"path": result.group(3) or "/",
"query": result.group(4) or ""
}
urls = [
"https://www.example.com/path/page.html?id=123&name=test",
"http://localhost:8080/api/users",
"https://google.com"
]
for url in urls:
info = parse_url(url)
if info:
print(f"\nURL: {url}")
for key, value in info.items():
print(f" {key}: {value}")
输出:
TEXT
📖 仅展示
# 函数定义成功
▶ 示例:文本替换与脱敏(难度⭐⭐)
PYTHON
import re
text = "用户张三的手机号是13800138000,邮箱是zhangsan@example.com,身份证号110101199001011234"
phone_masked = re.sub(r"(1[3-9]\d)\d{4}(\d{4})", r"\1****\2", text)
print(f"手机脱敏:{phone_masked}")
email_masked = re.sub(r"(\w{2})\w+(@\w+\.\w+)", r"\1***\2", text)
print(f"邮箱脱敏:{email_masked}")
id_masked = re.sub(r"(\d{4})\d{10}(\d{4})", r"\1**********\2", text)
print(f"身份证脱敏:{id_masked}")
template = "你好,{name}!订单{order_id}已发货。"
data = {"name": "Alice", "order_id": "ORD2024001"}
result = re.sub(r"\{(\w+)\}", lambda m: data.get(m.group(1), m.group(0)), template)
print(f"模板替换:{result}")
输出:
TEXT
📖 仅展示
手机脱敏:用户张三的手机号是138****8000,邮箱是zhangsan@example.com,身份证号110101199001011234
邮箱脱敏:用户张三的手机号是13800138000,邮箱是zh***@example.com,身份证号110101199001011234
身份证脱敏:用户张三的手机号是13800138000,邮箱是zhangsan@example.com,身份证号1101**********1234
模板替换:你好,Alice!订单ORD2024001已发货。
5. 贪婪与懒惰
默认情况下,* 和 + 是贪婪的——尽可能多匹配。加 ? 变成懒惰——尽可能少匹配:
PYTHON
import re
text = "<h1>标题</h1><p>段落</p>"
# 贪婪模式——匹配尽可能多的内容
greedy = re.search(r"<.+>", text)
print(greedy.group()) # <h1>标题</h1><p>段落</p>
# 懒惰模式——匹配尽可能少的内容(加 ?)
lazy = re.search(r"<.+?>", text)
print(lazy.group()) # <h1>
💡
.*?(懒惰)vs .*(贪婪): 处理 HTML、JSON 等有嵌套结构的内容时,懒惰模式更常用。贪婪模式经常"越界"匹配到不该匹配的内容。如果不确定用什么,先用懒惰。
6. 常见应用场景
- 表单验证:验证邮箱格式、手机号、身份证号。
- 数据提取:从网页、日志、文本中提取特定模式的信息。
- 文本替换:批量替换符合模式的内容(如脱敏手机号中间四位)。
- 语法高亮:编辑器用正则识别关键字和字符串。
- 日志分析:从海量日志中筛选和处理特定模式的行。
❓ 常见问题
Q 正则表达式中的
r 前缀是什么意思?A
r 表示原始字符串(raw string)。在普通字符串中 \n 是换行,在原始字符串中 \n 就是反斜杠+字母n。正则表达式大量使用反斜杠(如 \d、\w),不加 r 就要写 \\d,非常痛苦。Q 正则表达式性能怎么样?
A 大多数情况足够快。但如果写得很差(如
(.*)* 这种嵌套量词),可能导致"灾难性回溯"——处理一段简单文本需要几十分钟。解决方案:避免嵌套量词、用懒惰模式、用 re.compile() 预编译重复使用的正则。Q 复杂的正则表达式很难读懂,有什么好办法?
A ① 加注释——
re.VERBOSE 模式允许正则有空格和注释。② 拆分成多个简单的正则。③ 用在线工具调试(如 regex101.com)。正则表达式是"写起来爽,读起来哭"的典型——不要追求"一行正则解决所有问题"。📖 小节
re.search()查找第一个匹配、findall()查找所有、sub()替换- 元字符:
\d数字、\w字母数字、\s空白、.任意字符 - 量词:
*任意、+至少1、?0或1、{n}精确n次 - 分组
()提取部分内容,group(1)获取第一个分组 - 贪婪默认尽可能多匹配,加
?变为懒惰尽可能少匹配 - 正则前面加
r避免反斜杠地狱
📝 作业
-
基础题(难度⭐):写一个函数
is_valid_email(email),用正则验证邮箱格式(含@,@前后非空,有域名后缀)。 -
进阶题(难度⭐⭐):写一个函数
extract_numbers(text),从字符串中提取所有数字(包括整数和小数),返回它们的和。例如"价格是 19.99 元,运费 5 元,优惠券 -10 元"→ 返回 14.99。 -
挑战题(难度⭐⭐⭐):写一个"简单模板引擎"。给定模板
"你好,{name}!你的订单 {order_id} 已发货,预计 {days} 天内到达。"和一个字典{"name": "Alice", "order_id": "2024001", "days": 3},用正则替换所有{变量名}为对应的值。提示:用re.sub()结合回调函数。