正则表达式(regex)是描述文本模式的特殊字符串,能快速定位符合规则的内容。
它由普通字符和元字符(如*、\d)组成,可定义匹配规则。例如\w+@\w+\.\w+能直接匹配所有邮箱。
其核心价值是高效处理文本,在数据清洗、爬虫解析等场景中,比传统方法更简洁高效。
作为跨语言技术,正则在各编程语言中都有支持,是处理文本的实用工具。
下面我们将详细讲讲正则表达式
导入库re
正则表达式是标准库,直接可以导入使用,不需要下载,其库名为re
import re
正则表达式的四个常用函数
在 Python 的re模块中,有四个函数是处理文本匹配的核心工具,覆盖了大多数文本处理场景:
1. re.search ():查找首个匹配项
功能:在整个字符串中搜索第一个符合目标的内容,返回匹配对象。
示例:
import re
text = "苹果,香蕉,橘子,香蕉"
result = re.search("香蕉", text)
print(result.group()) # 输出:香蕉
特点:找到第一个匹配后就停止搜索,适合提取首个目标。
2. re.findall ():获取所有匹配结果
功能:查找字符串中所有符合目标的内容,返回包含所有结果的列表。
示例:
fruits = re.findall("香蕉", "苹果,香蕉,橘子,香蕉")
print(fruits) # 输出:['香蕉', '香蕉']
特点:一次性返回所有匹配项,常用于批量提取同类信息。
3. re.sub ():替换匹配内容
功能:将匹配到的内容替换为指定字符串,支持简单替换逻辑。
示例:
# 用"橙子"替换"香蕉"
new_text = re.sub("香蕉", "橙子", "苹果,香蕉,橘子,香蕉")
print(new_text) # 输出:苹果,橙子,橘子,橙子
特点:能快速替换文本中的目标内容,是文本修改的常用工具。
4. re.match ():从开头匹配内容
功能:只从字符串的开头开始匹配目标内容,若开头不匹配则返回 None。
示例:
# 字符串开头是"苹果",匹配成功
result1 = re.match("苹果", "苹果,香蕉,橘子")
print(result1.group()) # 输出:苹果
# 字符串开头不是"香蕉",匹配失败
result2 = re.match("香蕉", "苹果,香蕉,橘子")
print(result2) # 输出:None
特点:仅检查字符串开头是否符合目标,适合验证字符串是否以特定内容开头。
这四个函数基本能满足日常文本处理需求,掌握它们可以解决大部分简单的文本匹配问题。
一、表示字符范围
字符范围用于指定匹配的字符集合,主要通过方括号[]
实现:
1. [abc]
:匹配 a、b、c 中任意一个字符
- 匹配逻辑:方括号内的任意单个字符
- 示例代码:
a='a1b2c3'
re.findall('[abc]', a) # 输出:['a', 'b', 'c']
- 结果分析:匹配到字符串中的 'a'、'b'、'c',跳过数字
- 实际应用:筛选特定字符,如验证验证码是否包含指定字母
2. [a-z]
:匹配任意小写字母
- 匹配逻辑:ASCII 码范围从 97 (a) 到 122 (z) 的字符
- 示例代码:
a='A1b2C3'
re.findall('[a-z]', a) # 输出:['b']
- 结果分析:仅匹配到小写字母 'b',忽略大写和数字
- 实际应用:提取文本中的英文单词
3. [0-9a-zA-Z]
:匹配数字、字母
- 匹配逻辑:组合多个字符范围
- 示例代码:
a='a1b2c3'
re.findall('[0-9a-zA-Z]',a) # 输出:['a', '1']
- 结果分析:匹配字母和数字,跳过特殊符号
- 实际应用:过滤用户名中的非法字符
4. [^abc]
:匹配除 a、b、c 外的任意字符
- 匹配逻辑:方括号内
^
表示取反 - 示例代码:
a='a1b2c3'
re.findall('[^abc]', a) # 输出:['1', '2', '3']
- 结果分析:匹配所有非 a、b、c 的字符
- 实际应用:清理文本中的干扰字符
二、表示字符串出现次数
出现次数和位置限定符用于约束匹配规则:
1. *
:前面元素出现 0 次或多次
- 匹配逻辑:贪婪匹配,尽可能多匹配
- 示例代码:
a= 'a ab abb'
re.findall('ab*',a) # 输出:['a', 'ab', 'abb']
- 结果分析:
- 第一个 'a':b 出现 0 次
- 第二个 'ab':b 出现 1 次
- 第三个 'abb':b 出现 2 次
- 实际应用:匹配可能带后缀的固定前缀
2. +
:前面元素出现 1 次或多次
- 匹配逻辑:至少出现 1 次
- 示例代码:
a='a ab abb'
re.findall('ab+', a) # 输出:['ab', 'abb']
- 结果分析:跳过单独的 'a',仅匹配带 b 的情况
- 实际应用:验证密码至少包含一个特定字符
3. ?
:前面元素出现 0 次或 1 次
- 匹配逻辑:非贪婪匹配,尽可能少匹配
- 示例代码:
a='a ab abb'
re.findall('ab?', a) # 输出:['a', 'ab', 'ab']
- 结果分析:
- 第一个 'a':b 出现 0 次
- 后两个 'abb':b 出现 1 次(非贪婪截断)
- 实际应用:处理可选参数,如 color/colour
4. {n}
:前面元素恰好出现 n 次
- 匹配逻辑:精确控制重复次数
- 示例代码:
a='aa aaa'
re.findall('a{2}', a) # 输出:['aa', 'aa']
- 结果分析:
- 'aa':完整匹配一次
- 'aaa':拆分为两个连续的 'aa'
- 实际应用:验证日期格式中的年份(如 2023)
5. {n,}
:前面元素至少出现 n 次
- 匹配逻辑:无上限的重复
- 示例代码:
a='aa aaa aaaa'
re.findall('a{2,}', a) # 输出:['aa', 'aaa', 'aaaa']
- 结果分析:匹配所有长度≥2 的连续 'a'
- 实际应用:检测连续重复字符(如密码强度)
6. {n,m}
:前面元素出现 n 到 m 次
- 匹配逻辑:区间控制重复次数
- 示例代码:
a= 'aa aaa aaaa'
re.findall('a{2,3}',a) # 输出:['aa', 'aaa', 'aaa']
- 结果分析:
- 'aaaa' 被拆分为两个 'aaa'(优先匹配最大长度)
- 实际应用:验证手机号长度(如 11 位)
7. ^
:匹配字符串开头
- 匹配逻辑:定位匹配位置
- 示例代码:
a='abc a123'
re.findall('^a', a) # 输出:['a']
- 结果分析:仅匹配第一个单词的开头 'a'
- 实际应用:验证 URL 协议(如 ^https://)
8. $
:匹配字符串结尾
- 匹配逻辑:定位匹配位置
- 示例代码:
a='abc123 a1234'
re.findall('3$', a) # 输出:['3']
- 结果分析:仅匹配完全以 '3' 结尾的字符串
- 实际应用:验证文件扩展名(如.jpg$)
三,表示同一类字符
1. \d
:匹配任意数字
- 匹配逻辑:等价于
[0-9]
,匹配单个数字字符 - 示例代码:
a = 'a1b2c3'
re.findall(r'\d', a) # 输出:['1', '2', '3']
- 结果分析:从字符串中提取所有数字字符
2. \D
:匹配任意非数字
- 匹配逻辑:等价于
[^0-9]
,匹配数字以外的任意字符 - 示例代码:
a = 'a1b2c3'
re.findall(r'\D', a) # 输出:['a', 'b', 'c']
- 结果分析:提取所有非数字字符,忽略数字
3. \w
:匹配单词字符
- 匹配逻辑:等价于
[a-zA-Z0-9_]
,匹配字母、数字、下划线 - 示例代码:
a = 'a1_b@c'
re.findall(r'\w', a) # 输出:['a', '1', '_', 'b', 'c']
- 结果分析:保留单词字符,过滤特殊符号
@
4. \W
:匹配非单词字符
- 匹配逻辑:等价于
[^a-zA-Z0-9_]
,匹配单词字符以外的字符 - 示例代码:
a = 'a1_b@c'
re.findall(r'\W', a) # 输出:['@']
- 结果分析:仅提取特殊符号,忽略单词字符
5. \s
:匹配空白字符
- 匹配逻辑:匹配空格、制表符、换行符等空白字符
- 示例代码:
a = 'a b\tc\n'
re.findall(r'\s', a) # 输出:[' ', '\t', '\n']
- 结果分析:提取所有类型的空白字符
6. \S
:匹配非空白字符
- 匹配逻辑:匹配除空白字符外的任意字符
- 示例代码:
a = 'a b\tc\n'
re.findall(r'\S', a) # 输出:['a', 'b', 'c']
- 结果分析:提取所有可见字符,忽略空白
7. \b
:匹配单词边界
- 匹配逻辑:单词字符(
\w
)与非单词字符(\W
)的交界处 - 示例代码:
a = 'cat catch copycat'
re.findall(r'\bcat\b', a) # 输出:['cat']
- 结果分析:仅匹配独立单词 "cat",排除 "catch" 和 "copycat" 中的 "cat"
8. \B
:匹配非单词边界
- 匹配逻辑:两个单词字符之间或两个非单词字符之间的位置
- 示例代码:
a = 'cat copycat'
re.findall(r'\Bcat\B', a) # 输出:[]
- 结果分析:"cat" 两侧均为单词边界,因此无匹配结果
9. .
:匹配任意字符(除换行符)
- 匹配逻辑:匹配单个任意字符,但不包括换行符
\n
- 示例代码:
a = 'abc\n123'
re.findall(r'.', a) # 输出:['a', 'b', 'c', '1', '2', '3']
- 结果分析:换行符被忽略,仅匹配可见字符
正则表达式中的贪婪与非贪婪模式
一、贪婪模式(默认行为)
核心特点:尽可能多地匹配字符,直到无法继续匹配为止。
触发符号:*
、+
、?
、{n,}
、{n,m}
等重复量词默认采用贪婪模式。
1. .*
:匹配任意字符(贪婪)
a = '<div>content</div>'
re.findall(r'<.*>', a) # 输出:['<div>content</div>']
- 匹配逻辑:
<
匹配起始标签.*
贪婪地匹配所有后续字符,直到字符串末尾>
尝试匹配,但字符串已结束,回溯直到找到最后一个>
2. \d+
:匹配数字(贪婪)
a = '123abc'
re.findall(r'\d+', a) # 输出:['123']
- 匹配逻辑:
\d+
尽可能多地匹配数字,直到遇到a
停止。
二、非贪婪模式(懒惰模式)
核心特点:尽可能少地匹配字符,一旦满足条件就停止匹配。
触发符号:在重复量词后添加 ?
,如 *?
、+?
、??
、{n,}?
、{n,m}?
。
3. .*?
:匹配任意字符(非贪婪)
a = '<div>content</div>'
re.findall(r'<.*?>', a) # 输出:['<div>', '</div>']
- 匹配逻辑:
<
匹配起始标签.*?
非贪婪地匹配最少字符,直到遇到第一个>
- 立即停止匹配,继续寻找下一个匹配项
4. \d+?
:匹配数字(非贪婪)
a = '123abc'
re.findall(r'\d+?', a) # 输出:['1', '2', '3']
- 匹配逻辑:
\d+?
每次只匹配一个数字,满足+
至少一次的要求后立即停止。
三、对比与应用场景
5. 贪婪 vs 非贪婪
a = 'ababab'
re.findall(r'a.*b', a) # 贪婪:['ababab']
re.findall(r'a.*?b', a) # 非贪婪:['ab', 'ab', 'ab']
- 结果分析:
- 贪婪模式:从第一个
a
开始,尽可能多地匹配,直到最后一个b
- 非贪婪模式:从每个
a
开始,匹配到最近的b
即停止
- 贪婪模式:从第一个
6. 实际应用:提取 HTML 标签
a = '<p>First</p><p>Second</p>'
re.findall(r'<p>.*</p>', a) # 贪婪:['<p>First</p><p>Second</p>']
re.findall(r'<p>.*?</p>', a) # 非贪婪:['<p>First</p>', '<p>Second</p>']
- 最佳实践:提取成对标签时,必须使用非贪婪模式避免过度匹配。
或和组
一、或操作符(|)
核心功能:匹配多个模式中的任意一个,优先级低于分组。
1. 基础语法
a = 'cat dog fish'
re.findall(r'cat|dog', a) # 输出:['cat', 'dog']
- 匹配逻辑:依次尝试匹配
cat
或dog
,找到即停止。
2. 跨类型匹配
a = '123 abc'
re.findall(r'\d+|abc', a) # 输出:['123', 'abc']
- 匹配逻辑:优先匹配连续数字,否则匹配
abc
。
3. 注意优先级
a = 'ab ac'
re.findall(r'a|b|c', a) # 输出:['a', 'b', 'a', 'c'](匹配单个字符)
re.findall(r'a(b|c)', a) # 输出:['ab', 'ac'](正确分组)
- 最佳实践:使用
|
时建议用括号明确分组,避免优先级问题。
二、分组(Parentheses)
核心功能:将多个字符视为一个整体,并可捕获匹配结果。
4. 基础分组
a = 'abab'
re.findall(r'(ab)+', a) # 输出:['ab']
- 匹配逻辑:将
ab
作为一个整体重复匹配,捕获最后一次出现的ab
。
5. 捕获分组
a = '2023-07-10'
re.findall(r'(\d{4})-(\d{2})-(\d{2})', a) # 输出:[('2023', '07', '10')]
- 提取技巧:通过索引访问分组结果(如
match.group(1)
返回2023
)。
6. 非捕获分组
a = 'abab'
re.findall(r'(?:ab)+', a) # 输出:['abab']
- 语法差异:
(?:...)
表示不捕获分组内容,仅用于分组。
7. 反向引用
a = 'hello world'
re.findall(r'(\w) \1', a) # 输出:['l'](匹配重复字符)
- 匹配逻辑:
\1
引用第一个分组内容,此处匹配两个连续相同字符。
三、组合应用
8. 分组与或结合
a = 'John Smith, Jane Doe'
re.findall(r'(John|Jane) (Smith|Doe)', a) # 输出:[('John', 'Smith'), ('Jane', 'Doe')]
- 匹配逻辑:匹配名和姓的任意组合。
9. 复杂分组嵌套
a = '<div class="container">Text</div>'
re.findall(r'<div(.*?)>(.*?)</div>', a) # 输出:[(' class="container"', 'Text')]
- 提取技巧:分组 1 捕获属性,分组 2 捕获内容。
四、注意事项
-
分组性能:过多嵌套分组可能影响匹配效率,非必要时使用非捕获分组。
-
交替顺序:
|
按从左到右的顺序尝试匹配,长模式应优先放置。
示例:r'https?|ftp'
比r'ftp|https?'
更高效。 -
边界处理:确保分组不遗漏必要字符。
示例:r'(a|ab)'
应改为r'(ab|a)'
以优先匹配更长模式。