正则表达式完全指南:从入门到精通
一、什么是正则表达式?
正则表达式(Regular Expression,简称 Regex)是一种用于匹配字符串中字符组合的模式。它由普通字符和特殊字符(元字符)组成,可以用来验证、搜索、替换文本。
🎯 核心价值:用一行代码完成复杂的字符串处理任务
二、基础语法
2.1 普通字符
| 字符 |
说明 |
示例 |
abc |
匹配字符串 "abc" |
hello 匹配 hello |
123 |
匹配数字 |
123abc 匹配 123 |
\ |
转义字符 |
\. 匹配实际的点号 |
| 元字符 |
说明 |
示例 |
匹配结果 |
. |
匹配任意单个字符(除换行符) |
h.t |
hat, hot, h t |
^ |
匹配字符串开头 |
^Hello |
以 Hello 开头的字符串 |
$ |
匹配字符串结尾 |
world$ |
以 world 结尾的字符串 |
| ` |
` |
或运算符 |
`cat |
() |
分组捕获 |
(ab)+ |
匹配一个或多个 ab |
[] |
字符集 |
[abc] |
匹配 a、b 或 c |
2.3 量词(Quantifiers)
| 量词 |
说明 |
示例 |
匹配次数 |
* |
零次或多次 |
ab*c |
ac, abc, abbc... |
+ |
一次或多次 |
ab+c |
abc, abbc...(不匹配 ac) |
? |
零次或一次 |
colou?r |
color, colour |
{n} |
恰好 n 次 |
a{3} |
aaa |
{n,} |
至少 n 次 |
a{2,} |
aa, aaa, aaaa... |
{n,m} |
n 到 m 次 |
a{2,4} |
aa, aaa, aaaa |
2.4 字符类(Character Classes)
| 类 |
说明 |
示例 |
\d |
数字 0-9 |
\d+ 匹配 123 |
\D |
非数字 |
\D+ 匹配 abc |
\w |
单词字符 a-zA-Z0-9_ |
\w+ 匹配 hello_123 |
\W |
非单词字符 |
\W+ 匹配 @#$ |
\s |
空白字符(空格、制表符、换行符) |
\s+ 匹配多个空格 |
\S |
非空白字符 |
\S+ 匹配 hello |
\b |
单词边界 |
\bword\b 精确匹配 word |
\B |
非单词边界 |
\Bword\B 匹配 swordfish 中的 word |
2.5 反义(Negations)
| 类 |
说明 |
示例 |
[^abc] |
不匹配 a、b、c |
[^0-9] 匹配非数字 |
[a-z] |
匹配小写字母 |
[a-z]+ 匹配 hello |
[A-Z] |
匹配大写字母 |
[A-Z]+ 匹配 HELLO |
[0-9] |
匹配数字 |
[0-9]+ 匹配 12345 |
三、进阶语法
3.1 贪婪与非贪婪
| 模式 |
说明 |
示例 |
.* |
贪婪匹配(尽可能多) |
.* in aabbcc 匹配 aabbcc |
.*? |
非贪婪匹配(尽可能少) |
.*? in aabbcc 匹配空字符串 |
.+? |
非贪婪匹配 |
.+? in aabbcc 匹配 a |
{n,m}? |
非贪婪量词 |
a{2,4}? 匹配 aa |
示例:
import re
html = '<p>Hello</p><p>World</p>'
# 贪婪匹配(错误)
re.findall(r'<p>.*</p>', html) # ['<p>Hello</p><p>World</p>']
# 非贪婪匹配(正确)
re.findall(r'<p>.*?</p>', html) # ['<p>Hello</p>', '<p>World</p>']
3.2 前瞻与后顾(Lookaround)
| 类型 |
语法 |
说明 |
| 正向前瞻 |
(?=pattern) |
匹配后面跟着 pattern 的位置 |
| 负向前瞻 |
(?!pattern) |
匹配后面不跟着 pattern 的位置 |
| 正向后顾 |
(?<=pattern) |
匹配前面是 pattern 的位置 |
| 负向后顾 |
(?<!pattern) |
匹配前面不是 pattern 的位置 |
示例:
import re
# 提取价格(不包含货币符号)
text = '价格:$100 和 ¥200'
# 正向前瞻
re.findall(r'(?<=\$)\d+', text) # ['100']
re.findall(r'(?<=¥)\d+', text) # ['200']
# 负向前瞻:匹配后面不跟数字的单词
re.findall(r'\b\w+(?!\d)', 'abc123 def456') # ['abc', 'def']
3.3 分组与捕获
import re
# 基本分组
pattern = r'(\d{3})-(\d{4})'
text = '电话:123-4567'
match = re.search(pattern, text)
if match:
print(match.group(0)) # 123-4567(完整匹配)
print(match.group(1)) # 123(第一组)
print(match.group(2)) # 4567(第二组)
print(match.groups()) # ('123', '4567')
3.4 命名分组
import re
# 命名分组 (?P<name>pattern)
pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
text = '日期:2024-01-15'
match = re.search(pattern, text)
if match:
print(match.group('year')) # 2024
print(match.group('month')) # 01
print(match.group('day')) # 15
3.5 非捕获分组
import re
# 非捕获分组 (?:pattern) - 不保存匹配结果
pattern = r'(?:http|https)://(\S+)'
text = '访问 https://example.com'
match = re.search(pattern, text)
if match:
print(match.group(0)) # https://example.com
print(match.group(1)) # example.com(只有捕获组)
四、Python re 模块详解
4.1 常用函数
| 函数 |
说明 |
返回值 |
re.match() |
从字符串开头匹配 |
Match 对象或 None |
re.search() |
搜索整个字符串 |
第一个匹配的 Match 对象 |
re.findall() |
查找所有匹配 |
列表 |
re.finditer() |
查找所有匹配 |
迭代器 |
re.sub() |
替换匹配的字符串 |
新字符串 |
re.split() |
按模式分割字符串 |
列表 |
re.compile() |
编译正则表达式 |
Pattern 对象 |
4.2 使用示例
import re
# 1. match - 从开头匹配
re.match(r'\d+', '123abc') # Match: '123'
re.match(r'\d+', 'abc123') # None(不是开头)
# 2. search - 搜索第一个匹配
re.search(r'\d+', 'abc123def') # Match: '123'
# 3. findall - 查找所有匹配
re.findall(r'\d+', 'a1b2c3') # ['1', '2', '3']
# 4. sub - 替换
re.sub(r'\d+', '*', 'a1b2c3') # 'a*b*c*'
re.sub(r'(\w+)', r'\1\1', 'ab') # 'aabb'(重复单词)
# 5. split - 分割
re.split(r'\s+', 'hello world foo') # ['hello', 'world', 'foo']
re.split(r'[;|,]', 'a;b,c|d') # ['a', 'b', 'c', 'd']
# 6. compile - 编译复用
email_pattern = re.compile(r'[\w.]+@[\w.]+\.\w+')
email_pattern.findall('test@example.com and info@test.org')
# ['test@example.com', 'info@test.org']
4.3 编译标志(Flags)
| 标志 |
说明 |
使用方式 |
re.IGNORECASE |
忽略大小写 |
re.IGNORECASE 或 re.I |
re.MULTILINE |
多行模式(^ $ 匹配每行) |
re.MULTILINE 或 re.M |
re.DOTALL |
点号匹配换行符 |
re.DOTALL 或 re.S |
re.VERBOSE |
允许注释和空格 |
re.VERBOSE 或 re.X |
re.UNICODE |
Unicode 模式 |
re.UNICODE 或 re.U |
import re
# 忽略大小写
re.findall(r'hello', 'Hello HELLO hello', re.I) # ['Hello', 'HELLO', 'hello']
# 多行模式
text = 'line1\nline2\nline3'
re.findall(r'^\w+', text, re.M) # ['line1', 'line2', 'line3']
# 点号匹配换行符
re.findall(r'.+', 'line1\nline2', re.S) # ['line1\nline2']
# 详细的正则(忽略空格和注释)
phone = re.compile(r'''
(\d{3,4}) # 区号
[-\s]? # 可选分隔符
(\d{7,8}) # 电话号码
''', re.VERBOSE)
五、爬虫实战常用正则
5.1 URL 提取
import re
# 匹配 HTTP/HTTPS URL
url_pattern = r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[^\s]*'
text = '请访问 https://example.com/path?query=1 或 http://test.org'
re.findall(url_pattern, text)
# ['https://example.com/path?query=1', 'http://test.org']
# 匹配相对 URL
relative_pattern = r'href=["\']([^"\']+)["\']'
html = '<a href="/page1">Link</a>'
re.findall(relative_pattern, html) # ['/page1']
5.2 邮箱提取
import re
# 基本邮箱验证
email_pattern = r'[\w.+-]+@[\w-]+\.[\w.-]+'
# 更严格的邮箱验证
strict_email = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
emails = """
联系方式:
- 简单邮箱:test@example.com
- 公司邮箱:user.name@company.co.uk
- 个人邮箱:user+tag@gmail.com
"""
re.findall(email_pattern, emails)
# ['test@example.com', 'user.name@company.co.uk', 'user+tag@gmail.com']
5.3 手机号提取
import re
# 中国大陆手机号
phone_pattern = r'1[3-9]\d{9}'
# 带格式的手机号
formatted_phone = r'1[3-9]\d{1}[-\s]?\d{4}[-\s]?\d{4}'
text = '联系我:13812345678 或 139-1234-5678'
re.findall(phone_pattern, text) # ['13812345678', '13912345678']
5.4 IP 地址提取
import re
# 基本 IPv4
ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
# 严格的 IPv4 验证
strict_ip = r'^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$'
log = '访问来自 192.168.1.100 和 10.0.0.1'
re.findall(ip_pattern, log) # ['192.168.1.100', '10.0.0.1']
5.5 HTML 内容提取
import re
# 提取标题
title_pattern = r'<title>(.*?)</title>'
# 提取 meta 描述
meta_pattern = r'<meta\s+name=["\']description["\']\s+content=["\'](.*?)["\']'
# 提取所有文本(去标签)
text_pattern = r'<[^>]+>'
html = '''
<html>
<head><title>网页标题</title></head>
<body>
<h1>主标题</h1>
<p>段落内容</p>
</body>
</html>
'''
re.findall(title_pattern, html) # ['网页标题']
re.sub(text_pattern, '', html) # '网页标题主标题段落内容'
5.6 数据清洗
import re
# 去除多余空白
clean = r'\s+'
text = ' hello world \n foo '
re.sub(clean, ' ', text.strip()) # 'hello world foo'
# 去除 HTML 实体
entity_pattern = r'&[a-zA-Z]+;|&#\d+;'
text = '价格:100<200 & 300'
re.sub(entity_pattern, lambda m: {
'<': '<', '>': '>', '&': '&'
}.get(m.group(), ''), text)
# 中文提取
chinese_pattern = r'[\u4e00-\u9fa5]+'
text = 'Hello 你好 World 世界'
re.findall(chinese_pattern, text) # ['你好', '世界']
六、常用正则表达式速查表
6.1 验证类
# 手机号验证
r'^1[3-9]\d{9}$'
# 邮箱验证
r'^[\w.+-]+@[\w-]+\.[\w.-]+$'
# 身份证号验证(18位)
r'^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$'
# URL 验证
r'^https?://[-\w.]+(?:/[-\w./]*)?$'
# IP 地址验证
r'^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$'
6.2 提取类
# 中文字符
r'[\u4e00-\u9fa5]+'
# 英文单词
r'\b[a-zA-Z]+\b'
# 数字(整数和小数)
r'-?\d+\.?\d*'
# 日期格式 YYYY-MM-DD
r'\d{4}[-/]\d{1,2}[-/]\d{1,2}'
# 时间格式 HH:MM:SS
r'\d{1,2}:\d{2}:\d{2}'
# Hex 颜色值
r'#(?:[0-9a-fA-F]{3}){1,2}\b'
6.3 替换类
# 压缩多个空格
r'\s+'
# HTML 标签
r'<[^>]+>'
# 中文标点转英文标点
r'[,。;:!?]'
# 全角转半角
r'[\uff01-\uff5e]'
七、性能优化技巧
7.1 编译正则
import re
# ❌ 不推荐:每次调用都重新编译
def find_emails(text):
return re.findall(r'[\w.+-]+@[\w-]+\.[\w.-]+', text)
# ✅ 推荐:编译一次复用
EMAIL_PATTERN = re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+')
def find_emails(text):
return EMAIL_PATTERN.findall(text)
7.2 使用原始字符串
# ❌ 不推荐:需要双重转义
pattern = "\\d+\\.\\d+"
# ✅ 推荐:原始字符串
pattern = r'\d+\.\d+'
7.3 避免回溯
import re
# ❌ 容易产生回溯(灾难性回溯)
bad_pattern = r'(a+)+b'
# ✅ 更高效的写法
good_pattern = r'a+b'
7.4 使用非贪婪匹配
import re
html = '<div>content</div><div>more</div>'
# ❌ 贪婪匹配
re.findall(r'<div>.*</div>', html) # ['<div>content</div><div>more</div>']
# ✅ 非贪婪匹配
re.findall(r'<div>.*?</div>', html) # ['<div>content</div>', '<div>more</div>']
八、常见错误与调试
8.1 常见错误
| 错误 |
原因 |
解决方案 |
re.error: nothing to repeat |
量词前没有字符 |
检查正则表达式 |
re.error: bad escape |
无效的转义序列 |
使用原始字符串 |
| 匹配结果为空 |
正则写错或模式不匹配 |
逐步调试正则 |
8.2 调试技巧
import re
# 使用 re.VERBOSE 添加注释
pattern = re.compile(r'''
^ # 字符串开头
(?P<year>\d{4}) # 年
- # 分隔符
(?P<month>\d{2}) # 月
- # 分隔符
(?P<day>\d{2}) # 日
$ # 字符串结尾
''', re.VERBOSE)
# 在线调试工具
# https://regex101.com/
# https://debuggex.com/
九、总结
📋 正则表达式核心要点
- 基础元字符 :
., ^, $, *, +, ?, [], (), \|
- 字符类 :
\d, \w, \s 及其大写反义
- 量词 :
*, +, ?, {n}, {n,}, {n,m}
- 贪婪与非贪婪 :
.* vs .*?
- 分组捕获 :
() 和命名分组 (?P<name>)
- 前瞻后顾 :
(?=), (?!), (?<=), (?<!)
🎯 学习建议
- 从简单模式开始,逐步复杂化
- 使用在线工具(regex101.com)测试
- 先理解需求,再编写正则
- 避免过度使用,可读性很重要
- 注意性能,避免灾难性回溯