正则表达式基础
学习Python中正则表达式的基础语法,掌握模式匹配和文本处理的强大工具
正则表达式概述
正则表达式是一种强大的文本模式匹配工具,用于查找、替换和验证文本。Python通过内置的re模块提供了对正则表达式的支持。
正则表达式的应用场景
- 验证邮箱、电话号码、身份证号等格式
- 从文本中提取特定信息
- 文本替换和格式化
- 日志分析和处理
- 网页爬虫数据提取
import re
# 简单示例:匹配邮箱
email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
text = "请联系我们:admin@example.com 或 support@test.org"
matches = re.findall(email_pattern, text)
print(matches) # 输出: ['admin@example.com', 'support@test.org']
基本语法
正则表达式由普通字符和特殊字符(元字符)组成,用于定义匹配模式。
| 语法 | 描述 | 示例 |
|---|---|---|
. |
匹配任意单个字符(除换行符外) | "a.c" 匹配 "abc", "a1c" |
\ |
转义特殊字符 | "a\.c" 只匹配 "a.c" |
| |
逻辑或 | "cat|dog" 匹配 "cat" 或 "dog" |
实操案例
案例1:匹配不同格式的日期
import re
# 匹配多种日期格式:2023-12-01, 2023/12/01, 2023.12.01
date_pattern = r"\d{4}[-/.]\d{1,2}[-/.]\d{1,2}"
text = "项目开始日期:2023-12-01,结束日期:2023/12/31,审核日期:2024.01.05"
matches = re.findall(date_pattern, text)
print(matches) # 输出: ['2023-12-01', '2023/12/31', '2024.01.05']
案例2:匹配产品型号
import re
# 匹配产品型号格式:字母+数字+字母
product_pattern = r"[A-Za-z]\d+[A-Za-z]"
text = "库存产品:A123B, C45D, X6789Y, 123ABC"
matches = re.findall(product_pattern, text)
print(matches) # 输出: ['A123B', 'C45D', 'X6789Y']
字符类
字符类用于匹配一组特定的字符,用方括号[]表示。
| 语法 | 描述 | 示例 |
|---|---|---|
[abc] |
匹配a、b或c中的任意一个字符 | "[aeiou]" 匹配任意元音字母 |
[a-z] |
匹配任意小写字母 | "[a-z]{3}" 匹配3个小写字母 |
[A-Z] |
匹配任意大写字母 | |
[0-9] |
匹配任意数字 | "[0-9]+" 匹配一个或多个数字 |
[^abc] |
匹配除a、b、c外的任意字符 | |
\d |
匹配任意数字,等价于[0-9] | |
\D |
匹配任意非数字字符 | |
\w |
匹配字母、数字或下划线 | |
\W |
匹配非字母、数字或下划线 | |
\s |
匹配空白字符(空格、制表符、换行符等) | |
\S |
匹配非空白字符 |
实操案例
案例1:提取文本中的所有数字
import re
# 提取所有数字
text = "价格:¥129.99,数量:20个,折扣:8.5折"
numbers = re.findall(r"\d+\.?\d*", text)
print(numbers) # 输出: ['129.99', '20', '8.5']
案例2:过滤特殊字符
import re
# 过滤掉所有非字母和数字的字符(包括中文字符)
text = "Hello! World, 你好!123#$%"
clean_text = re.sub(r"[^a-zA-Z0-9\s\u4e00-\u9fa5]", "", text)
print(clean_text) # 输出: 'Hello World 你好 123'
案例3:验证用户名格式(字母开头,允许字母、数字、下划线,长度4-20位)
import re
def validate_username(username):
pattern = r"^[a-zA-Z][a-zA-Z0-9_]{3,19}$"
return bool(re.match(pattern, username))
print(validate_username("user123")) # 输出: True
print(validate_username("123user")) # 输出: False
print(validate_username("user")) # 输出: True
print(validate_username("user@123")) # 输出: False
量词
量词用于指定字符或字符组合的重复次数。
| 语法 | 描述 | 示例 |
|---|---|---|
* |
匹配前面的字符0次或多次 | "a*" 匹配 "", "a", "aa"... |
+ |
匹配前面的字符1次或多次 | "a+" 匹配 "a", "aa"... |
? |
匹配前面的字符0次或1次 | "colou?r" 匹配 "color" 和 "colour" |
{n} |
精确匹配前面的字符n次 | "\d{3}" 匹配3位数字 |
{n,} |
匹配前面的字符至少n次 | "\d{2,}" 匹配2位或更多数字 |
{n,m} |
匹配前面的字符n到m次 | "\d{2,4}" 匹配2-4位数字 |
实操案例
案例1:匹配手机号码(中国大陆)
import re
# 匹配中国大陆手机号码
def validate_phone(phone):
pattern = r"^1[3-9]\d{9}$"
return bool(re.match(pattern, phone))
print(validate_phone("13812345678")) # 输出: True
print(validate_phone("12345678901")) # 输出: False
print(validate_phone("1381234")) # 输出: False
案例2:匹配URL地址
import re
# 匹配HTTP/HTTPS URL
url_pattern = r"^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$"
urls = ["https://www.example.com", "http://test.org", "www.invalid", "http://127.0.0.1"]
for url in urls:
print(f"{url}: {bool(re.match(url_pattern, url))}")
# 输出:
# https://www.example.com: True
# http://test.org: True
# www.invalid: False
# http://127.0.0.1: True
案例3:提取HTML标签
import re
# 提取HTML标签
html = "<div class='container'><h1>Title</h1><p>Paragraph</p></div>"
html_tags = re.findall(r"<[^>]+>", html)
print(html_tags) # 输出: ['<div class="container">', '<h1>', '</h1>', '<p>', '</p>', '</div>']
贪婪与非贪婪匹配
正则表达式中的量词默认是贪婪的,会尽可能多地匹配字符。在量词后添加?可以将其转换为非贪婪模式,尽可能少地匹配字符。
| 模式类型 | 语法 | 描述 |
|---|---|---|
| 贪婪模式 | *, +, ?, {n,m} |
尽可能多地匹配字符 |
| 非贪婪模式 | *?, +?, ??, {n,m}? |
尽可能少地匹配字符 |
实操案例
案例1:贪婪与非贪婪对比
import re
# 测试文本
text = "<div>内容1</div><div>内容2</div>"
# 贪婪匹配
greedy_pattern = r"<div>.*</div>"
greedy_match = re.findall(greedy_pattern, text)
print("贪婪匹配结果:", greedy_match) # 输出: ['<div>内容1</div><div>内容2</div>']
# 非贪婪匹配
lazy_pattern = r"<div>.*?</div>"
lazy_match = re.findall(lazy_pattern, text)
print("非贪婪匹配结果:", lazy_match) # 输出: ['<div>内容1</div>', '<div>内容2</div>']
案例2:提取HTML标签内容
import re
# 测试文本
html = "<p class=\"intro\">这是介绍段落</p><p>这是普通段落</p>"
# 非贪婪匹配提取标签内容
pattern = r"<p[^>]*>(.*?)</p>"
contents = re.findall(pattern, html)
print("提取的内容:", contents) # 输出: ['这是介绍段落', '这是普通段落']
案例3:解析日志文件
import re
# 日志文本
log = "2023-05-01 10:15:30 ERROR: Failed to connect to database\n2023-05-01 10:16:45 WARNING: Low disk space"
# 使用非贪婪匹配提取日志级别和消息
greedy_pattern = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.*): (.*)"
lazy_pattern = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.*?): (.*)"
# 对于这种情况,贪婪和非贪婪匹配结果相同,但非贪婪模式在复杂情况下更可靠
matches = re.findall(lazy_pattern, log)
for match in matches:
print(f"时间: {match[0]}, 级别: {match[1]}, 消息: {match[2]}")
锚点
锚点用于指定匹配的位置,不匹配任何实际字符。
| 语法 | 描述 |
|---|---|
^ |
匹配字符串的开始位置 |
$ |
匹配字符串的结束位置 |
\b |
匹配单词边界 |
\B |
匹配非单词边界 |
实操案例
案例1:验证密码强度(至少8位,包含大小写字母和数字)
import re
def validate_password(password):
# 至少8位,包含大小写字母和数字
pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$"
return bool(re.match(pattern, password))
print(validate_password("Password123")) # 输出: True
print(validate_password("password")) # 输出: False
print(validate_password("PASSWORD123")) # 输出: False
print(validate_password("Pass1")) # 输出: False
案例2:匹配完整单词
import re
# 只匹配完整的单词"cat"
text = "The cat sat on the cathedral"
matches = re.findall(r"\bcat\b", text)
print(matches) # 输出: ['cat']
# 匹配所有包含"cat"的单词
matches_all = re.findall(r"cat", text)
print(matches_all) # 输出: ['cat', 'cat']
分组
分组用于将多个字符组合成一个单元,可以对分组应用量词,也可以在匹配后提取分组内容。
| 语法 | 描述 |
|---|---|
(pattern) |
创建一个捕获组 |
(?:pattern) |
创建一个非捕获组 |
(?P<name>pattern) |
创建一个命名捕获组 |
实操案例
案例1:提取URL中的域名
import re
url = "https://www.example.com/path/to/page"
pattern = r"^https?:\/\/(www\.)?([a-zA-Z0-9.-]+)"
match = re.match(pattern, url)
if match:
domain = match.group(2) # 提取第二个捕获组
print(f"域名: {domain}") # 输出: 域名: example.com
案例2:使用命名捕获组提取日期信息
import re
date_text = "2023-12-01"
pattern = r"^(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})$"
match = re.match(pattern, date_text)
if match:
year = match.group("year")
month = match.group("month")
day = match.group("day")
print(f"年: {year}, 月: {month}, 日: {day}") # 输出: 年: 2023, 月: 12, 日: 01
案例3:替换重复的单词
import re
# 去除文本中重复的单词
text = "这这是是一个个重重复复的的文文本文本"
# 匹配任意字符重复两次
result = re.sub(r"(.)\1", r"\1", text)
print(result) # 输出: 这是一个重复的文本
# 英文示例
en_text = "the the quick quick brown fox"
# 匹配完整单词重复
en_result = re.sub(r"\b(\w+)\s+\1\b", r"\1", en_text)
print(en_result) # 输出: the quick brown fox
Python re模块
Python的re模块提供了一系列处理正则表达式的函数。
| 函数 | 描述 |
|---|---|
re.match() |
从字符串开头匹配模式 |
re.search() |
在整个字符串中搜索模式 |
re.findall() |
找到所有匹配的子串并返回列表 |
re.finditer() |
找到所有匹配并返回迭代器 |
re.sub() |
替换匹配的子串 |
re.split() |
根据匹配分割字符串 |
re.compile() |
编译正则表达式模式以提高效率 |
实操案例
案例1:使用re.compile优化正则表达式性能
import re
import time
# 编译正则表达式
email_pattern = re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}")
# 测试文本
text = "联系邮箱:admin@example.com, support@test.org, user123@gmail.com"
# 使用编译后的正则表达式
start_time = time.time()
matches = email_pattern.findall(text)
end_time = time.time()
print(f"匹配结果: {matches}")
print(f"耗时: {(end_time - start_time) * 1000:.4f} 毫秒")
# 对于频繁使用的正则表达式,编译可以显著提高性能
案例2:使用re.split()分割字符串
import re
# 使用正则表达式分割字符串
text = "apple,orange;banana grape\tpear"
# 按逗号、分号、空格或制表符分割
result = re.split(r"[,;\s\t]+", text)
print(result) # 输出: ['apple', 'orange', 'banana', 'grape', 'pear']
# 保留分隔符的示例
date_text = "2023-12-01 2023/12/02 2023.12.03"
# 按日期分隔符分割并保留分隔符
result_with_separators = re.split(r"([-/.])", date_text)
print(result_with_separators) # 输出: ['2023', '-', '12', '-', '01 2023', '/', '12', '/', '02 2023', '.', '12', '.', '03']
综合实操案例
故事化案例:小明的日志分析器
小明需要开发一个日志分析器,从服务器日志中提取有用信息。让我们用正则表达式来帮助他!
案例1:提取IP地址
import re
# 从日志中提取IP地址
log_text = "2023-12-01 10:15:30 192.168.1.1 GET /index.html 200\n"
log_text += "2023-12-01 10:16:45 10.0.0.5 POST /login 401\n"
log_text += "2023-12-01 10:17:20 172.16.254.1 GET /api/data 200"
# 匹配IPv4地址的正则表达式
ip_pattern = r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
ips = re.findall(ip_pattern, log_text)
print(f"提取的IP地址: {ips}") # 输出: ['192.168.1.1', '10.0.0.5', '172.16.254.1']
案例2:分析HTTP请求方法和状态码
import re
# 分析HTTP请求方法和状态码
log_text = "2023-12-01 10:15:30 192.168.1.1 GET /index.html 200\n"
log_text += "2023-12-01 10:16:45 10.0.0.5 POST /login 401\n"
log_text += "2023-12-01 10:17:20 172.16.254.1 GET /api/data 200"
# 提取请求方法和状态码
request_pattern = r"(GET|POST|PUT|DELETE|PATCH) .+ (\d{3})"
matches = re.findall(request_pattern, log_text)
print("请求方法和状态码:")
for method, status in matches:
print(f"方法: {method}, 状态码: {status}")
# 输出:
# 请求方法和状态码:
# 方法: GET, 状态码: 200
# 方法: POST, 状态码: 401
# 方法: GET, 状态码: 200
案例3:提取URL路径参数
import re
# 从URL中提取路径参数
url = "https://example.com/search?q=python&category=tutorials&page=2&sort=relevance"
# 提取查询参数
param_pattern = r"[?&]([^=]+)=([^&]+)"
params = re.findall(param_pattern, url)
param_dict = {key: value for key, value in params}
print(f"提取的参数: {param_dict}")
# 输出: {'q': 'python', 'category': 'tutorials', 'page': '2', 'sort': 'relevance'}
20个复杂匹配案例
下面是20个实用的复杂正则表达式匹配案例,涵盖了各种常见的文本处理场景。
1. 匹配邮箱地址
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
2. 匹配手机号码(中国大陆)
r"^1[3-9]\d{9}$"
3. 匹配URL(HTTP/HTTPS)
r"^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$"
4. 匹配IPv4地址
r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
5. 匹配日期(YYYY-MM-DD)
r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$"
6. 匹配时间(HH:MM:SS)
r"^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$"
7. 匹配身份证号码(18位)
r"^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$"
8. 匹配强密码(至少8位,包含大小写字母、数字和特殊字符)
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
9. 匹配中文字符
r"[\u4e00-\u9fa5]+"
10. 匹配HTML标签
r"<[^>]+>"
11. 匹配CSS颜色值(HEX)
r"#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"
12. 匹配JavaScript函数定义
r"function\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*{"
13. 匹配SQL表名
r"FROM\s+([a-zA-Z0-9_]+)"
14. 匹配Markdown标题
r"^#{1,6}\s+(.*)$"
15. 匹配CSV文件中的单元格
r"""(?:[^""\,]+|\.)*""|[^,]+"
16. 匹配MAC地址
r"^(?:[0-9A-Fa-f]{2}[:-]){5}(?:[0-9A-Fa-f]{2})$"
17. 匹配邮政编码(中国)
r"^[1-9]\d{5}$"
18. 匹配文件路径(Windows)
r"^[a-zA-Z]:\(?:[^\/:*?\"<>|\r\n]+\)*[^\/:*?\"<>|\r\n]*$"
19. 匹配文件路径(Linux/Unix)
r"^/(?:[^/\0]+/)*[^/\0]*$"
20. 匹配版本号(SemVer)
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
练习题
练习题1:验证邮箱格式
编写一个函数,使用正则表达式验证邮箱地址是否格式正确。
提示:标准邮箱格式包含用户名、@符号、域名和顶级域名。
练习题2:提取电话号码
从一段文本中提取所有的手机号码(中国大陆格式)。
提示:中国大陆手机号码通常以1开头,后跟10位数字。
练习题3:格式化手机号码
编写一个函数,将中国大陆手机号码格式化为 XXX-XXXX-XXXX 的形式。
提示:使用正则表达式的分组功能和替换功能。
练习题4:提取HTML标签文本
编写一个函数,从HTML文本中提取指定标签内的所有文本内容。
提示:使用正则表达式的分组捕获标签内的文本。
练习题5:验证身份证号码
编写一个函数,使用正则表达式验证18位身份证号码的格式是否正确。
提示:身份证号码前6位为地址码,接下来8位为出生日期码,然后3位为顺序码,最后1位为校验码(可能是X)。