Python 正则表达式
目录
学习目标
- 掌握正则表达式的基本语法
- 学会使用
re模块进行匹配、搜索和替换 - 理解贪婪与非贪婪匹配
- 掌握常用正则模式和实际应用
1. re 模块基础
1.1 匹配与搜索
import re
# re.match - 从字符串开头匹配
result = re.match(r'hello', 'hello world')
print(result) # <re.Match object; span=(0, 5), match='hello'>
print(result.group()) # hello
# re.search - 搜索整个字符串
result = re.search(r'world', 'hello world')
print(result.group()) # world
# re.findall - 查找所有匹配
result = re.findall(r'\d+', 'abc 123 def 456')
print(result) # ['123', '456']
# re.finditer - 返回迭代器
for match in re.finditer(r'\d+', 'abc 123 def 456'):
print(f"找到: {match.group()} 在位置 {match.span()}")1.2 替换与分割
import re
# re.sub - 替换
result = re.sub(r'\d+', '#', 'abc 123 def 456')
print(result) # abc # def #
# 替换并限制次数
result = re.sub(r'\d+', '#', '1 2 3 4', count=2)
print(result) # # # 3 4
# re.subn - 替换并返回次数
result = re.subn(r'\d+', '#', 'abc 123 def 456')
print(result) # ('abc # def #', 2)
# re.split - 分割
result = re.split(r'\s+', 'hello world python')
print(result) # ['hello', 'world', 'python']2. 正则表达式语法
2.1 元字符
import re
# . 匹配任意字符(除换行)
print(re.findall(r'a.c', 'abc aac acc axc')) # ['abc', 'aac', 'acc', 'axc']
# ^ 匹配开头 $ 匹配结尾
print(bool(re.match(r'^hello', 'hello world'))) # True
print(bool(re.search(r'world$', 'hello world'))) # True
# * 零次或多次 + 一次或多次 ? 零次或一次
print(re.findall(r'ab*c', 'ac abc abbc abbbc')) # ['ac', 'abc', 'abbc', 'abbbc']
print(re.findall(r'ab+c', 'ac abc abbc')) # ['abc', 'abbc']
print(re.findall(r'ab?c', 'ac abc abbc')) # ['ac', 'abc']
# {n} 恰好n次 {n,} 至少n次 {n,m} n到m次
print(re.findall(r'a{3}', 'aa aaa aaaa')) # ['aaa', 'aaa']
print(re.findall(r'a{2,}', 'a aa aaa')) # ['aa', 'aaa']
print(re.findall(r'a{2,3}', 'a aa aaa aaaa')) # ['aa', 'aaa', 'aaa']2.2 字符类
import re
# [...] 字符集
print(re.findall(r'[aeiou]', 'hello world')) # ['e', 'o', 'o']
# [^...] 否定字符集
print(re.findall(r'[^aeiou]', 'hello')) # ['h', 'l', 'l']
# \d 数字 \D 非数字
print(re.findall(r'\d+', 'age: 25, score: 90')) # ['25', '90']
# \w 单词字符 [a-zA-Z0-9_] \W 非单词字符
print(re.findall(r'\w+', 'hello world!')) # ['hello', 'world']
# \s 空白字符 \S 非空白字符
print(re.findall(r'\S+', 'hello world')) # ['hello', 'world']
# 预定义字符集
# \d = [0-9]
# \w = [a-zA-Z0-9_]
# \s = [ \t\n\r\f\v]2.3 分组与引用
import re
# (...) 捕获分组
result = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2024-01-15')
print(result.group(0)) # 2024-01-15(完整匹配)
print(result.group(1)) # 2024(第一组)
print(result.group(2)) # 01(第二组)
print(result.group(3)) # 15(第三组)
print(result.groups()) # ('2024', '01', '15')
# 命名分组 (?P<name>...)
result = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})', '2024-01')
print(result.group('year')) # 2024
print(result.group('month')) # 01
# (?:...) 非捕获分组
result = re.findall(r'(?:https?://)(\w+\.\w+)', 'https://google.com http://test.org')
print(result) # ['google.com', 'test.org']
# \1 \2 反向引用
print(re.findall(r'(\w+) \1', 'hello hello world world')) # ['hello', 'world']3. 贪婪与非贪婪
import re
text = '<div>content1</div><div>content2</div>'
# 贪婪匹配(默认)- 匹配尽可能长的字符串
print(re.findall(r'<div>.*</div>', text))
# ['<div>content1</div><div>content2</div>']
# 非贪婪匹配 - 匹配尽可能短的字符串
print(re.findall(r'<div>.*?</div>', text))
# ['<div>content1</div>', '<div>content2</div>']
# 其他非贪婪量词
# *? +? ?? {n,m}?
text = '"hello" "world"'
print(re.findall(r'".*?"', text)) # ['"hello"', '"world"']4. 编译与标志
4.1 编译正则
import re
# 编译正则(提高效率,适合重复使用)
pattern = re.compile(r'\d{4}-\d{2}-\d{2}')
# 使用编译后的对象
print(pattern.match('2024-01-15'))
print(pattern.search('date: 2024-01-15'))
print(pattern.findall('2024-01-15 2024-02-20'))4.2 标志位
import re
# re.IGNORECASE / re.I - 忽略大小写
print(re.findall(r'hello', 'HELLO hello Hello', re.I))
# ['HELLO', 'hello', 'Hello']
# re.DOTALL / re.S - . 匹配换行
print(re.findall(r'a.b', 'a\nb', re.S)) # ['a\nb']
# re.MULTILINE / re.M - ^$ 匹配每行开头结尾
text = 'line1\nline2\nline3'
print(re.findall(r'^line\d', text, re.M)) # ['line1', 'line2', 'line3']
# 多个标志组合
print(re.findall(r'hello.world', 'HELLO\nWORLD', re.I | re.S))5. 实际应用
5.1 邮箱验证
import re
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
# 测试
print(validate_email('test@example.com')) # True
print(validate_email('invalid.email')) # False
print(validate_email('user.name+tag@domain.co')) # True5.2 手机号提取
import re
def extract_phones(text):
# 中国手机号
pattern = r'1[3-9]\d{9}'
return re.findall(pattern, text)
text = "联系我:13800138000 或 15912345678"
print(extract_phones(text)) # ['13800138000', '15912345678']5.3 URL 解析
import re
def parse_url(url):
pattern = r'(?P<protocol>https?)://(?P<host>[^:/\s]+)(?::(?P<port>\d+))?(?P<path>/[^\s]*)?'
match = re.match(pattern, url)
if match:
return match.groupdict()
return None
url = "https://example.com:8080/path/to/page?query=1"
print(parse_url(url))
# {'protocol': 'https', 'host': 'example.com', 'port': '8080', 'path': '/path/to/page?query=1'}5.4 日志解析
import re
from datetime import datetime
def parse_log_line(line):
"""解析 Nginx 日志"""
pattern = r'(?P<ip>\S+) - - \[(?P<time>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) (?P<protocol>\S+)" (?P<status>\d+) (?P<size>\d+)'
match = re.match(pattern, line)
return match.groupdict() if match else None
log = '192.168.1.1 - - [15/Jan/2024:14:30:00 +0800] "GET /index.html HTTP/1.1" 200 1234'
result = parse_log_line(log)
print(result)6. 正则最佳实践
import re
# 1. 使用原始字符串
# 推荐
pattern = r'\d+\w+'
# 不推荐
pattern = '\\d+\\w+'
# 2. 复杂正则应编译
EMAIL_PATTERN = re.compile(r'^[\w.-]+@[\w.-]+\.\w{2,}$')
# 3. 使用注释模式(VERBOSE)
PHONE_PATTERN = re.compile(r'''
^
(\+?86)? # 可选的国家代码
[-\s]? # 可选的分隔符
1[3-9]\d{9} # 手机号
$
''', re.VERBOSE)
# 4. 处理匹配失败
match = re.search(r'\d+', 'no numbers')
if match:
print(match.group())
else:
print("未找到匹配")本节小结
- 匹配函数:
match(开头)、search(全局)、findall、finditer - 修改函数:
sub(替换)、split(分割) - 元字符:
. ^ $ * + ? {} [] \ | () - 分组:捕获分组
()、命名分组(?P<name>...)、非捕获(?:...) - 贪婪:
*+?默认贪婪,加?变非贪婪 - 编译:频繁使用的正则应编译,支持标志位
练习
- 编写正则验证中国大陆身份证号(18位)
- 使用正则从 HTML 中提取所有链接的 href 和文本
- 编写函数,将驼峰命名转换为下划线命名(camelCase -> snake_case)
- 使用正则解析 Markdown 中的标题、链接和图片