正则表达式(末尾有大量实例)

正则表达式是什么?

处理某一类符合某种规则的字符串中所描述这些规则的工具/表达式。
个人理解:正则表达式就是使用字符,转义字符,和特殊字符等组成的一个规则,使用正则表达式对文本内容进行搜索,匹配和替换等

在线检测工具(记录)

https://deerchao.cn/tools/wegester/

元字符

1、‘ . ’匹配除了换行符以外的任意字符
2、‘\w’匹配数字,汉字,下划线,字母
3、‘\s’匹配单个空白符号或者制表符
4、‘\d’匹配阿拉伯数字
5、‘\b’匹配单词的开始和结束
6、‘^’匹配字符串的开头
7、‘$’匹配字符串的结束

转义符

查找元字符本身的时候需要用‘\’来取消元字符本身的意思,例如查找‘.’,那么就需要用. 例如:www.cn www.cn,c:\data c:\data

重复

1、* 重复0次或者多次
2、+ 重复1次或者更多次
3、?重复0次或者1次
4、{n} 重复指定n次
5、{n,}重复指定n次或者更多次
6、{n,m}重复指定n到m次
7、()子组
8、[]取值范围
9、{}匹配次数

例题1:匹配带区号的电话号码

分析:010-12345678,01012345678,(010)12345678
reg = (?0\d{2}[)-]?\d{8}
解释:首先查找括号(?可出现可不出现(0次或者1次),匹配区号中第一位0,后两位\d{2},[)-]取值范围中的)和-可以出现0或者1次。后面的电话号码\d{8}

分支条件

其实就是管道符,| 也就是表明‘或’的意思。

例题2:匹配手机号

分析:分析手机号的常规手机号段:13[0-9],14[5-9],15[0-3,5-9],16[2,5,6,7,8],17[0-8],18[0-9],19[0-3,5-9]
非常规的不在范围内
reg = ^(13[0-9]|14[5-9]|15[0-3,5-9]|16[2,5,6,7,8]|17[0-8]|18[0-9]|19[0-3,5-9])\d{8}$
解释:开头三位数字13X 和14X中间的管道符就是或者的意思

反义

1、\W 匹配非字母,数字,下划线,汉字的字符
2、\S 匹配单个的非空白符或者制表符
3、\D 匹配非阿拉伯数字的字符
4、\B 匹配非单词开头或者结束的部分
5、[^x] 匹配除了x以外的任意字符

例题:

#特殊字符(元字符): . * + ? () [] {} ^ $
var2 = '__1234love123 @ _2i4l22oveyou'
var3 = 'love123@ _2i4l22oveyou'

reg = '.' # . 代表单个任意字符,除了换行 #' '是个空格
reg = '\w*' # 匹配任意次数包含0次(只要找到了一个不符合要求的就返回前面的内容)有就有,没有拉到 #开头是空格,没匹配到内容但是匹配成功了,返回了search对象,只不过是空
reg = '\w+' # 至少匹配一次(从头到尾连续往后找,一定会找到相应内容,实在找不到就返回None)
reg = '\W+?' #拒绝贪婪匹配,就是前面的匹配规则只要达标就行 var3 #@
reg = '\w*?' #拒绝贪婪,就是前面的规则只要达成就好,*是0次也行?就ok

reg = '\w{8}' #{}代表匹配次数,是一个数字时,代表必须要匹配到该次数
reg = '\w{2,4}' #两个数字时候,表示匹配的区间次数
reg = '[a-zA-Z]' #代表取值范围 a-z和A-Z达标大小写字母
reg = '[a-zA-Z0-9_]' #等同于\w
reg = '\w{2}\d{4}\w' #__1234l var2 = '__1234love123 @ _2i4l22oveyou'

reg = '\w{2}(\d{4})\w' #()代表子组,就是在一整个匹配结果中,再单独提取一份小括号内容,获取子组内容需要使用groups()方法提取,光用search不行
res = re.search(reg,var2)
print(res.groups()) #('1234',)

