文章

正则匹配

正则匹配

1. 导入 re 模块

1
import re

2. 基本方法

2.1 re.match() - 从字符串开头匹配

1
2
3
4
5
6
# 匹配字符串开头
result = re.match(r'hello', 'hello world')
if result:
    print("Match found:", result.group())  # hello
else:
    print("No match")

2.2 re.search() - 搜索整个字符串

1
2
3
4
5
6
# 搜索第一个匹配
result = re.search(r'world', 'hello world')
if result:
    print("Found:", result.group())  # world
    print("Start position:", result.start())  # 6
    print("End position:", result.end())    # 11

2.3 re.findall() - 查找所有匹配

1
2
3
4
5
6
7
8
# 查找所有匹配的字符串
text = "苹果 10元,香蕉 5元,橙子 8元"
prices = re.findall(r'\d+元', text)
print(prices)  # ['10元', '5元', '8元']

# 只提取数字
numbers = re.findall(r'\d+', text)
print(numbers)  # ['10', '5', '8']

2.4 re.finditer() - 返回迭代器

1
2
3
4
5
6
# 返回匹配对象的迭代器
text = "今天是2023-10-01,明天是2023-10-02"
pattern = r'\d{4}-\d{2}-\d{2}'

for match in re.finditer(pattern, text):
    print(f"Found: {match.group()} at {match.start()}-{match.end()}")

2.5 re.sub() - 替换字符串

1
2
3
4
5
6
7
8
9
10
11
12
# 替换匹配的内容
text = "今天是2023-10-01"
new_text = re.sub(r'\d{4}-\d{2}-\d{2}', 'XXXX-XX-XX', text)
print(new_text)  # 今天是XXXX-XX-XX

# 使用函数进行替换
def replace_date(match):
    return f"日期: {match.group()}"

text = "今天是2023-10-01"
new_text = re.sub(r'\d{4}-\d{2}-\d{2}', replace_date, text)
print(new_text)  # 今天是日期: 2023-10-01

2.6 re.split() - 分割字符串

1
2
3
4
# 按正则表达式分割
text = "苹果,香蕉;橙子 西瓜"
items = re.split(r'[,;\s]+', text)
print(items)  # ['苹果', '香蕉', '橙子', '西瓜']

3. 常用正则表达式模式

3.1 基础模式

1
2
3
4
5
6
7
8
9
10
11
12
# 匹配数字
re.findall(r'\d+', "我有100元")  # ['100']

# 匹配单词
re.findall(r'\w+', "hello world!")  # ['hello', 'world']

# 匹配空白字符
re.split(r'\s+', "a  b   c")  # ['a', 'b', 'c']

# 匹配任意字符(除换行外)
re.search(r'a.b', "a b")  # 匹配
re.search(r'a.b', "a\nb")  # 不匹配

3.2 量词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# * 0次或多次
re.findall(r'ab*', "a ab abb abbb")  # ['a', 'ab', 'abb', 'abbb']

# + 1次或多次
re.findall(r'ab+', "a ab abb abbb")  # ['ab', 'abb', 'abbb']

# ? 0次或1次
re.findall(r'ab?', "a ab abb")  # ['a', 'ab', 'ab']

# {n} 恰好n次
re.findall(r'\d{3}', "123 4567 89")  # ['123', '456']

# {n,} 至少n次
re.findall(r'\d{3,}', "12 123 1234")  # ['123', '1234']

# {n,m} n到m次
re.findall(r'\d{2,4}', "1 12 123 1234 12345")  # ['12', '123', '1234', '1234']

3.3 字符集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# [abc] 匹配a、b或c
re.findall(r'[abc]', "apple banana cherry")  # ['a', 'b', 'a', 'a', 'c']

# [a-z] 匹配小写字母
re.findall(r'[a-z]+', "Hello World")  # ['ello', 'orld']

