- 我们经常在写代码的时候,想要获取指定元素,一个万能的方法就是使用正则表达式来提取。但是对于正则表达式很多人觉得很复杂,各种符合看的都头晕,再加上网上很多资料都没系统介绍,就更加搞不懂了。因此我想写这一篇关于正则表达式的文章,来将这个看起来很高大上的东西尽量给大家说清道明。
首先,正则表达式不是Python独有的,很多语言都有正则表达式,它是一个处理字符串的强大工具,有它自己的语法结构,在Python中,自带的有专门的正则表达式模块,即re模块。
- 在讲正则表达式的语法结构之前,我们先来看下关于正则表达式的一些常用的匹配规则
模式 | 描述 |
---|---|
\w | 匹配字母,数字及下划线 |
\W | 匹配非字母,数字及下划线的字符 |
\s | 匹配任意空白字符,等价于[\t\n\r\f] |
\S | 匹配任意非空字符 |
\d | 匹配任意数字,即[0-9] |
\D | 匹配任意非数字的字符 |
\A | 匹配字符串的开头 |
^ | 匹配一行字符串的开头 |
$ | 匹配一行字符串的结尾 |
. | 匹配除换行符外的任意字符 |
* | 匹配0个或多个表达式 |
+ | 匹配1个或多个表达式 |
? | 匹配0次或1次前面的表达式,若要匹配?则用?表示 |
() | 匹配括号中的表达式,也表示一个组 |
- 匹配方法 Python中常用的正则表达式的方法有3种,分别为:
match():从字符串的开头开始匹配,若匹配则返回匹配成功的结果,若不匹配则返回None
search():通过扫描整个字符串来进行匹配,若匹配成功则返回第一个匹配的结果,若不匹配则返回None
findall():通过扫描整个字符串来进行匹配,如果匹配成功则将所有匹配的结果返回,若未成功匹配则返回None
接下来我们以match()方法来进行实例操作。
在匹配方法中,如match(),第一个参数传入正则表达式,第二个参数传入要匹配的字符串。search()和findall()方法也是一样。
即 re.match(正则表达式,待匹配的字符串)
- 例1 匹配规则的简单运用:
import re
data = 'zhangsan 1234567 beijingshi '
result = re.match('^zhangsan\s\d{7}\s\w{10}', data)
print(result)
上例中关于字符串的正则表达式可以写为:
^zhangsan\s\d{7}\s\w{10}
根据匹配规则,开头字符串为”zhangsan",因此可以写为‘zhangsan’(或者是z也行);后面是空格,则用’\s’来匹配,再后面是数字,用’\d’来匹配,由于有7个数字,因此可将‘\d\d\d\d\d\d\d’缩写成’\d{7}’,后面继续是空格,用’\s’匹配,最后是10个字符,可用’\w{10}'来匹配。
上述程序的运行结果为:
/Users/Desktop/Code/test/text2.py
<_sre.SRE_Match object; span=(0, 27), match='zhangsan 1234567 beijingshi'>
Process finished with exit code 0
表示匹配成功。
- 例2 匹配并提取目标数据:
import re
data = 'name:zhangsan tel:1234567 addr:beijingshi '
result = re.match('^name:(\w{8})\stel:(\d{7})\saddr:(\w{10})', data)
print(result)
print(result.group())
print(result.group(1))
print(result.group(2))
print(result.group(3))
运行结果为:
/Users/Desktop/Code/test/text2.py
<_sre.SRE_Match object; span=(0, 41), match='name:zhangsan tel:1234567 addr:beijingshi'>
name:zhangsan tel:1234567 addr:beijingshi
zhangsan
1234567
beijingshi
Process finished with exit code 0
如上述例子所示,当我们想要获取字符串中某些字符的时候,我们可以用()括号将想要提取的字符括起来,()括号实际上标识了一个子字符串的起始位置和结束位置,被标记的每个子字符串会一次对应每一个组,然后我们可以调用group()方法然后传入分组的索引即可获取指定的子字符串,如果不加索引,则输出整个匹配的结果。
注意:索引是从1开始的,不是从0开始。
- 例3: 通用匹配
通过之前两个例子我们发现,如果根据每个字符一一对应的关系去写正则表达式,这样写会很麻烦,并且写多了有时候看的也头晕。因此我们接下来介绍一种通用的写法,它会使正则表达式写起来简单明了很多,那就是
.* (点星) 根据之前的匹配规则,我们发现
.(点)是匹配除换行符外的任意字符,*(星)是代表匹配前面的字符无限次,因此将他们组合在一起就可以匹配任意字符了。
下面我们将例1中的正则表达式做一下修改: 将正则表达式写为:^z.*shi
import re
data = 'zhangsan 1234567 beijingshi'
result = re.match('^z.*shi', data)
print(result)
运行结果为:
/Users/Desktop/Code/test/text2.py
<_sre.SRE_Match object; span=(0, 27), match='zhangsan 1234567 beijingshi'>
Process finished with exit code 0
我们发现完全可以正确匹配,但是正则表达式精简了很多,我们可以将一些不重要的匹配信息都可以用.*来代替。
通过例3我们知道了一种通用匹配规则,用它可以简化很多正则表达式的写法,那么是不是这个方法就是一个万能的呢?我们接下来了看一下例4.
- 例4 贪婪匹配:
import re
data = 'name:zhangsan tel:1234567 addr:beijingshi'
result = re.match('^n.*(\d+).*', data)
print(result)
print(result.group(1))
我们想要获取data中的tel的值,即获取’1234567’,根据通用匹配规则,我么将正则表达式写为:
^n.(\d+).
其中^n是匹配开头,由于要匹配的是多个数字,则将需要匹配的数字正则表达式写为(\d+),其余的都用通用匹配规则.*来表示。
我们来看一下运行结果:
/Users/Desktop/Code/test/text2.py
<_sre.SRE_Match object; span=(0, 41), match='name:zhangsan tel:1234567 addr:beijingshi'>
7
Process finished with exit code 0
根据运行结果,我们发现我们已经成功匹配到了该字符串,但是输出到结果并不是我们想要的‘1234567’,输出的结果只有一个数字’7’,这是为什么呢?
这里就涉及到贪婪匹配与非贪婪匹配到问题了。.*是贪婪匹配,它会尽可能匹配多的字符(比较贪婪)。正则表达式中.*后面是(\d+),+表示的是匹配一个或多个表达式,\d+表示匹配一个或多个数字(至少1个),但是由于没有具体说明要匹配多少个数字,前面的.*就会一直匹配,直到满足\d+的最低匹配条件(匹配一个数字),因此前面的.将数字‘123456’都给匹配了,留给(\d+)的就只剩下一个’7’了,因此最终运行结就只有数字’7’了。
那么避免出现这种情况的方法就是采用非贪婪匹配的方式。非贪婪匹配的写法就是在贪婪匹配的后面加一个?问号,即.?(点星问号)
将上述代码中贪婪匹配换成非贪婪匹配后,我们看一下结果是怎样的。
import re
data = 'name:zhangsan tel:1234567 addr:beijingshi'
result = re.match('^n.*?(\d+).*', data)
print(result)
print(result.group(1))
运行结果:
/Users/Desktop/Code/test/text2.py
<_sre.SRE_Match object; span=(0, 41), match='name:zhangsan tel:1234567 addr:beijingshi'>
1234567
Process finished with exit code 0
采用非贪婪匹配后,发现能正常匹配到我们想要的数据了。
非贪婪匹配则是匹配尽可能少的字符,上例中当.?匹配到tel后面的冒号时,再往后就是数字了,恰好可以跟\d+匹配,那么.?则停止匹配,剩下的交给\d+去匹配后面的数字。因此我们可以得到想要的‘1234567’了。
在做匹配的时候,我们是不是只采用非贪婪匹配就“万事大吉”了呢?我们接下来看一下例5。
- 例5: 我们想要获取addr后面的值,即‘beijingshi’
import re
data = 'name:zhangsan tel:1234567 addr:beijingshi'
result1 = re.match('^n.*?addr:(.*?)', data)
result2 = re.match('^n.*?addr:(.*)', data)
print(result1)
print('result1 ',result1.group(1))
print('result2 ',result2.group(1))
运行结果为:
/Users/Desktop/Code/test/text2.py
<_sre.SRE_Match object; span=(0, 31), match='name:zhangsan tel:1234567 addr:'>
result1
result2 beijingshi
Process finished with exit code 0
我们发现,当采用非贪婪匹配时,无法匹配到结果,返回值为空。
因此当遇到需要匹配的值位于字符串结尾的时候,非贪婪匹配将无法匹配到所需要的值,我们需要用贪婪匹配的方式去进行匹配;当需要匹配的值在字符串中间的时候,我们尽量优先使用非贪婪匹配的方式进行匹配。
-
修饰符
在上文中我们讲了两个很重要的两个通用匹配方法,贪婪匹配和非贪婪匹配。但是这两种匹配方式并不是所有字符串都能匹配,因为无论是贪婪(.)还是非贪婪(.?)匹配表达式中都由.(点)来表示任意字符,然而我们在匹配规则中也说明了.(点)可以匹配任意字符,但是换行符除外。因此当我们需要匹配的字符串包含换行符的时候,我么就无法直接使用通用匹配方法了。为了解决这个问题,我们可以用修饰符来修正这个问题。
常用修饰符:re.S和re.I (大写i)
re.s : 使.(点)匹配包括换行符在内的所有字符
re.I : 使匹配对大小写不敏感
使用时的写法:
re.match(正则表达式,待匹配的字符串,re.S)
re.match(正则表达式,待匹配的字符串,re.I) -
search()方法的简单介绍
search()方法的使用与match()方法的使用一模一样,但是它不同从头开始匹配,可以直接匹配中间的值。
例:
import re
data = '''
name:zhangsan
tel:1234567
addr:beijingshi
'''
result1 = re.search('t.*?(\d+)', data, re.S)
result2 = re.match('t.*?(\d+)', data, re.S)
print(result1)
print('result1 ',result1.group(1))
print('result2 ',result2)
结果:
/Users/Desktop/Code/test/text2.py
<_sre.SRE_Match object; span=(24, 35), match='tel:1234567'>
result1 1234567
None
Process finished with exit code 0
在上例中,我们用search()方法没有从整个字符串的开头name开始匹配,而是直接从中间tel开始匹配,结果是匹配成功,输出了我们想要的值。但是我们用match()方法这样操作时,发现无法成功匹配,返回了None。match()更适合于用来检测某个字符串是否符合某个正则表达式的规则。
- findall()
findall()方法与search()方法一样,都是通过扫描整个字符串进行匹配。但是search()只返回匹配成功的第一个值,后面的值不会返回;而findall()是返回所有匹配成功的值。
关于Python的正则表达式的基本运用就讲这么多了,希望各位看过后都能对正则表达式有一个比较清晰的了解。若对各位有帮助的话记得帮忙点个赞👍哦~