模式符号(I U)

在Python的re模块中有两个符号,可以指定匹配模式

#正则的模式 re.I(不区分大小写)
varstr = 'iLOVEyou'
reg = '[a-z]{5,10}'
#如果不加模式,会返回None
res = re.search(reg,varstr,re.I) #<_sre.SRE_Match object; span=(0, 8), match='iLOVEyou'>
print(res)

re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
re.I 使匹配对大小写不敏感

例题:匹配邮箱地址

分析:格式总结:数字字母下划线组合而成的名称加各种格式的域名
1234@qq.com
wangwu@qq.com
dhah123@qq.com
wang_san@qq.com
lisi@vip.qq.com --多级域名
chang@ge.cn --企业域名
zhang.san_heuhu@gmail.com.cn --含有分隔符.的邮箱
以@符号分割,左右两边分别匹配
左边:[a-zA-Z0-9]+([.][a-zA-Z0-9]+)*
右边:@(\w+.)+[a-z]{2,6}
完整的正则表达式
reg = ‘[1]+([
.][a-zA-Z0-9]+)*@(\w+.)+[a-z]{2,6}$’

结合Python的re模块:

varmail = ['lisi@vip.qq.com','1234@qq.com','wangwu@qq.com','dhah123@qq.com','wang_san@qq.com','chang@ge.cn','zhang.san_heuhu@gmail.com.cn']
for i in  varmail:
    res = re.search(reg,i)
    if not res:
        print('邮箱地址不正确!',i)

例题:验证ip地址

分析:
ip地址的规则:
号段:0.0.0.0~255.255.255.255
先匹配0-199的范围,也就是0|1开头的
[0,1]\d{1,2}
在匹配200~255的范围,就是2开头的地址,如果第二位是0-4,那么第三位是0-9
如果第二位是5,第三位只能是0-5
2[0-4][0-9]|5[0-5]
重点在于一位一位的去匹配

reg = ‘^([0,1]?\d{1,2}|2([0-4][0-9]|5[0-5]))(.([0,1]?\d{1,2}|2([0-4][0-9]|5[0-5]))){3}$’

结合Python的re模块:

reg = '^([0,1]?\d{1,2}|2([0-4][0-9]|5[0-5]))(\.([0,1]?\d{1,2}|2([0-4][0-9]|5[0-5]))){3}$'
ip = ['192.168.1.1','0.0.0.0','255.255.255.255','256.123.123.123','127.1.1.1','127.0.0.0','255.123.123.122','8.8.8.8']
for i in ip:
    res = re.search(reg,i)
    if not res:
        print('ip地址不正确',i) #ip地址不正确 256.123.123.123
    else:
        print(res)

贪婪与懒惰

其实上面的例题中已经提到了,贪婪和拒绝贪婪

1、*? 重复任意次数,但尽可能少重复
2、+?重复一次或者更多次,尽可能少重复
3、 ?? 重复0次或者一次,尽可能少重复
4、{n,m}? 重复n到m次,尽可能少重复
5、{n,}?重复n次以上,尽可能少重复

反向引用

使用小括号制定一个子表达式后,匹配这个子表达式的文本,也就是此分组捕获的内容,可以再表达式或者其他程序中进一步的处理,这样每个分组会自动拥有一个组号,规则是从左享有,以分组的左括号为标志,第一个出现的分组号为1,第二个为2,以此类推。
反向引用长用于重复搜索前面某个分组匹配的文本,例如,\1代表分组1的匹配文本。
例如:\b(\w+)\b\s+\1\b可以来匹配重复的单词,效果:baby baby ,go go

当然,也可以指定自己的组名,(<?name>\w+)或者写成(?'name'\w+)也行,这样组名就是name,当然可以自己指定,这里只是范例
以下是常用的分组语法:

