Python的re模块:正则表达式处理的魔法棒
对话实录
小白:(发愁)我想从一大段文本里找出所有邮箱地址,手动找太费劲了,有没有简便方法?
专家:(挥舞魔法棒)用 Python 的re模块,轻松实现文本的搜索、匹配与替换!不过,要想用好它,得先掌握正则表达式的基础知识。
正则表达式基础知识
正则表达式作为高级的文本模式匹配、抽取、和搜索。简单地说,正则表达式(简称为 regex)是一些由字符和特殊符号组成的字符串,按照设定的匹配逻辑能够匹配一系列有相似特征的字符串。
常用到到的表达式符号如下,将不同的符号组合为正则表达式。
符号 |
解释 |
示例 |
re1|re2 |
匹配正则表达式 re1 或者 re2 |
foo|bar |
. |
匹配任何字符(除了\n 之外) |
b.b |
^ |
匹配字符串起始部分 |
^Dear |
$ |
匹配字符串终止部分 |
/bin/*sh$ |
* |
匹配 0 次或者多次前面出现的正则表达式 |
[A-Za-z0-9]* |
+ |
匹配 1 次或者多次前面出现的正则表达式 |
[a-z]+\.com |
? |
匹配 0 次或者 1 次前面出现的正则表达式 |
goo? |
{N} |
匹配 N 次前面出现的正则表达式 |
[0-9]{3} |
{M,N} |
匹配 M~N 次前面出现的正则表达式 |
[0-9]{5,9} |
[…] |
匹配来自字符集的任意单一字符 |
[aeiou] |
[..x−y..] |
匹配 x~y 范围中的任意单一字符 |
[0-9], [A-Za-z] |
[^…] |
不匹配此字符集中出现的任何一个字符,包括某一范围的字符(如果在此字符集中出现) |
[^aeiou], [^A-Za-z0-9] |
(*|+|?|{})? |
用于匹配上面频繁出现/重复出现符号的非贪婪版本(*、+、?、{}) |
.*?[a-z] |
(…) |
匹配封闭的正则表达式,然后另存为子组 |
([0-9]{3})?,f(oo| |
\d |
匹配任何十进制数字,与[0-9]一致(\D 与\d 相反,不匹配任何非数值型的数字) |
data\d+.txt |
\w |
匹配任何字母数字字符,与[A-Za-z0-9_]相同(\W 与之相反) |
[A-Za-z_]\w+ |
\s |
匹配任何空格字符,与[\n\t\r\v\f]相同(\S 与之相反) |
of\sthe |
\b |
匹配任何单词边界(\B 与之相反) |
\bThe\b |
\N |
匹配已保存的子组 N(参见上面的(…)) |
price: \16 |
\c |
逐字匹配任何特殊字符 c(即,仅按照字面意义匹配,不匹配特殊含义) |
\., \\, \* |
\A(\Z) |
匹配字符串的起始(结束)(另见上面介绍的^和$) |
\ADear |
举例:正则表达式中,普通字符会精确匹配自身。比如要匹配字符串 “hello” 中的 “h”,直接用 “h” 就能匹配。
import re
text = "hello"
match = re.search('h', text)
if match:
print("匹配成功")
特殊字符在正则里有特定含义,不能直接匹配其字面意思。像\就是特殊字符,若要匹配反斜杠,需写成\\。
re模块基础入门
1 常用函数速查表
函数 |
用法 |
说明 |
re.match |
re.match(pattern, string) |
尝试从字符串的起始部分对模式进行匹配。如果匹配成功,返回一个匹配对象;如果匹配失败,返回 None; 匹配对象的 group()方法用于显示成功的匹配。 |
re.search |
re.search(pattern, string) |
search()方法会在任意位置搜索正则表达式第一次出现的匹配情况(即使可以匹配到多个,也只会获取第一次匹配到的数据)。如果搜索到成功的匹配,会返回一个匹配对象;否则,返回 None。 |
re.sub |
re.sub(pattern, repl, string) |
替换匹配到的文本 |
re.split |
re.split(pattern, string) |
split()函数在正则表达式匹配到内容后,将其他未匹配的内容分割为列表,可支持最大分割次数,类似与字符串str.split()方法。 |
re.findall |
re.findall(pattern, string) |
findall()函数匹配正则表达式,匹配所有符合条件的数据,并返回一个列表,匹配不上返回为空列表 |
re.finditer |
re.finditer(pattern, string) |
finditer()函数与 findall()函数相同,但返回的不是一个列表,而是一个迭代器。对于每一次匹配,迭代器都返回一个match匹配对象 |
re.compile |
re.compile(pattern) |
将正则表达式预编译成对象,后续使用该对象进行匹配。 |
2. 简单匹配
re模块中的search函数可在字符串中搜索匹配的模式。假设我们要在一段文本中查找 “apple”:
import re
text = "I have an apple and a banana."
match = re.search('apple', text)
if match:
print("找到了!位置是从", match.start(), "到", match.end())
# 输出:找到了!位置是从 7 到 12
这里re.search就像在文本 “大海” 里捞 “apple” 这根 “针”,一旦找到,就能告诉你它的位置。
3. 使用正则表达式
正则表达式是re模块的核心,它能定义更复杂的匹配模式。比如匹配一个简单的电话号码格式(三位数字 - 四位数字):
phone_pattern = r'\d{3}-\d{4}'
phone_text = "联系电话:123-4567"
phone_match = re.search(phone_pattern, phone_text)
if phone_match:
print("匹配到电话号码:", phone_match.group())
# 输出:匹配到电话号码: 123-4567
\d表示数字,{3}和{4}分别表示前面的字符(数字)重复 3 次和 4 次,这样就精准匹配到了目标电话号码格式。
常用功能及案例
案例 1:提取邮箱地址
实际应用中,从文本提取邮箱地址很常见。
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
text_with_email = "我的邮箱是example@domain.com,有事联系。"
email_match = re.search(email_pattern, text_with_email)
if email_match:
print("提取到的邮箱:", email_match.group())
# 输出:提取到的邮箱: example@domain.com
这个正则表达式通过\b单词边界确保匹配的是完整邮箱,[A-Za-z0-9._%+-]+匹配邮箱名部分,@后面匹配域名部分。
案例 2:替换文本
re模块的sub函数可用于替换匹配到的文本。比如将一段文本中的所有数字替换为 “X”:
text_with_nums = "我今年25岁,有3只猫。"
new_text = re.sub(r'\d+', 'X', text_with_nums)
print(new_text)
# 输出:我今年X岁,有X只猫。
\d+表示匹配一个或多个数字,re.sub将这些数字全部替换为 “X”。
案例 3:分割文本
re模块的split函数能按指定模式分割字符串。例如,按逗号和空格分割句子:
sentence = "苹果, 香蕉, 橙子, 葡萄"
fruits = re.split(r',\s*', sentence)
print(fruits)
# 输出:['苹果', '香蕉', '橙子', '葡萄']
r',\s*'这个模式表示匹配逗号及后面可能存在的 0 个或多个空格,以此来分割句子得到水果列表。
闭坑指南
正则表达式书写错误
正则表达式语法复杂,一个小错误就可能导致匹配失败。比如想匹配一个整数,错误地写成\d(只匹配一个数字),而正确的应该是\d+(匹配一个或多个数字)。
# 错误示范,只能匹配一个数字
num_pattern_wrong = r'\d'
num_text = "12345"
wrong_match = re.search(num_pattern_wrong, num_text)
if wrong_match:
print("错误匹配:", wrong_match.group())
else:
print("未匹配到完整数字")
# 输出:未匹配到完整数字
# 正确示范
num_pattern_right = r'\d+'
right_match = re.search(num_pattern_right, num_text)
if right_match:
print("正确匹配:", right_match.group())
# 输出:正确匹配: 12345
务必仔细检查正则表达式语法,可借助在线正则表达式测试工具辅助调试。
贪婪与非贪婪模式混淆
正则表达式默认是贪婪模式,会尽可能多地匹配。比如.*会匹配尽可能长的字符串。如果想匹配最短的字符串,要用非贪婪模式.*?。
html_text = "
内容1
内容2
"
# 贪婪模式,会匹配整个文本
greedy_match = re.search(r'
.*
', html_text)
if greedy_match:
print("贪婪匹配:", greedy_match.group())
# 输出:贪婪匹配:
内容1
内容2
# 非贪婪模式,只匹配第一个div内容
non_greedy_match = re.search(r'
.*?
', html_text)
if non_greedy_match:
print("非贪婪匹配:", non_greedy_match.group())
# 输出:非贪婪匹配:
内容1
忽略转义字符
在正则表达式中,一些字符有特殊含义,如\。如果要匹配这些特殊字符本身,需要进行转义。例如匹配路径中的反斜杠\,要写成\\。
path = "C:\Program Files\Python"
# 错误示范,未转义反斜杠,会导致语法错误
# path_pattern_wrong = r'C:\Program Files\Python'
# 正确示范,转义反斜杠
path_pattern_right = r'C:\\Program Files\\Python'
right_match = re.search(path_pattern_right, path)
if right_match:
print("正确匹配路径:", right_match.group())
注意特殊字符的转义,确保正确匹配。
专家工具箱
1. 分组与捕获
使用括号()可以对正则表达式进行分组,捕获匹配的内容。比如从一个包含姓名和年龄的字符串中分别提取姓名和年龄:
info_pattern = r'(\w+)\s+(\d+)'
info_text = "Alice 25"
match = re.search(info_pattern, info_text)
if match:
name = match.group(1)
age = match.group(2)
print("姓名:", name, "年龄:", age)
# 输出:姓名: Alice 年龄: 25
这里(\w+)捕获姓名,(\d+)捕获年龄,方便后续分别处理。
2. 预编译正则表达式
对于需要多次使用的正则表达式,预编译可以提高效率。
email_pattern = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
texts = ["邮箱1: test1@example.com", "邮箱2: test2@domain.net"]
for text in texts:
match = email_pattern.search(text)
if match:
print("提取到邮箱:", match.group())
re.compile将正则表达式编译成对象,后续使用该对象进行匹配比每次都使用re.search效率更高。
3. 零宽断言
零宽断言用于在不匹配具体字符的情况下,断言某个位置满足特定条件。比如匹配以数字结尾的单词,但不捕获数字:
pattern = r'\w+(?=\d)'
text = "苹果1 香蕉2 橙子"
matches = re.findall(pattern, text)
print(matches)
# 输出:['苹果', '香蕉']
(?=\d)是正向零宽断言,表示前面的单词后面必须跟着一个数字,但数字不被匹配。
小白:(眼睛放光)原来re模块这么强大,能解决这么多文本处理难题!
专家:(微笑)记住:掌握re模块,文本处理将变得高效且精准!