# [^abc] 不匹配a、b、c
re.findall(r'[^aeiou]', "hello")  # ['h', 'l', 'l']

# 常用字符集简写
# \d = [0-9]
# \D = [^0-9]
# \w = [a-zA-Z0-9_]
# \W = [^a-zA-Z0-9_]
# \s = 空白字符
# \S = 非空白字符

4. 分组和捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 简单分组
text = "2023-10-01"
match = re.match(r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
    print(match.group())      # 2023-10-01
    print(match.group(1))     # 2023
    print(match.group(2))     # 10
    print(match.group(3))     # 01
    print(match.groups())     # ('2023', '10', '01')

# 命名分组
text = "John: 30"
match = re.match(r'(?P<name>\w+): (?P<age>\d+)', text)
if match:
    print(match.group('name'))  # John
    print(match.group('age'))   # 30

# 非捕获分组 (?:...)
text = "hello world"
match = re.match(r'(?:hello) (world)', text)
print(match.groups())  # ('world',) 只捕获world

5. 编译正则表达式(提高效率)

1
2
3
4
5
6
7
8
9
10
# 预编译正则表达式(适合多次使用)
pattern = re.compile(r'\d{3}-\d{4}')

# 使用编译后的模式
result1 = pattern.search("电话: 123-4567")
result2 = pattern.findall("电话1: 123-4567, 电话2: 890-1234")

# 编译时可以添加标志
pattern = re.compile(r'hello', re.IGNORECASE)  # 忽略大小写
pattern.search("HELLO world")  # 匹配

6. 标志(Flags)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 常用标志
text = "HELLO\nworld"

# re.I / re.IGNORECASE - 忽略大小写
re.findall(r'hello', text, re.I)  # ['HELLO']

# re.M / re.MULTILINE - 多行模式
re.findall(r'^world', text, re.M)  # ['world']

# re.S / re.DOTALL - 让.匹配包括换行符在内的所有字符
re.findall(r'HELLO.*world', text, re.S)  # ['HELLO\nworld']

# re.X / re.VERBOSE - 允许添加注释和空白
pattern = re.compile(r'''
    \d{3}   # 区号
    -       # 分隔符
    \d{8}   # 电话号码
''', re.VERBOSE)

7. 实用示例

示例1:验证邮箱格式

1
2
3
4
5
6
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

示例2:提取HTML标签内容

1
2
3
html = "<h1>标题</h1><p>段落内容</p>"
titles = re.findall(r'<h1>(.*?)</h1>', html)
paragraphs = re.findall(r'<p>(.*?)</p>', html)

示例3:提取URL参数

1
2
3
url = "https://example.com/page?name=John&age=30&city=Beijing"
params = re.findall(r'([^&=]+)=([^&=]+)', url)
print(dict(params))  # {'name': 'John', 'age': '30', 'city': 'Beijing'}

示例4:处理复杂文本

1
2
3
4
5
6
7
8
9
10
11
text = """
用户1: john@email.com, 电话: 138-1234-5678
用户2: jane@email.com, 电话: 139-8765-4321
"""

# 同时提取邮箱和电话
pattern = r'([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}).*?(\d{3}-\d{4}-\d{4})'
matches = re.findall(pattern, text, re.S)

for email, phone in matches:
    print(f"邮箱: {email}, 电话: {phone}")

8. 注意事项

  1. 使用原始字符串(raw string):在正则表达式前加 r,避免转义问题
  2. 贪婪 vs 非贪婪
    • .* 是贪婪匹配(匹配尽可能多)
    • .*? 是非贪婪匹配(匹配尽可能少)
  3. 性能考虑:多次使用的正则表达式应该先编译
  4. 边界匹配:使用 ^$ 匹配字符串开头和结尾
  5. 特殊字符需要转义. * + ? ^ $ { } [ ] ( ) | \

9. 调试工具

如果正则表达式复杂,可以使用在线工具调试:

本文由作者按照 CC BY 4.0 进行授权