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'&nbsp;|<.*?>')
    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 最佳实践总结

  1. 预编译常用模式

  2. 使用原始字符串

  3. 避免过度复杂的正则表达式

  4. 优先使用非贪婪匹配

  5. 合理使用分组

  6. 测试边界情况

  7. 考虑使用其他字符串方法替代简单匹配


总结

正则表达式是Python中强大的文本处理工具,通过掌握其语法和re模块的各种方法,可以高效地解决复杂的字符串处理问题。记住:

  • 从简单的模式开始,逐步构建复杂表达式

  • 充分利用分组和反向引用

  • 注意贪婪匹配和非贪婪匹配的区别

  • 预编译正则表达式以提高性能

  • 测试你的正则表达式以确保准确性

通过不断练习和实践,你将能够熟练运用正则表达式来处理各种文本处理任务。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