系列文章:Python 基础语法入门 本文目标:从零搞懂正则表达式语法,熟练使用 Python
re模块的 match/search/findall/sub/split 等方法,掌握邮箱、手机号、身份证等常用模式,并能用正则做日志分析、HTML 提取、密码校验等实战。
一、正则能干什么
正则表达式(Regular Expression,简称 regex / regexp)是一种描述字符串模式的微型语言。用短短一行就能表达"找出所有邮箱""验证手机号格式""把日期统一成 YYYY-MM-DD"这类需求。
常见应用场景:
| 场景 | 例子 |
|---|---|
| 数据提取 | 从一篇文章里提取所有邮箱、URL、手机号 |
| 格式校验 | 注册时校验邮箱、手机号、身份证是否合法 |
| 文本替换 | 把 2023/01/01 统一替换成 2023-01-01 |
| 日志清洗 | 从 Nginx 日志里提取 IP、状态码、访问路径 |
| 代码处理 | 批量重命名变量、提取函数签名 |
| 爬虫解析 | 从 HTML 里提取所有 <a href="..."> 链接 |
如果不用正则,写这些逻辑往往要几十行 if + for + 字符串切片;用正则,一行搞定。
二、正则基础语法
正则由普通字符 和元字符组成。普通字符匹配自身,元字符有特殊含义。下面逐一讲解。
2.1 预定义字符类
| 写法 | 含义 | 示例 |
|---|---|---|
. |
任意一个字符(除换行) | a.c 匹配 abc、a1c |
\d |
一个数字(0-9) | \d\d 匹配 42 |
\D |
非数字 | \D 匹配 a、! |
\w |
字母/数字/下划线 | \w+ 匹配 hello_123 |
\W |
非字母数字下划线 | \W 匹配 @、空格 |
\s |
空白符(空格、Tab、换行) | a\sb 匹配 a b |
\S |
非空白符 | |
\b |
单词边界 | \bcat\b 匹配单词 cat,不匹配 category |
import re
print(re.findall(r'\d', 'a1b2c3')) # ['1', '2', '3']
print(re.findall(r'\w+', 'hello world!')) # ['hello', 'world']
print(re.findall(r'\s', 'a b\tc\nd')) # [' ', '\t', '\n']
运行结果:
['1', '2', '3']
['hello', 'world']
[' ', '\t', '\n']
2.2 量词(重复次数)
| 写法 | 含义 | 示例 |
|---|---|---|
* |
0 次或多次 | ab*c 匹配 ac、abc、abbc |
+ |
1 次或多次 | ab+c 匹配 abc、abbc,不匹配 ac |
? |
0 次或 1 次 | colou?r 匹配 color、colour |
{m} |
恰好 m 次 | \d{3} 匹配 123 |
{m,} |
至少 m 次 | \d{2,} 匹配 12、123、1234 |
{m,n} |
m 到 n 次 | \d{2,4} 匹配 12、123、1234 |
import re
print(re.findall(r'ab*c', 'ac abc abbc abbbc')) # ['ac', 'abc', 'abbc', 'abbbc']
print(re.findall(r'\d{3}', '12 123 1234 12345')) # ['123', '123', '123', '123']
print(re.findall(r'\d{2,4}', '1 12 123 1234 12345')) # ['12', '123', '1234', '1234']
运行结果:
['ac', 'abc', 'abbc', 'abbbc']
['123', '123', '123', '123']
['12', '123', '1234', '1234']
2.3 边界与锚点
| 写法 | 含义 |
|---|---|
^ |
字符串开头 |
$ |
字符串结尾 |
\b |
单词边界 |
import re
print(re.findall(r'^Hello', 'Hello World')) # ['Hello'](开头匹配)
print(re.findall(r'^Hello', 'Say Hello')) # [](不在开头)
print(re.findall(r'World$', 'Hello World')) # ['World'](结尾匹配)
print(re.findall(r'\bcat\b', 'cat category cat')) # ['cat', 'cat'](只匹配独立单词)
运行结果:
['Hello']
[]
['World']
['cat', 'cat']
2.4 字符集 []
[] 表示匹配方括号里的任意一个字符:
-
[aeiou]:匹配任一元音字母。 -
[a-z]:匹配任一小写字母。 -
[0-9a-fA-F]:匹配十六进制字符。 -
[^0-9]:^在开头表示取反,匹配非数字。import re
print(re.findall(r'[aeiou]', 'hello world')) # ['e', 'o', 'o']
print(re.findall(r'[a-z][0-9]', 'a1 B2 c3')) # ['a1', 'c3']
print(re.findall(r'[^0-9]', 'ab12cd34')) # ['a', 'b', 'c', 'd']
运行结果:
['e', 'o', 'o']
['a1', 'c3']
['a', 'b', 'c', 'd']
2.5 分组 () 与或 |
() 把一部分模式分组 ,可以整体应用量词、提取子串;| 表示或。
import re
# (ab)+ 匹配 ab、abab、ababab
print(re.findall(r'(ab)+', 'ab abab ababab x')) # ['ab', 'ab', 'ab']
# cat|dog 匹配 cat 或 dog
print(re.findall(r'cat|dog', 'I have a cat and a dog')) # ['cat', 'dog']
运行结果:
['ab', 'ab', 'ab']
['cat', 'dog']
2.6 转义字符
元字符(. * + ? ( ) [ ] { } ^ $ \ |)在正则里有特殊含义,要匹配它们本身,需用 \ 转义。
import re
# 匹配 IP 里的点
print(re.findall(r'\d+\.\d+\.\d+\.\d+', 'IP是192.168.1.1')) # ['192.168.1.1']
# 匹配价格里的 $
print(re.findall(r'\$\d+', 'price $100 and $200')) # ['$100', '$200']
运行结果:
['192.168.1.1']
['$100', '$200']
提示:在 Python 里写正则建议用原始字符串
r'...',否则\d会被当成转义字符报错或失效。
三、贪婪与非贪婪
3.1 什么是贪婪
默认情况下,* + {m,n} 会尽可能多地匹配,这叫"贪婪模式"。
import re
# 想匹配 <div>...</div>,贪婪会一直匹配到最后一个 >
s = '<div>hello</div><div>world</div>'
print(re.findall(r'<div>.*</div>', s))
# ['<div>hello</div><div>world</div>'] ← 匹配了整个!
3.2 非贪婪(懒惰)
在量词后加 ? 变成非贪婪,尽可能少地匹配:
import re
s = '<div>hello</div><div>world</div>'
print(re.findall(r'<div>.*?</div>', s))
# ['<div>hello</div>', '<div>world</div>'] ← 正确匹配每个 div
运行结果对比:
贪婪:['<div>hello</div><div>world</div>']
非贪婪:['<div>hello</div>', '<div>world</div>']
| 模式 | 写法 | 行为 |
|---|---|---|
| 贪婪 | * + ? {m,n} |
尽可能多匹配 |
| 非贪婪 | *? +? ?? {m,n}? |
尽可能少匹配 |
经验:提取 HTML/XML 标签内容时几乎都要用非贪婪
.*?。
四、re 模块常用方法
Python 内置 re 模块,下面逐个讲解核心方法。
4.1 re.match(从头匹配)
re.match(pattern, string) 只从字符串开头 匹配,匹配成功返回 Match 对象,否则返回 None。
import re
m = re.match(r'(\d+)-(\w+)', '100-python')
if m:
print(m.group()) # 100-python(整体)
print(m.group(1)) # 100(第1组)
print(m.group(2)) # python(第2组)
print(m.span()) # (0, 10)(匹配起止位置)
print(re.match(r'\d+', 'abc123')) # None(开头不是数字)
运行结果:
100-python
100
python
(0, 10)
None
4.2 re.search(搜索任意位置)
re.search 在整个字符串里找第一个匹配,不限开头。
import re
m = re.search(r'\d+', 'abc123def456')
print(m.group()) # 123(找到第一个数字串)
运行结果:
123
matchvssearch:match必须从头开始;search可以从任意位置。
4.3 re.findall(找所有)
返回所有匹配的字符串列表(有分组时返回分组元组列表)。
import re
print(re.findall(r'\d+', 'a1b22c333')) # ['1', '22', '333']
print(re.findall(r'(\w+)@(\w+)', 'a@b cc@dd')) # [('a', 'b'), ('cc', 'dd')]
运行结果:
['1', '22', '333']
[('a', 'b'), ('cc', 'dd')]
4.4 re.finditer
返回一个迭代器,每个元素是 Match 对象,能拿到位置信息,比 findall 更强大。
import re
for m in re.finditer(r'\d+', 'a1b22c333'):
print(m.group(), m.span())
运行结果:
1 (1, 2)
22 (3, 5)
333 (6, 9)
4.5 re.sub(替换)
re.sub(pattern, repl, string, count=0) 把匹配的部分替换成 repl。
import re
# 把日期 2023/01/01 改成 2023-01-01
s = '日期:2023/01/01 和 2023/12/31'
print(re.sub(r'/', '-', s))
# 日期:2023-01-01 和 2023-12-31
# 用分组反向引用重排日期:YYYY/MM/DD -> DD-MM-YYYY
print(re.sub(r'(\d{4})/(\d{2})/(\d{2})', r'\3-\2-\1', s))
# 日期:01-01-2023 和 31-12-2023
运行结果:
日期:2023-01-01 和 2023-12-31
日期:01-01-2023 和 31-12-2023
回调函数替换 :repl 可以是函数,接收 Match 对象,返回替换字符串。
import re
# 把每个数字乘以 2
s = 'a1b22c333'
result = re.sub(r'\d+', lambda m: str(int(m.group()) * 2), s)
print(result) # a2b44c666
运行结果:
a2b44c666
4.6 re.split(分割)
比 str.split 更强大,可以按正则模式分割。
import re
# 按任意空白或逗号分割
print(re.split(r'[,\s]+', 'a,b c,,d e'))
# ['a', 'b', 'c', 'd', 'e']
# 保留分隔符(加分组)
print(re.split(r'(\s+)', 'hello world foo'))
# ['hello', ' ', 'world', ' ', 'foo']
运行结果:
['a', 'b', 'c', 'd', 'e']
['hello', ' ', 'world', ' ', 'foo']
4.7 re.fullmatch
要求整个字符串完全匹配,常用于格式校验。
import re
print(bool(re.fullmatch(r'\d{11}', '13800138000'))) # True(11位手机号)
print(bool(re.fullmatch(r'\d{11}', '1380013800')))) # False(10位)
运行结果:
True
False
五、分组与命名分组
5.1 普通分组
import re
m = re.match(r'(\d{4})-(\d{2})-(\d{2})', '2023-08-15')
print(m.groups()) # ('2023', '08', '15')
print(m.group(1), m.group(2), m.group(3))
5.2 命名分组 (?P<name>...)
import re
m = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2023-08-15')
print(m.group('year')) # 2023
print(m.group('month')) # 08
print(m.groupdict()) # {'year': '2023', 'month': '08', 'day': '15'}
运行结果:
('2023', '08', '15')
2023 08 15
2023
08
{'year': '2023', 'month': '08', 'day': '15'}
命名分组可读性远高于数字编号,复杂正则强烈推荐。
六、编译与标志位
6.1 re.compile
同一条正则要多次使用时,先用 re.compile 编译成 Pattern 对象,效率更高。
import re
pattern = re.compile(r'\d+')
print(pattern.findall('a1b22')) # ['1', '22']
print(pattern.search('abc333')) # <Match object>
6.2 常用标志位
| 标志 | 含义 |
|---|---|
re.IGNORECASE / re.I |
忽略大小写 |
re.MULTILINE / re.M |
^ $ 匹配每行开头结尾 |
re.DOTALL / re.S |
. 也匹配换行符 |
re.VERBOSE / re.X |
允许正则里写注释和换行,提高可读性 |
import re
# 忽略大小写
print(re.findall(r'python', 'Python PYTHON python', re.I))
# ['Python', 'PYTHON', 'python']
# MULTILINE:每行开头
text = "line1\nline2\nline3"
print(re.findall(r'^line\d', text, re.M))
# ['line1', 'line2', 'line3']
# DOTALL:. 匹配换行
print(re.findall(r'<div>.*?</div>', '<div>a\nb</div>', re.S))
# ['<div>a\nb</div>']
运行结果:
['Python', 'PYTHON', 'python']
['line1', 'line2', 'line3']
['<div>a\nb</div>']
VERBOSE 示例(可读性极佳):
import re
phone_re = re.compile(r"""
^1 # 手机号以1开头
[3-9] # 第二位3-9
\d{9} # 后面9位数字
$
""", re.VERBOSE)
print(bool(phone_re.fullmatch('13800138000'))) # True
print(bool(phone_re.fullmatch('12800138000'))) # False(第二位不能是2)
运行结果:
True
False
七、常用实战模式
7.1 邮箱
import re
email_re = re.compile(r'^[\w.+-]+@[\w-]+\.[\w.]+$')
tests = ['user@example.com', 'a.b+c@d.cn', 'bad@', '@x.com', 'ok@sub.domain.org']
for t in tests:
print(t, bool(email_re.match(t)))
运行结果:
user@example.com True
a.b+c@d.cn True
bad@ False
@x.com False
ok@sub.domain.org True
7.2 手机号
import re
mobile_re = re.compile(r'^1[3-9]\d{9}$')
print(bool(mobile_re.match('13800138000'))) # True
print(bool(mobile_re.match('12345678901'))) # False
7.3 身份证(18位)
import re
# 18位:6位地区码 + 8位生日 + 3位顺序 + 1位校验(X或数字)
id_re = re.compile(r'^\d{6}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$')
print(bool(id_re.match('11010119900307891X'))) # True
print(bool(id_re.match('123456789012345678'))) # False
运行结果:
True
False
7.4 URL
import re
url_re = re.compile(r'^https?://[\w.-]+(:\d+)?(/[\w./-]*)?$')
print(bool(url_re.match('https://www.example.com:8080/path/to/page')))
print(bool(url_re.match('ftp://x.com')))
7.5 IP 地址
import re
ip_re = re.compile(r'^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$')
print(bool(ip_re.match('192.168.1.1'))) # True
print(bool(ip_re.match('256.1.1.1'))) # False
7.6 日期
import re
date_re = re.compile(r'\d{4}[-/]\d{2}[-/]\d{2}')
print(date_re.findall('日志 2023-08-15 错误,2023/08/16 修复'))
# ['2023-08-15', '2023/08/16']
7.7 中文字符
import re
# 中文 Unicode 范围 \u4e00-\u9fa5
print(re.findall(r'[\u4e00-\u9fa5]+', 'Hello 世界,你好 world!'))
# ['世界', '你好']
运行结果:
['2023-08-15', '2023/08/16']
['世界', '你好']
八、实战案例
下面通过几个真实开发中高频出现的场景,把前面学的语法和方法综合运用起来。每个案例都给出完整可运行代码和运行结果,建议复制到本地跑一遍,加深理解。正则的熟练度很大程度上来自实战积累,光看不练很容易忘;遇到需求先自己试着写,再对照本文写法,进步最快。
8.1 从 HTML 中提取所有链接
import re
html = '''
<html>
<body>
<a href="https://www.python.org">Python</a>
<a href='https://github.com'>GitHub</a>
<a href="/local/path">本地</a>
<img src="logo.png">
</body>
</html>
'''
# 提取所有 href 的值(兼容单引号双引号)
links = re.findall(r'href=["\']([^"\']+)["\']', html)
print("所有链接:", links)
# 只提取 http/https 开头的外链
outer = re.findall(r'href=["\'](https?://[^"\']+)["\']', html)
print("外链:", outer)
运行结果:
所有链接: ['https://www.python.org', 'https://github.com', '/local/path']
外链: ['https://www.python.org', 'https://github.com']
8.2 日志分析
模拟一段 Nginx 访问日志,提取 IP、时间、请求方法、路径、状态码。
import re
logs = """
192.168.1.10 - - [15/Aug/2023:10:00:01 +0800] "GET /index.html HTTP/1.1" 200 1234
10.0.0.5 - - [15/Aug/2023:10:00:02 +0800] "POST /api/login HTTP/1.1" 401 89
192.168.1.10 - - [15/Aug/2023:10:00:03 +0800] "GET /about HTTP/1.1" 404 64
"""
pattern = re.compile(r'''
(?P<ip>\d+\.\d+\.\d+\.\d+) # IP
\s.*?\[ # 中间内容到 [
(?P<time>[^\]]+) # 时间
\]\s" # ] "
(?P<method>\w+)\s # 请求方法
(?P<path>\S+)\s # 路径
HTTP/[\d.]+"\s # HTTP 版本
(?P<status>\d+) # 状态码
''', re.VERBOSE)
for line in logs.strip().splitlines():
m = pattern.search(line)
if m:
print(f"IP={m.group('ip')} 时间={m.group('time')} {m.group('method')} {m.group('path')} -> {m.group('status')}")
运行结果:
IP=192.168.1.10 时间=15/Aug/2023:10:00:01 +0800 GET /index.html -> 200
IP=10.0.0.5 时间=15/Aug/2023:10:00:02 +0800 POST /api/login -> 401
IP=192.168.1.10 时间=15/Aug/2023:10:00:03 +0800 GET /about -> 404
8.3 密码强度校验
规则:至少 8 位,含大写、小写、数字、特殊字符。
import re
def check_password(pwd):
if len(pwd) < 8:
return False, "长度至少8位"
if not re.search(r'[A-Z]', pwd):
return False, "需包含大写字母"
if not re.search(r'[a-z]', pwd):
return False, "需包含小写字母"
if not re.search(r'\d', pwd):
return False, "需包含数字"
if not re.search(r'[!@#$%^&*]', pwd):
return False, "需包含特殊字符"
return True, "强度合格"
for p in ['123', 'abcdefgh', 'Abcdefg1', 'Abcdefg1!']:
print(p, '->', check_password(p))
运行结果:
123 -> (False, '长度至少8位')
abcdefgh -> (False, '需包含大写字母')
Abcdefg1 -> (False, '需包含特殊字符')
Abcdefg1! -> (True, '强度合格')
8.4 从配置文件中提取键值对
实际开发中常遇到 .env、properties 这类配置文件,用正则批量提取键值对非常方便。
import re
config = """
# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASS=secret123
# 应用配置
DEBUG=true
LOG_LEVEL=info
"""
# 匹配 KEY=VALUE(跳过注释行和空行)
items = re.findall(r'^([A-Z_]+)=(.+)$', config, re.MULTILINE)
for k, v in items:
print(f"{k} => {v}")
运行结果:
DB_HOST => 127.0.0.1
DB_PORT => 3306
DB_USER => root
DB_PASS => secret123
DEBUG => true
LOG_LEVEL => info
8.5 敏感信息脱敏
日志里不小心打印了手机号、身份证,上线前要脱敏处理,正则一行搞定:
import re
text = "用户13800138000下单,身份证11010119900307891X,邮箱a@b.com"
# 手机号中间4位打星
text = re.sub(r'(1[3-9]\d)\d{4}(\d{4})', r'\1****\2', text)
# 身份证出生年月日打星
text = re.sub(r'(\d{6})\d{8}(\d{3}[\dX])', r'\1********\2', text)
# 邮箱用户名只留首字符
text = re.sub(r'(\w)\w+@', r'\1***@', text)
print(text)
运行结果:
用户138****8000下单,身份证110101********891X,邮箱a***@b.com
脱敏是数据安全基本功,正则是实现脱敏最简洁的工具。
8.6 批量替换文本中的日期格式
文档迁移时,常需要把各种日期格式统一成标准格式,正则配合回调函数非常灵活:
import re
doc = """
发布日期:2023年08月15日
更新:2023.08.20
历史:2023/08/01 ~ 2023/08/10
"""
# 把 中文日期、点分日期、斜杠日期 统一成 YYYY-MM-DD
def normalize(m):
y, mo, d = m.group('y'), m.group('m'), m.group('d')
return f"{y}-{mo}-{d}"
pattern = re.compile(r'(?P<y>\d{4})[年./-](?P<m>\d{2})[月./-](?P<d>\d{2})日?')
result = pattern.sub(normalize, doc)
print(result)
运行结果:
发布日期:2023-08-15
更新:2023-08-20
历史:2023-08-01 ~ 2023-08-10
一个正则同时兼容三种分隔符,配合命名分组提取年月日,再统一格式------这就是正则的威力。
九、正则性能优化
正则写不好会引发灾难性回溯,导致匹配极慢甚至卡死。优化建议:
-
尽量精确,少用
.*:能用[^<>]+就别用.*,减少回溯。 -
能用非贪婪就非贪婪 :
.*?比.*安全。 -
预编译 :多次使用的正则用
re.compile。 -
锚定边界 :加
^$或\b提前失败,减少无效尝试。 -
避免嵌套量词 :如
(a+)+这类写法是回溯灾难的元凶。 -
复杂校验拆成多步:与其写一个超长正则,不如分几个简单正则依次判断(参考密码校验)。
import re, time
反面教材:嵌套量词,匹配超长字符串会卡
bad = re.compile(r'(a+)+b')
s = 'a' * 30
t0 = time.time()
bad.search(s + 'b')
print(f"耗时 {time.time()-t0:.4f}s") # 可能明显变慢
十、正则的局限性与替代方案
正则虽强大,但并非万能。理解它的边界,才能在合适场景用合适工具,避免把正则用在不该用的地方,写出难以维护的"天书"代码。
10.1 正则无法处理嵌套结构
正则本质是正则文法 ,无法匹配任意深度的嵌套(如括号配对、HTML 标签嵌套)。例如想匹配所有 <div>...</div> 且 div 内部还能嵌套 div,正则无能为力------这属于上下文无关文法,需要栈结构来配对。
import re
# 嵌套 div,正则无法正确配对
html = '<div>外层<div>内层</div>外层</div>'
print(re.findall(r'<div>.*?</div>', html, re.S))
# ['<div>外层<div>内层</div>'] ← 错误,多了一个未闭合的 <div>
运行结果:
['<div>外层<div>内层</div>']
10.2 何时该用解析器
| 场景 | 推荐工具 |
|---|---|
| HTML 解析 | BeautifulSoup、lxml |
| XML 解析 | xml.etree.ElementTree |
| JSON 解析 | json 模块 |
| 配置文件 | configparser、toml |
| 复杂语法 | pyparsing、ANTLR |
原则:结构化数据用专用解析器,扁平文本提取用正则。
10.3 正则与字符串方法的选择
简单场景用字符串方法更清晰、更快,不必动用正则:
# 判断是否以某串开头
'hello'.startswith('he') # 比 re.match(r'he', 'hello') 好
# 简单替换
'abc'.replace('b', 'B') # 比 re.sub 好
# 简单分割
'a,b,c'.split(',') # 比 re.split 好
心法:能用字符串方法解决就别上正则;正则搞不定的嵌套结构果断换解析器。
10.4 正则调试技巧
写复杂正则时,推荐用以下方法调试,避免盲改:
-
在线工具 regex101.com:可视化每一步匹配,支持 Python 风格,还能生成解释。
-
分步构建:先写最核心的部分测试通过,再逐步加量词、分组、边界。
-
用 re.VERBOSE 加注释:把正则拆成多行写,每行注释说明意图。
-
单元测试:把正则封装成函数,用 pytest 覆盖各种边界用例。
import re
def extract_price(text):
"""从文本提取价格(元)"""
pattern = re.compile(r'''
(?:价格|价|¥|¥|$) # 前缀:价格/价/货币符号
\s* # 可选空白
(\d+(?:.\d+)?) # 金额(整数或小数)
\s* # 可选空白
(?:元|CNY|RMB)? # 可选单位
''', re.VERBOSE)
m = pattern.search(text)
return float(m.group(1)) if m else Noneprint(extract_price('商品价格 99.9 元')) # 99.9
print(extract_price('¥128')) # 128.0
print(extract_price('无价格信息')) # None
运行结果:
99.9
128.0
None
十一、最佳实践
- 始终用原始字符串
r'...',避免\被Python字符串转义吃掉。 - 格式校验用
fullmatch,提取用findall/finditer。 - 复杂正则用
re.VERBOSE加注释,三个月后自己还能看懂。 - 命名分组
(?P<name>...)比数字编号可读性高。 - 预编译
re.compile提升循环里反复匹配的性能。 - HTML/XML 解析别全靠正则 ,复杂结构用
BeautifulSoup、lxml更稳。 - 正则不是万能:涉及嵌套结构(括号配对、HTML 标签嵌套)正则无能为力,需用解析器。
- 测试正则 :推荐用 regex101.com 在线调试,可视化每一步匹配。
十二、小结
到这里,正则表达式从基础语法到实战应用的内容就全部讲完了。本文系统讲解了正则表达式从语法到实战的完整链路:
- 基础元字符 :
\d \w \s . \b、量词* + ? {m,n}、锚点^ $、字符集[]、分组()、或|。 - 贪婪与非贪婪 :加
?切换,提取标签内容必用非贪婪。 - re 模块七大方法 :
match/search/findall/finditer/sub/split/fullmatch。 - 分组与命名分组 :
groups()/groupdict()。 - 编译与标志位 :
re.I/re.M/re.S/re.X。 - 常用模式:邮箱、手机号、身份证、URL、IP、日期、中文。
- 三大实战:HTML 链接提取、日志分析、密码强度校验。
核心心法:先想清楚要匹配什么,再写正则;写完用 re.VERBOSE 加注释;复杂需求拆成多步,别追求一个正则通吃。
下一篇我们将进入 Web 开发,用 Flask 搭建你的第一个网站,敬请期待!
作者:全栈陈序员 系列:Python 基础语法入门 专栏:Python 从零到一 如果对你有帮助,欢迎点赞、收藏、关注 ❤️