Python正则表达式详解
·
1. 正则表达式简介
1.1 什么是正则表达式
正则表达式(Regular Expression)是一种用于匹配和处理文本的强大工具,它使用特定的语法规则来描述字符串的模式。
1.2 为什么使用正则表达式
-
强大的文本处理能力
-
高效的字符串匹配
-
灵活的文本提取
-
跨平台兼容性
1.3 Python中的re模块
Python通过re模块提供正则表达式支持:
import re
2. re模块基础
2.1 基本匹配模式
import re
# 简单的文本匹配
text = "Hello, my name is John and I'm 25 years old."
pattern = r"John"
# 使用search方法查找第一个匹配
match = re.search(pattern, text)
if match:
print(f"找到匹配: {match.group()}") # 输出: 找到匹配: John
print(f"匹配位置: {match.start()}到{match.end()}") # 输出: 匹配位置: 17到21
2.2 原始字符串
在Python中,建议使用原始字符串(raw string)来编写正则表达式:
# 推荐使用原始字符串
pattern = r"\d+" # 匹配数字
# 避免使用普通字符串(需要转义反斜杠)
pattern = "\\d+" # 不推荐
| 表达式 | 描述 | 示例 |
. | 匹配任意字符(除换行符) | a.c 匹配 "abc", "a c" |
[abc] | 匹配方括号中的任意字符 | [aeiou] 匹配任意元音字母 |
[^abc] | 匹配不在方括号中的任意字符 | [^aeiou] 匹配非元音字母 |
[a-z] | 匹配指定范围内的字符 | [0-9] 匹配数字 |
\d | 匹配数字,等同于 [0-9] | \d+ 匹配一个或多个数字 |
\D | 匹配非数字 | \D+ 匹配一个或多个非数字 |
\w | 匹配单词字符,等同于 [a-zA-Z0-9_] | \w+ 匹配单词 |
\W | 匹配非单词字符 | \W+ 匹配非单词字符 |
\s | 匹配空白字符(空格、制表符、换行符) | \s+ 匹配空白 |
\S | 匹配非空白字符 | \S+ 匹配非空白 |
3.2 量词
| 量词 | 描述 | 示例 |
* | 匹配0次或多次 | a* 匹配 "", "a", "aa", ... |
+ | 匹配1次或多次 | a+ 匹配 "a", "aa", ... |
? | 匹配0次或1次 | a? 匹配 "", "a" |
{n} | 匹配恰好n次 | a{3} 匹配 "aaa" |
{n,} | 匹配至少n次 | a{2,} 匹配 "aa", "aaa", ... |
{n,m} | 匹配n到m次 | a{2,4} 匹配 "aa", "aaa", "aaaa" |
3.3 边界匹配
| 边界 | 描述 | 示例 |
^ | 匹配字符串开头 | ^Hello 匹配以Hello开头的字符串 |
$ | 匹配字符串结尾 | world$ 匹配以world结尾的字符串 |
| \b | 匹配单词边界 | \bword\b 匹配独立的单词"word" |
\B | 匹配非单词边界 | \Bword\B 匹配非独立的"word" |
3.4 分组和选择
| 表达式 | 描述 | 示例 |
() | 捕获分组 | (abc)+ 匹配 "abc", "abcabc", |
(?:) | 非捕获分组 | (?:abc)+ 匹配但不捕获分组 |
| | 或操作 | cat|dog 匹配 "cat" 或 "dog" |
(?P<name>) | 命名分组 | (?P<year>\d{4}) 命名捕获组 |
3.5 特殊序列
| 序列 | 描述 | 示例 |
\A | 匹配字符串开始 | \AStart |
\Z | 匹配字符串结束 | end\Z |
\n | 匹配换行符 | |
\t | 匹配制表符 |
3.6 贪婪与非贪婪匹配
import re
text = "<div>content</div>"
# 贪婪匹配(默认)
greedy_pattern = r"<.*>"
greedy_match = re.search(greedy_pattern, text)
print(f"贪婪匹配: {greedy_match.group()}") # 输出: <div>content</div>
# 非贪婪匹配
non_greedy_pattern = r"<.*?>"
non_greedy_match = re.search(non_greedy_pattern, text)
print(f"非贪婪匹配: {non_greedy_match.group()}") # 输出: <div>
4. 常用方法详解
4.1 re.match() - 从字符串开始匹配
import re
text = "hello world"
pattern = r"hello"
# match从字符串开头匹配
match_result = re.match(pattern, text)
if match_result:
print("匹配成功:", match_result.group())
else:
print("匹配失败")
# 这个会失败,因为不是从开头匹配
match_result2 = re.match(r"world", text)
print("匹配结果:", match_result2) # None
4.2 re.search() - 搜索整个字符串
import re
text = "The price is $25.99 and quantity is 10"
pattern = r"\$\d+\.\d{2}" # 匹配价格格式
search_result = re.search(pattern, text)
if search_result:
print("找到价格:", search_result.group()) # 输出: $25.99
print("匹配位置:", search_result.span()) # 输出: (13, 19)
4.3 re.findall() - 查找所有匹配
import re
text = "苹果5元, 香蕉3元, 橙子8元"
pattern = r"\w+\d+元"
# 查找所有匹配项
all_matches = re.findall(pattern, text)
print("所有匹配:", all_matches) # 输出: ['苹果5元', '香蕉3元', '橙子8元']
# 使用分组
pattern2 = r"(\w+)(\d+)元"
matches_with_groups = re.findall(pattern2, text)
print("分组匹配:", matches_with_groups) # 输出: [('苹果', '5'), ('香蕉', '3'), ('橙子', '8')]
4.4 re.finditer() - 返回迭代器
import re
text = "Contact: john@example.com, mary@test.org"
pattern = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
# 使用finditer获取匹配对象
email_matches = re.finditer(pattern, text)
for match in email_matches:
print(f"邮箱: {match.group()}")
print(f"位置: {match.span()}")
4.5 re.sub() - 替换匹配文本
import re
text = "今天是2023-12-25,明天是2023-12-26"
pattern = r"(\d{4})-(\d{2})-(\d{2})"
# 替换日期格式
new_text = re.sub(pattern, r"\2/\3/\1", text)
print("替换后:", new_text) # 输出: 今天是12/25/2023,明天是12/26/2023
# 使用函数进行复杂替换
def date_replacer(match):
year, month, day = match.groups()
return f"{day}/{month}/{year}"
new_text2 = re.sub(pattern, date_replacer, text)
print("函数替换:", new_text2)
4.6 re.split() - 根据模式分割字符串
import re
text = "苹果,香蕉;橙子 西瓜|桃子"
pattern = r"[,; \|]" # 匹配逗号、分号、空格、竖线
# 分割字符串
result = re.split(pattern, text)
print("分割结果:", result) # 输出: ['苹果', '香蕉', '橙子', '西瓜', '桃子']
# 限制分割次数
result_limited = re.split(pattern, text, maxsplit=2)
print("限制分割:", result_limited) # 输出: ['苹果', '香蕉', '橙子 西瓜|桃子']
4.7 re.compile() - 编译正则表达式
import re
# 编译正则表达式(提高性能)
pattern = re.compile(r"\b\w{5}\b") # 匹配5个字母的单词
text = "Hello world this is a test string with multiple words"
# 使用编译后的模式
matches = pattern.findall(text)
print("5字母单词:", matches) # 输出: ['Hello', 'world']
# 编译时添加标志
pattern_ignore_case = re.compile(r"hello", re.IGNORECASE)
text2 = "Hello HELLO hello"
matches2 = pattern_ignore_case.findall(text2)
print("忽略大小写:", matches2) # 输出: ['Hello', 'HELLO', 'hello']
5. 高级技巧
5.1 标志(Flags)
import re
text = "Hello\nWORLD\nhello"
# IGNORECASE - 忽略大小写
result1 = re.findall(r"hello", text, re.IGNORECASE)
print("忽略大小写:", result1) # 输出: ['Hello', 'hello']
# MULTILINE - 多行模式
result2 = re.findall(r"^hello", text, re.IGNORECASE | re.MULTILINE)
print("多行模式:", result2) # 输出: ['Hello', 'hello']
# DOTALL - 让.匹配包括换行符的所有字符
result3 = re.findall(r"Hello.*hello", text, re.DOTALL | re.IGNORECASE)
print("DOTALL模式:", result3) # 输出: ['Hello\\nWORLD\\nhello']
# VERBOSE - 允许注释和空白
pattern = re.compile(r"""
\b # 单词边界
\d{3} # 3位数字
-? # 可选的连字符
\d{2} # 2位数字
-? # 可选的连字符
\d{4} # 4位数字
\b # 单词边界
""", re.VERBOSE)
text4 = "123-45-6789 123456789"
matches4 = pattern.findall(text4)
print("详细模式:", matches4) # 输出: ['123-45-6789', '123456789']
5.2 前后查找
import re
text = "苹果5元 香蕉3元 橙子8元"
# 正向向后查找 - 匹配"元"前面的数字
pattern1 = r"\d+(?=元)"
matches1 = re.findall(pattern1, text)
print("价格数字:", matches1) # 输出: ['5', '3', '8']
# 正向向前查找 - 匹配数字后面的"元"
pattern2 = r"(?<=\d)元"
matches2 = re.findall(pattern2, text)
print("元字:", matches2) # 输出: ['元', '元', '元']
# 负向向后查找 - 匹配不在特定模式前的数字
pattern3 = r"\d+(?!元)"
text3 = "5个苹果 3kg香蕉"
matches3 = re.findall(pattern3, text3)
print("非价格数字:", matches3) # 输出: ['5', '3']
5.3 条件匹配
import re
# 使用条件表达式
text = "color colour"
pattern = r"col(o|ou)r" # 匹配color或colour
matches = re.findall(pattern, text)
print("条件匹配:", matches) # 输出: ['o', 'ou']
5.4 命名分组和反向引用
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))
# 测试邮箱验证
emails = [
"user@example.com",
"invalid-email",
"user.name@domain.co.uk",
"user@domain"
]
for email in emails:
is_valid = validate_email(email)
print(f"{email}: {'有效' if is_valid else '无效'}")
6.2 URL提取
import re
def extract_urls(text):
pattern = r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\w\.-]*\??[/\w\.-=&%]*'
return re.findall(pattern, text)
# 测试URL提取
text = """
访问我们的网站 https://www.example.com 或者
http://sub.domain.co.uk/path?query=value
还有无效的URL: ftp://example.com
"""
urls = extract_urls(text)
print("提取的URL:", urls)
6.3 HTML标签处理
import re
def remove_html_tags(html):
# 移除非破坏性空格和HTML标签
clean = re.compile(r' |<.*?>')
return clean.sub('', html)
def extract_links(html):
# 提取所有链接
pattern = r'<a\s+(?:[^>]*?\s+)?href=(["\'])(.*?)\1'
return re.findall(pattern, html)
# 测试HTML处理
html_text = """
<div class="content">
<p>这是一个<a href="https://example.com">链接</a></p>
<a href='/about'>关于我们</a>
</div>
"""
clean_text = remove_html_tags(html_text)
print("清理后文本:", clean_text.strip())
links = extract_links(html_text)
print("提取的链接:", [link[1] for link in links])
6.4 数据清洗
import re
def clean_text(text):
# 移除多余空格
text = re.sub(r'\s+', ' ', text)
# 移除特殊字符,保留中文、英文、数字和基本标点
text = re.sub(r'[^\w\s\u4e00-\u9fff,。!?]', '', text)
# 标准化标点符号
text = re.sub(r'[,,]+', ',', text)
text = re.sub(r'[。.]+', '。', text)
return text.strip()
# 测试数据清洗
dirty_text = "Hello!!! 这是一个测试文本。。。 有很多 多余空格@@@和!!!特殊字符"
clean = clean_text(dirty_text)
print("清洗前:", dirty_text)
print("清洗后:", clean)
6.5 日志分析
import re
from collections import Counter
def analyze_logs(log_text):
# 解析Apache/Nginx风格的访问日志
pattern = r'(\d+\.\d+\.\d+\.\d+) - - \[(.*?)\] "(.*?)" (\d+) (\d+) "(.*?)" "(.*?)"'
ip_counter = Counter()
status_counter = Counter()
for match in re.finditer(pattern, log_text):
ip, timestamp, request, status, size, referer, user_agent = match.groups()
ip_counter[ip] += 1
status_counter[status] += 1
return {
'ip_requests': dict(ip_counter.most_common(5)),
'status_codes': dict(status_counter)
}
# 模拟日志分析
log_data = """
192.168.1.1 - - [25/Dec/2023:10:30:00 +0800] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
192.168.1.2 - - [25/Dec/2023:10:31:00 +0800] "POST /login HTTP/1.1" 401 567 "-" "Mozilla/5.0"
192.168.1.1 - - [25/Dec/2023:10:32:00 +0800] "GET /about.html HTTP/1.1" 200 2345 "-" "Mozilla/5.0"
"""
analysis = analyze_logs(log_data)
print("日志分析结果:", analysis)
7. 性能优化
7.1 预编译正则表达式
import re
import time
# 不推荐:每次调用都编译
def slow_method(text):
for i in range(1000):
re.findall(r'\d+', text)
# 推荐:预编译
def fast_method(text):
pattern = re.compile(r'\d+')
for i in range(1000):
pattern.findall(text)
text = "这里有123个苹果和456个橙子"
# 性能对比
start = time.time()
slow_method(text)
print("未编译耗时:", time.time() - start)
start = time.time()
fast_method(text)
print("预编译耗时:", time.time() - start)
7.2 避免回溯灾难
import re
# 有问题的模式(可能导致 catastrophic backtracking)
problematic_pattern = r"(\w+)*@"
# 改进的模式
better_pattern = r"\w+@"
text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"
try:
# 这个可能会很慢或导致栈溢出
# re.match(problematic_pattern, text)
result = re.match(better_pattern, text)
print("匹配成功:", result)
except re.error as e:
print("正则表达式错误:", e)
7.3 使用非贪婪量词
import re
text = "<div>content1</div><div>content2</div>"
# 贪婪匹配(可能匹配过多内容)
greedy_result = re.findall(r"<div>.*</div>", text)
print("贪婪匹配:", greedy_result) # 输出: ['<div>content1</div><div>content2</div>']
# 非贪婪匹配(更精确)
non_greedy_result = re.findall(r"<div>.*?</div>", text)
print("非贪婪匹配:", non_greedy_result) # 输出: ['<div>content1</div>', '<div>content2</div>']
7.4 最佳实践总结
-
预编译常用模式
-
使用原始字符串
-
避免过度复杂的正则表达式
-
优先使用非贪婪匹配
-
合理使用分组
-
测试边界情况
-
考虑使用其他字符串方法替代简单匹配
总结
正则表达式是Python中强大的文本处理工具,通过掌握其语法和re模块的各种方法,可以高效地解决复杂的字符串处理问题。记住:
-
从简单的模式开始,逐步构建复杂表达式
-
充分利用分组和反向引用
-
注意贪婪匹配和非贪婪匹配的区别
-
预编译正则表达式以提高性能
-
测试你的正则表达式以确保准确性
通过不断练习和实践,你将能够熟练运用正则表达式来处理各种文本处理任务。
更多推荐
所有评论(0)