分类 代码/语法 说明
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
(?exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

零宽断言

用于查找在某些内容之前或者之后的东西,很像‘\b ,^ ,$’这样的元字符标志指定位置,这个位置应该满足一定的条件(即断言)因此成为‘零宽断言’。
“断言用来声明一个应该为真的事实,正则表达式中只有当断言为真的时候才会继续进行匹配”
个人理解:断言为真的部分都是不要的,要的是断言意外的内容!
1、(?=exp)也叫零宽度正预测先行断言,就是断言自身出现的位置的后面能匹配表达式exp
例如:I'm singing 和 you're dancing
reg = \b\w+(?=ing)\b
此时能够匹配到的内容就是sing 和 danc

2、(?<=exp)零宽度正回顾后发断言,意思是,断言自身出现的位置的前面能匹配表达式exp
例如:reading a book
reg = (?<=\b re)\w+\b
结果是 ading

例如:查找一个很长的数字中每三位要做操作,查找这样的需要操作的数字时,((?<=\d)\d{3})+\b
reg = ((?<=\d)\d{3})+\b 匹配1234567890 这10位数
结果是:234567890
如果想从左侧起每三位做操作,需要用到零宽度正预测
reg = \b((?=\d)\d{3})+ 匹配1234567890
结果是:123456789

例如:(?<=\s)\d+(?=\s)匹配了空白符间隔的数字,但不包含这些空白符
例如:一个比较复杂的例子:(?<=<(\w+)>).(?=</\1>)是匹配不包含属性的简单html标签内里的内容,(?<=<(\w+)>)代表的可能是什么的不带属性的标签,.是指匹配任意内容,(?=</\1>)里的/是转义字符的匹配应用,\1是指反向引用,引用的正是捕获的第一组,前面(\w+)匹配到的内容,这样的标签可能是这样的标签对,但整个正则表达式匹配到的内容并不包含本身。

负向零宽断言

前面提到的是怎么查找不是某个字符或者不再某个字符类里的字符方法,如果我们想要确保某个字符没有出现,但不想去匹配,这样就需要使用到负向零宽断言
例如:我们想要查找某种单词里出现了字母q,但后面跟着的不是字母u,那么我们可以这样写:\b\wq[^u]\w\b
但是以q为结尾的单词就会出错,因为[^u]总要匹配一个字符,万一匹配到q结尾后的空格或者是句号,那么\w\b就匹配下一个单词了,也就可能出现 iraq fighting
所以此时我们就可以使用负向零宽断言解决,因为它职匹配一个位置,并不消费任何字符。
那么我们此时,改一下:\b\w
q(?!u)\w*\b

1、零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。
例如:\d{3}(?!\d)也就是说,匹配三位数字,而且这三位数字后面不能是数字
例如:\b((?!abc)\w)+\b单词不包含连续字符abc
2、零宽度负回顾后发断言(?<!exp),断言此位置的前面不能匹配表达式exp
例如:(?<![a-z])\d{7}匹配前面不是小写字母的七位数字,s1 2345678 90匹配的内容

注释

小括号的另一种用途是通过语法(?#comment)来包含注释。例如:2[0-4]\d(?#200-249)|250-5|[01]?\d\d?(?#0-199)。

语法 解释
(?<= # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的内容
# (即HTML/XML标签)
) # 前缀结束
.* # 匹配任意文本
(?= # 断言要匹配的文本的后缀
</\1> # 查找尖括号括起来的内容
) # 后缀结束

平衡组/递归匹配(暂时没用到,看的云里雾里)

平衡组语法每个语言支持的不一定,支持此功能需要使用不同的语法

这里需要用到以下的语法构造:

(?'group') 把捕获的内容命名为group,并压入堆栈(Stack)
(?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
(?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
(?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败

  • 最常见的引用是匹配HTML,下面的例子是匹配嵌套的div标签:
    <div[>]*>[<>](((?'Open'<div[>]*>)[<>])+((?'-Open'</div>)[^<>])+)(?(Open)(?!))</div>
代码/语法 说明
\a 报警字符(打印它的效果是电脑嘀一声)
\b 通常是单词分界位置,但如果在字符类里使用代表退格
\t 制表符,Tab
\r 回车
\v 竖向制表符
\f 换页符
\n 换行符
\e Escape
\0nn ASCII代码中八进制代码为nn的字符
\xnn ASCII代码中十六进制代码为nn的字符
\unnnn Unicode代码中十六进制代码为nnnn的字符
\cN ASCII控制字符。比如\cC代表Ctrl+C
\A 字符串开头(类似^,但不受处理多行选项的影响)
\Z 字符串结尾或行尾(不受处理多行选项的影响)
\z 字符串结尾(类似$,但不受处理多行选项的影响)
\G 当前搜索的开头
\p Unicode中命名为name的字符类,例如\p
(?>exp) 贪婪子表达式
(?-exp) 平衡组
(?im-nsx:exp) 在子表达式exp中改变处理选项
(?im-nsx) 为表达式后面的部分改变处理选项
(?(exp)yes no)
(?(exp)yes) 同上,只是使用空表达式作为no
(?(name)yes no)
(?(name)yes) 同上,只是使用空表达式作为no

如果还有问题,就看看MSDN在线文档,快速参考下:
https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/regular-expression-language-quick-reference?redirectedfrom=MSDN

Python的re模块中关于正则表达式的部分总结:

  • 使用正则表达式的模块需要import re
    例子:
#使用正则表达式进行匹配的基本语法
import re

#定义需要匹配的字符串
var = 'iloveyou786heheda34567dhdhdhd567ui'
#定义规则正则表达式
reg = '\d\d\d'
#findall方法会按照规则在字符串中进行匹配所有符合的结果
res = re.findall(reg,var)
print(res)
#输出结果:
['786', '345', '567']
  • 由以上的例子,可以看到findall()方法,那么来探讨几种查找匹配方法
    match() search() findall()finditer()
  • match():从字符的开头进行匹配,如果目标字符串第一个字符就符合要求,匹配成功则继续匹配,否则匹配失败(不过算是尽可能少的匹配到对应字符串,重复的目标内容只匹配一次),也就是说,如果首个字符就不一致,不再匹配并返回失败的对象
    注意:这个方法匹配成功后返回match对象,可以使用group和span方法获取数据和下标区间,区间左闭右开,例如[0,5)
#reg = 'love'
# reg = 'ilove'
#使用match函数进行匹配,但意思是,如果reg的首字母和var中的字符串首字符不一样,就不再匹配
# res = re.match(reg,var)
# print(res) #None 匹配不成功,成功就是返回匹配的对象
# print(res.group()) #获取匹配的数据
# print(res.span())  #获取匹配的数据下标区间 ([0, 5)左闭右开)
import re

var = 'iloveyouilove'
#reg = 'love'
reg = 'ilove'
#使用match函数进行匹配,但意思是,如果reg的首字母和var中的字符串首字符不一样,就不再匹配
res = re.match(reg,var)
print(res) #None 匹配不成功,成功就是返回匹配的对象
print(res.group()) #获取匹配的数据
print(res.span())  #获取匹配的数据下标区间 ([0, 5)左闭右开)
#输出结果
#<_sre.SRE_Match object; span=(0, 5), match='ilove'>
#ilove
#(0, 5)
  • search()方法用法是一样的,不举例,只说说规则
  • 从字符的开头你的开头进行搜索式的匹配
  • 匹配成功则返回match对象,匹配失败则返回none
  • 成功后可以使用group和span方法获取数据和下标区间

下面来讨论,search和match的区别:
返回的match对象,都有匹配到内容,但search方法中第一字符不符合要求,就不继续搜索了,而serach方法是搜索式匹配,直到匹配结束没有再返回None,有的话直返回第一个。

import re

var = 'iloveyouilove'
#reg = 'love'
reg = 'ilove'
#使用match函数进行匹配,但意思是,如果reg的首字母和var中的字符串首字符不一样,就不再匹配
res = re.match(reg,var)
print(res) #None 匹配不成功,成功就是返回匹配的对象
print(res.group()) #获取匹配的数据
print(res.span())  #获取匹配的数据下标区间 ([0, 5)左闭右开)


var1 = 'loveyouilove'
#reg = 'love'
reg = 'ilove'
#使用match函数进行匹配,但意思是,如果reg的首字母和var中的字符串首字符不一样,就不再匹配
#res = re.match(reg,var)
res = re.search(reg,var)
print(res) #None 匹配不成功,成功就是返回匹配的对象
print(res.group()) #获取匹配的数据
print(res.span())  #获取匹配的数据下标区间 ([0, 5)左闭右开)

#输出结果:
<_sre.SRE_Match object; span=(0, 5), match='ilove'>
ilove
(0, 5)
<_sre.SRE_Match object; span=(0, 5), match='ilove'>
ilove
(0, 5)

下面来看,搜索失败的时候,返回内容的不同:

import re

var = 'loveyouilove'
#reg = 'love'
reg = 'ilove'
#使用match函数进行匹配,但意思是,如果reg的首字母和var中的字符串首字符不一样,就不再匹配
res = re.match(reg,var)
print(res) #None 匹配不成功,成功就是返回匹配的对象
print(res.group()) #获取匹配的数据
print(res.span())  #获取匹配的数据下标区间 ([0, 5)左闭右开)

#输出结果:
  None
Traceback (most recent call last):
  File "/Users/kiki/Desktop/pythonProject/bugs/venv/1111111.py", line 9, in <module>
    print(res.group()) #获取匹配的数据
AttributeError: 'NoneType' object has no attribute 'group'


所以,通过以上代码可以看出,search方法是从字符串开头进行搜索式匹配,而match方法是从字符串开头进行匹配,如果第一个字符不符合,后面即使能够匹配到目标内容也就返回None了
其他的区别,看别的博主写过效率,在执行同一个匹配规则时,内存和执行用时,match更好一些,但两者相差不多。

  • findall():按照正则表达式在目标内容中进行搜索式匹配所有符合规则的元素,找到结果返回一个列表,如果没有找到返回[]
var = 'iloveyouyouyouyou'
reg = 'love'
res = re.findall(reg,var)
print(res) #可以输出结果,但如果没有匹配到,就返回[]
  • finditer():匹配规则和findall是一样的,但结果是由match对象组成的迭代器
res = re.finditer(reg,var)
print(res) #<callable_iterator object at 0x7fa605ec3eb8>

其他的方法sub()和split()和compile()

  • sub(pattern,reapl,string):按照正则表达式的规则进行搜索匹配要替换的字符串,完成替换的功能,源字符串不被改变
'''
参数解读:
pattern正则表达式的规则,匹配需要被替换的字符
reapl:替换后的字符
string:原始字符串
'''
import re

var = 'iloveyou786heheda34567dhdhdhd567ui iloveyou786heheda34567dhdhdhd567ui'
reg = 'love'
res= re.sub(reg,'aaa',var)
print(res,var) #iaaayou786heheda34567dhdhdhd567ui iloveyou786heheda34567dhdhdhd567ui
  • re.split()按照指定的正则规则进行数据分割
vars = 'hello my name uis hanfu'
res = re.split(' ',vars)
print(res) #['hello', 'my', 'name', 'uis', 'hanfu']
  • compile()可以直接将正则表达式定义为正则对象,使用正则对象直接操作
arr = ['i love you 123','i love you 234','i love you 145','i love you 789']
reg = re.compile('\d{3}') #compile正则对象
for i in arr:
    # reg = '\d{3}'
    # res = re.search(reg,i)
    # print(res.group())
    #正则对象直接调用search方法,性能和效率会变高
    res = reg.search(i).group()
    print(res)
#输出结果:
#123 234 145 789

  1. a-zA-Z0-9 ↩︎

posted @ 2021-07-10 19:31  ST_T  阅读(934)  评论(0编辑  收藏  举报