一、基本概念
什么是正则?
正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串组成规则的方法。
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。简单地说,正则表达式就是一门专门用于处理字符串的增强语法。
本质上是嵌入在Python中的一种微小的、高度专业化的编程语言,可通过re模块获得。
为什么要用正则?
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
- 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”)
- 可以通过正则表达式,从字符串中获取我们想要的特定部分。
正则表达式的特点是:
- 灵活性、逻辑性和功能性非常强;
- 可以迅速地用极简单的方式达到字符串的复杂控制。
二、正则的使用
常用匹配模式(元字符)
| 模 式 | 描 述 |
|---|---|
| ^ | 匹配字符串的开头 |
| $ | 匹配字符串的末尾。 |
| . | 匹配任意字符,除了换行符。当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 |
| [...] | 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k' |
| [^...] | 不在[ ]中的字符:[^abc] 匹配除了a,b,c之外的字符。 |
| * | 匹配0个或多个的表达式。 |
| + | 匹配1个或多个的表达式。 |
| ? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 |
| { n} | 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 "Bob" 中的 "o",但是能匹配 "food" 中的两个 o。 |
| { n,} | 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。"o{1,}" 等价于 "o+"。"o{0,}" 则等价于 "o*"。 |
| { n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 |
| a | b | 匹配a或b |
| (re) | 匹配括号内的表达式,也表示对正则表达式分组并记住匹配的文本 |
| \w | 匹配字母数字及下划线 |
| \W | 匹配非字母数字及下划线 |
| \s | 匹配任意空白字符,等价于 [ \t\n\r\f]。 |
| \S | 匹配任意非空字符 |
| \d | 匹配任意数字,等价于 [0-9]. |
| \D | 匹配任意非数字 |
| \A | 匹配字符串开始(开头) |
| \Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。 |
| \z | 匹配字符串结束(结尾) |
| \G | 匹配最后匹配完成的位置。 |
| \n | 匹配一个换行符。 |
| \t | 匹配一个制表符。 |
实际应用(import re)
单个简单使用:
-
\w与\W
print(re.findall('\w', 'abc 123')) # ['a', 'b', 'c', '1', '2', '3'] print(re.findall('\W', 'abc 123')) # [' '] -
\s与\S
print(re.findall('\s', 'abc 123')) # [' '] print(re.findall('\S', 'abc 123')) # ['a', 'b', 'c', '1', '2', '3'] -
\n、\t都是空,都可以被\s匹配
print(re.findall('\s', 'abc \n def \t 123')) # [' ', '\n', ' ', ' ', '\t', ' '] -
\n与\t
print(re.findall(r'\n', 'abc def \n123')) # ['\n'] print(re.findall(r'\t', 'abc def\t123')) # ['\t']上面的代码块中,“r”的作用是告诉编译器,后面引号里的内容是我指定的正则规则,而不是我用它原来的功能。==(但凡写正则最好在前加上r)==
-
\d与\D
print(re.findall('\d', 'abc 123')) # ['1', '2', '3'] print(re.findall('\D', 'abc 123')) # ['a', 'b', 'c', ' '] -
\A与\Z
print(re.findall('\Aab', 'abc 123')) # \A ==> ^ # ['ab'] print(re.findall('123\Z', 'abc 123')) # \Z ==> $ # ['123'] print(re.findall('^abc$', 'abcabc')) # [] print(re.findall('^abc$', 'abc')) # ['abc']
重复匹配:| . | * | ? | .* | .*? | + | {n,m} |
-
.(点号):匹配除了\n之外的任意一个字符,指定re.DOTALL之后才能匹配换行符
print(re.findall('a.b', 'a1b a2b a b abbbb a\nb a\tb a*b')) # ['a1b', 'a2b', 'a b', 'abb', 'a\tb', 'a*b'] print(re.findall('a.b', 'a1b a2b a b abbbb a\nb a\tb a*b', re.DOTALL)) # ['a1b', 'a2b', 'a b', 'abb', 'a\nb', 'a\tb', 'a*b'] -
*(星号):左侧字符单独重复0次或无穷次,有几个就拿几个,全都薅走,就算没有也要薅走。性格贪婪
print(re.findall('ab*', 'a ab abb abbbbbbbb bbbbbbbb')) # ['a', 'ab', 'abb', 'abbbbbbbb'] -
+(加号):左侧字符单独重复1次或无穷次,也就比星号克制那么一丢丢,没有就留着,只要有就一定要薅。性格贪婪
print(re.findall('ab+', 'a ab abb abbbbbbbb bbbbbbbb')) # ['ab', 'abb', 'abbbbbbbb'] -
?(问号):左侧字符单独重复0次或1次,比起上面俩不要脸的货可强太多了,但也不是好人啊。
print(re.findall('ab?', 'a ab abb abbbbbbbb bbbbbbbb')) # ['a', 'ab', 'ab', 'ab'] -
{n,m}:左侧字符自定重复n次到m次。
形式 解释 {0,} 相当于 * {1,} 相当于 + {0,1} 相当于 ? {n} 单独一个n代表只出现n次,多一次不行少一次也不行 print(re.findall('ab{2,5}', 'a ab abb abbb abbbb abbbbbbbb bbbbbbbb')) # ['abb', 'abbb', 'abbbb', 'abbbbb']
使用正则表达式提取出字符串中的数字(整数和小数)
print(re.findall('\d+\.?\d*', "asdfasdf123as1111111.123dfa12adsf1asdf3"))
# ['123', '1111111.123', '12', '1', '3']
问题是会有下面这种状况发生:
print(re.findall('\d+\.?\d*', "as1111111.dfa"))
# ['1111111.']
这个时候我们就可以通过对列表进行第二次正则筛选来解决。
贪婪与非贪婪
source = '<html><head><title>Title</title>'
我们要把上面的字符串中的所有html标签都提取出来,得到下面这样的一个列表
['<html>', '<head>', '<title>', '</title>']
我们很容易想到使用正则表达式 <.*>
print(re.findall(r'<.*>',source))
可是运行的结果却是下面这样:
['<html><head><title>Title</title>']
在正则表达式中,‘*’和‘+’都是贪婪地,使用他们时,会尽可能多的匹配内容,所以, <.*> 中的 星号(表示任意次数的重复),一直匹配到了 字符串最后的 </title> 里面的e。
解决这个问题,就需要使用非贪婪模式,让其尽可能少的匹配内容,也就是在星号后面加上 ? ,变成 <.*?>
运行结果:
print(re.findall(r'<.*?>', source))
# ['<html>', '<head>', '<title>', '</title>']
[ ]匹配指定字符之一
-
[ ]内单独指定、范围指定
print(re.findall('a[0-5]b', 'a1111111b a3b a4b a9b aXb a b a\nb', re.DOTALL)) # ['a3b', 'a4b'] print(re.findall('a[012345]b', 'a1111111b a3b a4b a9b aXb a b a\nb', re.DOTALL)) # ['a3b', 'a4b'] -
[ ]内联合指定,可以指定范围或符号,不支持语法
print(re.findall('a[0-9A-Z ]b', 'a1111111b a3b a4b a9b aXb a b')) # ['a3b', 'a4b', 'a9b', 'aXb', 'a b'] -
[ ]内指定a+b*,+与*就不会被当做语法处理,而是被当做互相独立的单个字符。[ ]能识别出的语法也就是“-”指定范围与“^”取反。
print(re.findall('a[a+b*]b', 'aaba+ba*babb')) # ['aab', 'a+b', 'a*b', 'abb']
也就是说:
| 模式 | 相比于[ ] |
|---|---|
| \d | 匹配任何十进制数字;这等价于类 [0-9]。 |
| \D | 匹配任何非数字字符;这等价于类 [^0-9]。 |
| \s | 匹配任何空白字符;这等价于类 [ \t\n\r\f\v]。 |
| \S | 匹配任何非空白字符;这相当于类 [^ \t\n\r\f\v]。 |
| \w | 匹配任何字母与数字字符;这相当于类 [a-zA-Z0-9_]。 |
| \W | 匹配任何非字母与数字字符;这相当于类 [^a-zA-Z0-9_]。 |
注意:[ ]内的字符,若不是指定范围,那多个字符之间就是“或者”的意思。
单行模式与多行模式
正则表达式可以设定 单行模式 和 多行模式
默认为单行模式,添加“re.M”则指定为多行模式。
^ 表示匹配文本的 开头 位置。
- 如果是
单行模式,表示匹配整个文本的开头位置。 - 如果是
多行模式,表示匹配文本每行的开头位置。
import re
content = '''001-苹果价格-60
002-橙子价格-70
003-香蕉价格-80'''
print(re.findall(r'^\d+', content))
# 单行模式下:['001']
print(re.findall(r'^\d+', content, re.M))
# 多行模式下:['001', '002', '003']
$ 表示匹配文本的 结尾 位置。
- 如果是
单行模式,表示匹配整个文本的结尾位置。 - 如果是
多行模式,表示匹配文本每行的结尾位置。
import re
content = '''001-苹果价格-60
002-橙子价格-70
003-香蕉价格-80'''
print(re.findall(r'\d+$', content))
# 单行模式下:['80']
print(re.findall(r'\d+$', content, re.M))
# 多行模式下:['60', '70', '80']
( ) 分组选择,组选择
括号称之为 正则表达式的 组选择。
组 就是把 正则表达式 匹配的内容 里面 其中的某些部分 标记为某个组。
我们可以在 正则表达式中 标记 多个 组
举个例子:
import re
content = '''苹果,苹果是绿色的
橙子,橙子是橙色的
香蕉,香蕉是黄色的'''
print(re.findall(r'^.*,', content, re.M))
# ['苹果,', '橙子,', '香蕉,']
print(re.findall(r'^(.*),', content, re.M))
# ['苹果', '橙子', '香蕉']
我们可以看到,当我们使用括号进行组选择之后,输出的内容不再带有中文逗号了。也就是说,加了括号进行组选择之后,相当于使用不加括号的规则先筛选出了内容,然后又丢弃了括号外的其他部分,只保留了组选择(括号内)的部分进行输出。
如果是进行了多组选择呢?
情况如下:
print(re.findall(r'^(.*)(,)', content, re.M))
# [('苹果', ','), ('橙子', ','), ('香蕉', ',')]
我们看到,组与组的输出结果是一起作为一个元组返回的,而这个元组的元素顺序是按分组顺序决定的。
三、使用正则表达式切割字符串
字符串 对象的 split 方法只适用于 简单的字符串分割。 有时,你需要更加灵活的字符串切割。
比如,我们需要从下面字符串中提取武将的名字。
names = '关羽; 张飞, 赵云,马超, 黄忠 李逵'
我们发现这些名字之间, 有的是分号隔开,有的是逗号隔开,有的是空格隔开, 而且分割符号周围还有不定数量的空格
这时,可以使用正则表达式里面的 split 方法:
import re
names = '关羽; 张飞, 赵云, 马超, 黄忠 李逵'
namelist = re.split(r'[;,\s]\s*', names)
print(namelist)
# ['关羽', '张飞', '赵云', '马超', '黄忠', '李逵']
正则表达式 [;,\s]\s* 指定了,分割符为 分号、逗号、空格 里面的任意一种均可,并且 该符号周围可以有不定数量的空格。
四、字符串替换
字符串 对象的 replace 方法只适应于 简单的 替换。 有时,你需要更加灵活的字符串替换。
比如,我们需要在下面这段文本中 所有的 链接中 找到所以 /avxxxxxx/ 这种 以 /av 开头,后面接一串数字, 这种模式的字符串。
然后,这些字符串全部 替换为 /cn345677/ 。
names = '''
下面是这学期要学习的课程:
<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是牛顿第2运动定律
<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是毕达哥拉斯公式
<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是切割磁力线
'''
被替换的内容不是固定的,所以没法用 字符串的replace方法。
这时,可以使用正则表达式里面的 sub 方法:
import re
names = '''
下面是这学期要学习的课程:
<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是牛顿第2运动定律
<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是毕达哥拉斯公式
<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是切割磁力线
'''
newStr = re.sub(r'/av\d+?/', '/cn345677/' , names)
print(newStr)
sub 方法就是也是替换 字符串, 但是被替换的内容 用 正则表达式来表示 符合特征的所有字符串。
比如,这里就是第一个参数 /av\d+?/ 这个正则表达式,表示以 /av 开头,后面是一串数字,再以 / 结尾的 这种特征的字符串 ,是需要被替换的。
第二个参数,这里 是 '/cn345677/' 这个字符串,表示用什么来替换。
第三个参数是源字符串。