正则表达式基础

学习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)。