跳至内容

正则表达式

正则表达式(Regular Expression)是一套描述字符串匹配规则的语法,常用于表单校验、爬虫数据提取、日志分析等场景。Python 的 re 模块提供完整支持。

元字符速查

预定义字符集

元字符匹配内容
.任意字符(不含换行符 \n,加 re.S 后包含)
\d数字 [0-9]
\D非数字
\w字母、数字、下划线 [a-zA-Z0-9_](支持 Unicode)
\W\w
\s空白符(空格、\t\n\r
\S非空白符
\n换行符
\t制表符
[abc]字符组,匹配其中任一字符
[^abc]反字符组,匹配不在其中的字符
[a-z]范围,匹配 a 到 z 的任一小写字母

量词

量词含义
?0 次或 1 次
+1 次或多次
*0 次或多次
{n}恰好 n 次
{n,}至少 n 次
{n,m}n 到 m 次

量词默认贪婪匹配(尽可能多匹配),在量词后加 ? 变为非贪婪(尽可能少匹配)。

边界与分组

语法含义
^字符串开头(多行模式下为每行开头)
$字符串结尾(多行模式下为每行结尾)
\b单词边界
a|b匹配 a 或 b
(abc)分组,可通过 group(n) 提取
(?:abc)非捕获分组,不占用编号
(?P<name>abc)命名分组
(?P=name)引用命名分组已匹配的内容

re 模块常用函数

import re

text = "2024-06-01, 联系电话: 13812345678, 备用: 010-87654321"

# findall:返回所有匹配的列表
phones = re.findall(r"1[3-9]\d{9}", text)
print(phones)   # ['13812345678']

# search:返回第一个匹配对象(未匹配返回 None)
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", text)
if m:
    print(m.group())    # 2024-06-01(整个匹配)
    print(m.group(1))   # 2024(第一个分组)
    print(m.group(2))   # 06
    print(m.span())     # (0, 10)(匹配的起止位置)

# match:只匹配字符串开头(其余与 search 相同)
m = re.match(r"\d{4}", text)   # 匹配开头是否是4位数字

# fullmatch:整个字符串必须完全匹配
m = re.fullmatch(r"\d{11}", "13812345678")  # 完整11位数字

# finditer:返回迭代器,每个元素是 Match 对象
for m in re.finditer(r"\d+", text):
    print(m.group(), m.start())

# sub:替换匹配内容
clean = re.sub(r"\d{11}", "***", text)
print(clean)   # 2024-06-01, 联系电话: ***, 备用: 010-87654321

# subn:替换并返回替换次数
result, count = re.subn(r"\d+", "N", text)
print(result, count)

# split:按匹配分割字符串
parts = re.split(r"[,\s]+", "a, b,  c,d")
print(parts)   # ['a', 'b', 'c', 'd']

compile — 预编译正则

当同一模式需要多次使用时,预编译可提高性能:

import re

# 预编译
phone_pat = re.compile(r"1[3-9]\d{9}")
date_pat = re.compile(r"(\d{4})-(\d{2})-(\d{2})")

# 编译后的对象与 re 函数用法相同
print(phone_pat.findall("手机: 13912345678, 座机: 010-88888888"))
m = date_pat.search("日期: 2024-06-01")
if m:
    year, month, day = m.groups()
    print(year, month, day)  # 2024 06 01

修饰符(标志)

import re

# re.I(IGNORECASE):忽略大小写
re.findall(r"python", "Python PYTHON python", re.I)
# ['Python', 'PYTHON', 'python']

# re.M(MULTILINE):^ $ 匹配每行的开头/结尾
text = "first\nsecond\nthird"
re.findall(r"^\w+", text, re.M)   # ['first', 'second', 'third']

# re.S(DOTALL):. 匹配包括换行符在内的所有字符
re.search(r"a.+b", "a\nb", re.S)  # 可以匹配跨行内容

# re.X(VERBOSE):允许在正则中写注释和空格(提高可读性)
email_pat = re.compile(r"""
    [\w.\-]+    # 用户名部分
    @           # @ 符号
    [\w.\-]+    # 域名部分
    \.\w{2,6}   # 顶级域名
""", re.X)

贪婪 vs 非贪婪

import re

html = "<b>加粗</b>和<i>斜体</i>"

# 贪婪(默认):匹配尽可能长的字符串
re.findall(r"<.+>", html)
# ['<b>加粗</b>和<i>斜体</i>'](从第一个 < 到最后一个 >)

# 非贪婪(量词后加 ?):匹配尽可能短的字符串
re.findall(r"<.+?>", html)
# ['<b>', '</b>', '<i>', '</i>']

命名分组

命名分组比编号分组更清晰,通过 (?P<name>...) 定义,m.group("name") 提取:

import re

log = "2024-06-01 14:30:00 ERROR 数据库连接失败"

pat = re.compile(
    r"(?P<date>\d{4}-\d{2}-\d{2})"
    r" (?P<time>\d{2}:\d{2}:\d{2})"
    r" (?P<level>\w+)"
    r" (?P<message>.+)"
)

m = pat.match(log)
if m:
    print(m.group("date"))     # 2024-06-01
    print(m.group("level"))    # ERROR
    print(m.group("message"))  # 数据库连接失败
    print(m.groupdict())       # 所有命名分组的字典

实用正则示例

import re

# 手机号(大陆)
re.fullmatch(r"1[3-9]\d{9}", "13812345678")

# 邮箱
re.fullmatch(r"[\w.\-]+@[\w.\-]+\.\w{2,6}", "[email protected]")

# 日期(YYYY-MM-DD)
re.fullmatch(r"[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])", "2024-06-01")

# QQ 号(5-11 位,不以 0 开头)
re.fullmatch(r"[1-9]\d{4,10}", "123456789")

# IPv4 地址
re.fullmatch(r"(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)", "192.168.1.1")

# 提取 HTML 标签内容(简单场景,复杂场景推荐用 BeautifulSoup)
html = "<h1>标题</h1><p>段落</p>"
tags = re.findall(r"<(\w+)>(.*?)</\1>", html)
# [('h1', '标题'), ('p', '段落')]
正则表达式适合简单的字符串模式匹配,但不适合解析 HTML(嵌套结构)或 JSON。这类场景请使用专门的解析库(BeautifulSoup、json 模块等)。
最后更新于