python教程8、正则表达式re模块
re模块
正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串,在文本处理方面功能非常强大,也经常用作爬虫,来爬取特定内容,Python本身不支持正则,但是通过导入re模块,Python也能用正则表达式,下面就来讲一下python正则表达式的用法。正则表达式默认以单行开始匹配的
一、匹配规则
二、re的相关方法
re.findall()
findall(),可以将匹配到的结果以列表的形式返回,如果匹配不到则返回一个空列表,下面来看一下代码中的使用
1
2
3
4
5
6
7
8
|
import re<br><br> def re_method(): s1 = 'Hello, this is Joey' s2 = 'The first price is $9.90 and the second price is $100' print (re.findall(r '\w+' ,s1)) print (re.findall(r '\d+\.?\d*' ,s2)) if __name__ = = '__main__' : re_method() |
re.finditer()
可以将匹配到的结果生成一个迭代器
1
2
3
4
5
6
7
8
9
10
11
|
import re def re_method4(): # finditer s2 = 'The first price is $9.90 and the second price is $100' i = re.finditer(r '\d+\.?\d*' ,s2) for m in i: print (m.group()) if __name__ = = '__main__' : re_method4() |
re.search()
search是匹配整个字符串直道匹配到一个就返回。
1
2
3
4
5
6
7
8
9
|
import re def re_demo(): txt = 'If you puchase more than 100 sets, the price of product A is $9.90.' m = re.search(r '(\d+).*\$(\d+\.?\d*)' ,txt) print (m.groups()) if __name__ = = '__main__' : re_demo() |
re.match()
match从要匹配的字符串的开头开始,尝试匹配,如果字符串开始不符合正则表达式,则匹配失败,函数返回None,匹配成功的话用group取出匹配的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import re def re_method(): # search vs match s = 'abcdc' print (re.search(r 'c' ,s)) #search是从开头到结尾的匹配到第一个匹配的 print (re.search(r '^c' , s)) print (re.match(r 'c' ,s)) #match是开头开始匹配 print (re.match(r '.*c' , s)) def re_match_object(): # match对象 s1 = 'Joey Huang' m = re.match(r '(\w+) (\w+)' ,s1) print (m.group( 0 , 1 , 2 )) print (m.groups()) m1 = re.match(r '\w+ (\w+)' , s1) print (m1.group( 1 )) print (m1.groups()) if __name__ = = '__main__' : re_method() re_match_object() |
re.split()
split能够将匹配的子串分割后返回列表
1
2
3
4
5
6
7
8
9
|
import re def re_method1(): # split s = 'This is Joey Huang' print (re.split(r '\W' , s)) if __name__ = = '__main__' : re.method1() |
re.sub()、re.subn()
sub能将匹配到的字段用另一个字符串替换返回替换后的字符串,subn还返回替换的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import re def re_method2(): # sub s2 = 'The first price is $9.90 and the second price is $100' print (re.sub(r '\d+\.?\d*' , '<number>' ,s2, 2 )) # 还能指定替换的次数 def re_method3(): # subn s2 = 'The first price is $9.90 and the second price is $100' print (re.subn(r '\d+\.?\d*' , '<price>' ,s2)) if __name__ = = '__main__' : re_method2() re_method3() |
三、re的flags标识位
re.DOTALL
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 正则表达式默认以单行开始匹配的 import re def re_pattern_syntax(): # .表示任意单一字符 # *表示前一个字符出现>=0次 # re.DOTALL就可以匹配换行符\n,默认是以行来匹配的 print (re.match(r '.*' , 'abc\nedf' ).group()) print ( '*' * 80 ) print (re.match(r '.*' , 'abc\nedf' ,re.DOTALL).group()) if __name__ = = '__main__' : re_pattern_syntax() |
re.MULTILINE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 正则表达式默认以单行开始匹配的 import re def re_pattern_syntax1(): # ^表示字符串开头(单行) # re.MULTILINE多行匹配字符串开头 print (re.findall(r '^abc' , 'abc\nedf' )) print ( '*' * 80 ) print (re.findall(r '^abc' , 'abc\nabc' ,re.MULTILINE)) def re_pattern_syntax2(): # $表示字符串结尾 # re.MULTILINE表示行的结束 print (re.findall(r 'abc\d$' , 'abc1\nabc2' )) print ( '*' * 80 ) print (re.findall(r 'abc\d$' , 'abc1\nabc2' ,re.MULTILINE)) if __name__ = = '__main__' : re_pattern_syntax1() re_pattern_syntax2() |
?非贪婪模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import re def re_pattern_syntax4(): # greedy贪婪/non-greedy非贪婪,默认的是贪婪的匹配 s = '<H1>title</H1>' print (re.match(r '<.+>' , s).group()) #贪婪模式会匹配尽量多的匹配 print (re.match(r '<.+?>' , s).group()) #非贪婪模式匹配尽量少的匹配 print (re.match(r '<(.+)>' , s).group( 1 )) print (re.match(r '<(.+?)>' , s).group( 1 )) def re_pattern_syntax5(): # {m}/{m,}/{m,n} print (re.match(r 'ab{2,4}' , 'abbbbbbb' ).group()) #贪婪模式尽量匹配多 print (re.match(r 'ab{2,4}?' , 'abbbbbbb' ).group()) #非贪婪模式尽量匹配少 print ( '*' * 80 ) if __name__ = = '__main__' : re_pattern_syntax4() re_pattern_syntax5() |
re.I/re.IGNORECASE
1
2
3
4
5
6
7
8
9
|
import re def re_pattern_flags(): # re.I/re.IGNORECASE print (re.match(r '(Name)\s*:\s*(\w+)' , 'NAME : Joey' ,re.IGNORECASE).groups()) print ( '*' * 80 ) if __name__ = = '__main__' : re_pattern_syntax_meta_char() |
re.VERBOSE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import re def re_pattern_flags1(): # re.VERBOSE此标识位可以添加注释/re.compile s = 'the number is 20.5' r = re. compile (r ''' \d+ # 整数部分 \.? # 小数点,可能包含也可能不包含 \d* # 小数部分,可选 ''' ,re.VERBOSE) print (re.search(r,s).group()) print (r.search(s).group()) print ( '*' * 80 ) if __name__ = = '__main__' : re_pattern_syntax_meta_char1() |
四、原生字符串、编译、分组
1、原生字符串
细心的人会发现,我每一次在写匹配规则的话,都在前面加了一个r,为什么要这样写,下面从代码上来说明,
1
2
3
4
5
6
7
|
import re #“\b”在ASCII 字符中代表退格键,\b”在正则表达式中代表“匹配一个单词边界” print (re.findall( "\bblow" , "jason blow cat" )) #这里\b代表退格键,所以没有匹配到 print (re.findall( "\\bblow" , "jason blow cat" )) #用\转义后这里就匹配到了 ['blow'] print (re.findall(r "\bblow" , "jason blow cat" )) #用原生字符串后就不需要转义了 ['blow'] |
你可能注意到我们在正则表达式里使用“\d”,没用原始字符串,也没出现什么问题。那是因为ASCII 里没有对应的特殊字符,所以正则表达式编译器能够知道你指的是一个十进制数字。但是我们写代码本着严谨简单的原理,最好是都写成原生字符串的格式。
2、编译
如果一个匹配规则,我们要使用多次,我们就可以先将其编译,以后就不用每次都在去写匹配规则,下面来看一下用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import re def re_pattern_flags1(): # re.VERBOSE此标识位可以添加注释/re.compile s = 'the number is 20.5' r = re. compile (r ''' \d+ # 整数部分 \.? # 小数点,可能包含也可能不包含 \d* # 小数部分,可选 ''' ,re.VERBOSE) print (re.search(r,s).group()) print (r.search(s).group()) print ( '*' * 80 ) if __name__ = = '__main__' : re_pattern_syntax_meta_char1() |
3、分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()
表示的就是要提取的分组,可以有多个组,分组的用法很多,
记住正则分组: 去已经匹配到的数据中提取数据
re.match()有无分组比较
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 无分组 r = re.match( "h\w+" , origin) print (r.group()) # 获取匹配到的所有结果 print (r.groups()) # 获取模型中匹配到的分组结果 print (r.groupdict()) # 获取模型中匹配到的分组结果 # 有分组 # 为何要有分组?提取匹配成功的指定内容(先匹配成功全部正则,再匹配成功的局部内容提取出来) r = re.match( "h(\w+).*(?P<name>\d)$" , origin) print (r.group()) # 获取匹配到的所有结果 print (r.groups()) # 获取模型中匹配到的分组结果 print (r.groupdict()) # 获取模型中匹配到的分组中所有执行了key的组 |
re.search()有无分组比较
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 无分组 r = re.search( "a\w+" , origin) print (r.group()) # 获取匹配到的所有结果 print (r.groups()) # 获取模型中匹配到的分组结果 print (r.groupdict()) # 获取模型中匹配到的分组结果 # 有分组 r = re.search( "a(\w+).*(?P<name>\d)$" , origin) print (r.group()) # 获取匹配到的所有结果 print (r.groups()) # 获取模型中匹配到的分组结果 print (r.groupdict()) # 获取模型中匹配到的分组中所有执行了key的组 |
re.findall()有无分组比较
1
2
3
4
5
6
7
8
|
# 无分组 r = re.findall( "a\w+" ,origin) print (r) # 有分组 origin = "hello alex bcd abcd lge acd 19" r = re.findall( "a((\w*)c)(d)" , origin) print (r) |
re.split()有无分组比较
1
2
3
4
5
6
7
8
9
10
11
12
|
# 无分组 origin = "hello alex bcd alex lge alex acd 19" r = re.split( "alex" , origin, 1 ) print (r) # 有分组 origin = "hello alex bcd alex lge alex acd 19" r1 = re.split( "(alex)" , origin, 1 ) print (r1) r2 = re.split( "(al(ex))" , origin, 1 ) print (r2) |
再来一些例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
import re print (re.findall(r '(\d+)-([a-z])' , '34324-dfsdfs777-hhh' )) # [('34324', 'd'), ('777', 'h')] print (re.search(r '(\d+)-([a-z])' , '34324-dfsdfs777-hhh' ).group( 0 )) # 34324-d 返回整体 print (re.search(r '(\d+)-([a-z])' , '34324-dfsdfs777-hhh' ).group( 1 )) # 34324 获取第一个组 print (re.search(r '(\d+)-([a-z])' , '34324-dfsdfs777-hhh' ).group( 2 )) # d 获取第二个组 print (re.search(r '(\d+)-([a-z])' , '34324-dfsdfs777-hhh' ).group( 3 )) # IndexError: no such group print (re.search(r "(jason)kk\1" , "xjasonkkjason" ).group()) #\1表示应用编号为1的组 jasonkkjason print (re.search(r '(\d)gg\1' , '2j333gg3jjj8' ).group()) # 3gg3 \1表示使用第一个组\d # 下面的返回None 为什么是空?而匹配不到3gg7,因为\1的不仅表示第一组,而且匹配到的内容也要和第一组匹配到的内容相同,第一组匹配到3,第二组匹配到7 不相同所以返回空 print (re.search(r '(\d)gg\1' , '2j333gg7jjj8' )) print (re.search(r '(?P<first>\d)abc(?P=first)' , '1abc1' )) # 1abc1 声明一个组名,使用祖名引用一个组 r = re.match( '(?P<n1>h)(?P<n2>\w+)' , 'hello,hi,help' ) # 组名的另外一种用法 print (r.group()) # hello 返回匹配到的值 print (r.groups()) # ('h', 'ello')返回匹配到的分组 print (r.groupdict()) # {'n2': 'ello', 'n1': 'h'} 返回分组的结果,并且和相应的组名组成一个字典 # 分组是从已经匹配到的里面去取值 origin = "hello alex,acd,alex" print (re.findall(r '(a)(\w+)(x)' ,origin)) # [('a', 'le', 'x'), ('a', 'le', 'x')] print (re.findall(r 'a\w+' ,origin)) # ['alex', 'acd', 'alex'] print (re.findall(r 'a(\w+)' ,origin)) # ['lex', 'cd', 'lex'] print (re.findall(r '(a\w+)' ,origin)) # ['alex', 'acd', 'alex'] print (re.findall(r '(a)(\w+(e))(x)' ,origin)) # [('a', 'le', 'e', 'x'), ('a', 'le', 'e', 'x')] r = re.finditer(r '(a)(\w+(e))(?P<name>x)' ,origin) for i in r : print (i,i.group(),i.groupdict()) ''' [('a', 'le', 'e', 'x'), ('a', 'le', 'e', 'x')] <_sre.SRE_Match object; span=(6, 10), match='alex'> alex {'name': 'x'} <_sre.SRE_Match object; span=(15, 19), match='alex'> alex {'name': 'x'} ''' print (re.findall( '(\w)*' , 'alex' )) # 匹配到了alex、但是4次只取最后一次即 x 真实括号只有1个 print (re.findall(r '(\w)(\w)(\w)(\w)' , 'alex' )) # [('a', 'l', 'e', 'x')] 括号出现了4次,所以4个值都取到了 origin = 'hello alex sss hhh kkk' print (re.split(r 'a(\w+)' ,origin)) # ['hello ', 'lex', ' sss hhh kkk'] print (re.split(r 'a\w+' ,origin)) # ['hello ', ' sss hhh kkk'] |
练习
检测一个IP地址,比如说192.168.1.1,下面看下代码怎么实现的
1
2
|
c = re. compile (r '((1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.){3}(1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)' ) print (c.search( '245.255.256.25asdsa10.11.244.10' ).group()) # 10.11.244.10 245.255.256.25不符合要求所以就没有匹配出来 |
这里来解释下上面的匹配规则,先看 (1\d\d|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.),其中1\d\d表示匹配100-199的数字 | 代表或的意思,2[0-4]\d代表匹配100-249,25[0-5]代表匹配250-255,[1-9]\d|\d)代表匹配10-99和0-9,\.代表匹配一个点,{3}代表将前面的分组匹配3次,后面的一部分类似就不说明了。要匹配一个ip重要的是先理解ip每个字段的形式,然后再来写匹配规则。
常用正则表达式:
1
2
3
4
5
6
|
IP: ^( 25 [ 0 - 5 ]| 2 [ 0 - 4 ]\d|[ 0 - 1 ]?\d?\d)(\.( 25 [ 0 - 5 ]| 2 [ 0 - 4 ]\d|[ 0 - 1 ]?\d?\d)){ 3 }$ 手机号: ^ 1 [ 3 | 4 | 5 | 8 ][ 0 - 9 ]\d{ 8 }$ 邮箱: [a - zA - Z0 - 9_ - ] + @[a - zA - Z0 - 9_ - ] + (\.[a - zA - Z0 - 9_ - ] + ) + |