1 Python正则表达式介绍
如果大家在工作中需要频繁的处理文本(或者称字符串),例如使用爬虫来爬取网页的信息,那么正则表达式一定会成为你最好的帮手。
正则表达式是一个十分强大的字符串处理工具,几乎任何关于字符串的操作都可以使用正则表达式来完成。当然正则表达式可以在许多门语言中使用,但是大同小异,几乎函数的功能都差不多(只不过换了个名字)。本文主要介绍Python中正则表达式。
下面是一些基本介绍:
- 在Python中几乎所有的正则表达式操作都需要调用一个模块,那就是强大的re模块,是Python的一个标准库。
- 使用反斜杠
\
来对特殊字符进行转义。 - 不过最好使用
'r'
来直接使用原生字符串表示法(raw string notation)。
在网上有许多相关的详细教程,最详细的是Python的re模块官方教程,当然也其他的一些教程,例如正则表达式详解等。本文是在这些教程的基础上进行整合,提取出最为实用的一些使用方法。
2 元字符
对与正则表达式来说包含了特殊字符和普通字符,多数的普通字符(例如'A','a','0'
)就是一个最简单的正则表达式。它们可以匹配自身。当然,我们可以将这些字符串联起来,来匹配单词等。而一些特殊的字符会代替一类的普通的字符,或者对其周围的解释方法进行影响。
首先介绍一下元字符:
character | mean |
---|---|
. | 匹配任意字符(不包括换行符) |
^ | 匹配开始位置,多行模式下匹配每一行的开始 |
$ | 匹配结束位置,多行模式下匹配每一行的结束 |
* | 匹配前一个元字符0到多次 |
+ | 匹配前一个元字符1到多次 |
? | 匹配前一个元字符0到1次 |
{m,n} | 匹配前一个元字符m到n次 |
\\ | 转义字符,跟在其后的字符将失去作为特殊元字符的含义,例如\\ .只能匹配.,不能再匹配任意字符 |
[] | 字符集,一个字符的集合,可匹配其中任意一个字符 |
| | 逻辑表达式:或(比如a|b 代表可以匹配a 或b ) |
(…) | 分组,默认为捕获,即被分组的内容可以被单独取出,默认每个分组有个索引,从 1 开始,按照"("的顺序决定索引值 |
(?iLmsux) | 分组中可以设置模式,iLmsux之中的每个字符代表一个模式,用法参见3.1 模式I |
(?:…) | 分组的不捕获模式,计算索引时会跳过这个分组 |
(?P |
分组的命名模式,取此分组中的内容时可以使用索引也可以使用name |
(?P=name) | 分组的引用模式,可在同一个正则表达式用引用前面命名过的正则 |
(?#…) | 注释,不影响正则表达式其它部分,参见3.1 模式I |
(?=…) | 顺序肯定环视,表示所在位置右侧能够匹配括号内正则 |
(?!..) | 顺序否定环视,表示所在位置右侧不能匹配括号内正则 |
(?<=…) | 逆序肯定环视,表示所在位置左侧能够匹配括号内正则 |
(?<!..) | 逆序否定环视,表示所在位置左侧不能匹配括号内正则 |
(?(id/name)yes|no) | 若前面指定id或name的分区匹配成功则执行yes处的正则,否则执行no处的正则 |
\number | 匹配和前面索引为number的分组捕获到的内容一样的字符串 |
\A | 匹配字符串开始位置,忽略多行模式 |
\Z | 匹配字符串结束位置,忽略多行模式 |
\b | 匹配位于单词开始或结束位置的空字符串 |
\B | 匹配不位于单词开始或结束位置的空字符串 |
\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_] |
3 模式
3.1 模式 I
I模式(IGNORECASE),即忽略大小写的匹配模式, 样例如下:
1 | import re |
3.2 模式 L
L模式(LOCALE),字符集本地化。这个功能是为了支持的多语言版本的字符集使用环境的,比如在转义符\w
,在英文环境下,代表[a-zA-Z0-9_](所有的数字、字母、下划线),如果在一个法语环境下使用,缺省设置下,不能匹配"é"
或"ç"
。加上这L选项和就可以匹配了。不过这个对于中文环境似乎没有什么用,它仍然不能匹配中文字符。
3.3 模式 M
M模式(MULTILINE),多行模式,改变^
和$
的行为
1 | s = '''first line |
3.4 模式 S
S模式(DOTALL),此模式下'.'
的匹配不受限制,可匹配任何字符,包括换行符
1 | s = '''first line |
3.5 模式 X
X模式(VERBOSE),冗余模式,此模式忽略正则表达式中的空白和#号的注视,例如写一个匹配邮箱的正则表达式
1 | email_regex = re.compile("[\w+\.]+@[a-zA-Z\d]+\.(com|cn)") |
3.6 模式 U
U模式(UNICODE),使用\w,\W,\b,\B这些元字符时将按照UNICODE定义的属性。
3.7 其他
正则表达式的模式是可以同时使用多个的,在Python里面使用按位或运算符|
同时添加多个模式,如:
1 | re.compile('', re.I|re.M|re.S) |
4 函数
Python的re模块有许多方便的函数来使用正则表达式来操作字符。这些函数需要 我们熟练掌握。
-
compile(pattern, flags=0)
输入:正则表达式pattern,模式 flags(默认0,即不使用任何模式)。
输出:SRE_Pattern对象。1
2
3regex = re.compile(".+")
print(type(regex))
# output> <_sre.SRE_Pattern object at 0x00000000026BB0B8>这个对象可以调用其他函数来完成匹配,一般来说推荐使用 compile 函数预编译出一个正则模式之后再去使用,这样在后面的代码中可以很方便的复用它,当然大部分函数也可以不用 compile 直接使用,具体见 findall 函数。
1
2
3
4
5
6
7
8
9
10
11s = '''first line
second line
third line'''
#
regex = re.compile(".+")
# 调用 findall 函数
print(regex.findall(s))
# output> ['first line', 'second line', 'third line']
# 调用 search 函数
print(regex.search(s).group())
# output> first lin -
escape(pattern)
输入:正则表达式pattern。
输出:转义的正则表达式。
转义,如果文本中含有正则的元子符,你在写正则的时候需要使用转义的\
来进行匹配,当存在很多的时候会显得比较乱。这时便可以使用这个函数将其转义。1
2
3
4
5
6
7
8
9
10
11s = ".+\d123"
#
regex_str = re.escape(".+\d123")
# 查看转义后的字符
print regex_str
# output> \.\+\\d123
# 查看匹配到的结果
for g in re.findall(regex_str, s):
print g
# output> .+\d123 -
findall(pattern, string, flags=0)
输入:正则表达式pattern,待操作的字符串string,模式flags(默认0)。
输出:返回一个list(string中所有匹配的正则表达式的子串),如果没有匹配到任何子串,返回一个空列表。1
2
3
4
5
6
7
8
9
10
11
12s = '''first line
second line
third line'''
# compile 预编译后使用 findall
regex = re.compile("\w+")
print(regex.findall(s))
# output> ['first', 'line', 'second', 'line', 'third', 'line']
# 不使用 compile 直接使用 findall
print(re.findall("\w+", s))
# output> ['first', 'line', 'second', 'line', 'third', 'line'] -
finditer(pattern, string, flags=0)
输入:正则表达式pattern,待操作的字符串string,模式flags(默认0)。
输出:返回一个迭代器(每次迭代返回的是SRE_Match)。
作用同findall,但是返回迭代器,且返回SRE_Match对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15s = '''first line
second line
third line'''
regex = re.compile("\w+")
print(regex.finditer(s))
# output> <callable-iterator object at 0x0000000001DF3B38>
for i in regex.finditer(s):
print(i)
# output> <_sre.SRE_Match object at 0x0000000002B7A920>
# <_sre.SRE_Match object at 0x0000000002B7A8B8>
# <_sre.SRE_Match object at 0x0000000002B7A920>
# <_sre.SRE_Match object at 0x0000000002B7A8B8>
# <_sre.SRE_Match object at 0x0000000002B7A920>
# <_sre.SRE_Match object at 0x0000000002B7A8B8> -
match(pattern, string, flags=0)
输入:正则表达式pattern,待操作的字符串string,模式flags(默认0)。
输出:返回一个SRE_Match对象。
使用指定正则去待操作字符串中寻找可以匹配的子串,返回匹配上第一个字串,并且不再继续找,需要注意的是match函数是从字符串开始处开始查找的,如果开始处不匹配,则不再继续。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16s = '''first line
second line
third line'''
# compile
regex = re.compile("\w+")
m = regex.match(s)
print(m)
# output> <_sre.SRE_Match object at 0x0000000002BCA8B8>
print(m.group())
# output> first
# s 的开头是 "f", 但正则中限制了开始为 i 所以找不到
regex = re.compile("^i\w+")
print(regex.match(s))
# output> None -
purge()
当你在程序中使用 re 模块,无论是先使用 compile 还是直接使用比如 findall 来使用正则表达式操作文本,re 模块都会将正则表达式先编译一下, 并且会将编译过后的正则表达式放到缓存中,这样下次使用同样的正则表达式的时候就不需要再次编译, 因为编译其实是很费时的,这样可以提升效率,而默认缓存的正则表达式的个数是 100, 当你需要频繁使用少量正则表达式的时候,缓存可以提升效率,而使用的正则表达式过多时,缓存带来的优势就不明显了。此函数是清除缓存中的正则表达式(优化内存)。 -
search(pattern, string, flags=0)
输入:正则表达式pattern,待操作的字符串string,模式flags(默认0)。
输出:返回一个SRE_Match对象。
类似match,但不限制正则表达式的开始匹配位置。1
2
3
4
5
6
7
8
9
10
11
12
13
14s = '''first line
second line
third line'''
# 需要从开始处匹配 所以匹配不到
print(re.match('i\w+', s))
# output> None
# 没有限制起始匹配位置
print(re.search('i\w+', s))
# output> <_sre.SRE_Match object at 0x0000000002C6A920>
print(re.search('i\w+', s).group())
# output> irst -
split(pattern, string, maxsplit=0, flags=0)
输入:正则表达式pattern,待操作的字符串string,切分次数maxsplit(默认0),模式flags(默认0)。
输出:返回一个切分后子串的list,如果匹配不到,则返回一个list(list中是原字符串)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15s = '''first 111 line
second 222 line
third 333 line'''
# 按照数字切分
print(re.split('\d+', s))
# output> ['first ', ' line\nsecond ', ' line\nthird ', ' line']
# \.+ 匹配不到 返回包含自身的列表
print(re.split('\.+', s, 1))
# output> ['first 111 line\nsecond 222 line\nthird 333 line']
# maxsplit 参数
print(re.split('\d+', s, 1))
# output> ['first ', ' line\nsecond 222 line\nthird 333 line'] -
sub(pattern, repl, string, count=0, flags=0)
输入:正则表达式pattern,替换的字符串repl,待操作的字符串string,最大替换次数count(默认0),模式flags(默认0)。
输出:返回一个将正则表达式匹配到的字符串被替换成repl的字符串。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
46
47
48
49
50
51
52
53s = "the sum of 7 and 9 is [7+9]."
# 基本用法 将目标替换为固定字符串
print re.sub('\[7\+9\]', '16', s)
# output> the sum of 7 and 9 is 16.
# 高级用法 1 使用前面匹配的到的内容 \1 代表 pattern 中捕获到的第一个分组的内容
print re.sub('\[(7)\+(9)\]', r'\2\1', s)
# output> the sum of 7 and 9 is 97.
# 高级用法 2 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象
def replacement(m):
p_str = m.group()
if p_str == '7':
return '77'
if p_str == '9':
return '99'
return ''
print re.sub('\d', replacement, s)
# output> the sum of 77 and 99 is [77+99].
# 高级用法 3 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象 增加作用域 自动计算
scope = {}
example_string_1 = "the sum of 7 and 9 is [7+9]."
example_string_2 = "[name = 'Mr.Gumby']Hello,[name]"
def replacement(m):
code = m.group(1)
st = ''
try:
st = str(eval(code, scope))
except SyntaxError:
exec code in scope
return st
# 解析: code='7+9'
# str(eval(code, scope))='16'
print re.sub('\[(.+?)\]', replacement, example_string_1)
# output> the sum of 7 and 9 is 16.
# 两次替换
# 解析1: code="name = 'Mr.Gumby'"
# eval(code)
# raise SyntaxError
# exec code in scope
# 在命名空间 scope 中将 "Mr.Gumby" 赋给了变量 name
# 解析2: code="name"
# eval(name) 返回变量 name 的值 Mr.Gumby
print re.sub('\[(.+?)\]', replacement, example_string_2)
# output> Hello,Mr.Gumby -
subn(pattern, repl, string, count=0, flags=0)
输入:正则表达式pattern,替换的字符串repl,待操作的字符串string,最大替换次数count(默认0),模式flags(默认0)。
输出:返回一个元组,第一个值是将正则表达式匹配到的字符串被替换成repl的字符串;第二个值是发生替换的次数。(整体同sub) -
template(pattern, flags=0)
这个吧,咋一看和 compile 差不多,不过不支持 +、?、*、{} 等这样的元字符,只要是需要有重复功能的元字符,就不支持,查了查资料,貌似没人知道这个函数到底是干嘛的…
5 内置对象
-
SRE_Pattern:此对象是编译后的正则表达式,编译后可以复用、提升效率、也能获取一些其他的关于正则表达式的信息(如下所示)。
属性:name means flags 编译时指定的模式 groupindex 以正则表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内 groups 正则表达式中分组的数量 pattern 编译时用的正则表达式 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25s = 'Hello, Mr.Gumby : 2016/10/26'
p = re.compile('''(?: # 构造一个不捕获分组 用于使用 |
(?P<name>\w+\.\w+) # 匹配 Mr.Gumby
| # 或
(?P<no>\s+\.\w+) # 一个匹配不到的命名分组
)
.*? # 匹配 :
(\d+) # 匹配 2016
''', re.X)
#
print p.flags
# output> 64
print p.groupindex
# output> {'name': 1, 'no': 2}
print p.groups
# output> 3
print p.pattern
# output> (?: # 构造一个不捕获分组 用于使用 |
# (?P<name>\w+\.\w+) # 匹配 Mr.Gumby
# | # 或
# (?P<no>\s+\.\w+) # 一个匹配不到的命名分组
# )
# .*? # 匹配 :
# (\d+) # 匹配 2016函数:可使用 findall、finditer、match、search、split、sub、subn 等函数
-
SRE_Match:这个对相对保存本次匹配的结果,包含很多关于匹配过称以及匹配结果的信息。
属性:name means endpos 本次搜索结束位置索引 lastgroup 本次搜索匹配到的最后一个分组的别名 lastindex 本次搜索匹配到的最后一个分组的索引 pos 本次搜索开始位置索引 re 本次搜索使用的 SRE_Pattern 对象 regs 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置 string 本次搜索操作的字符串 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
30s = 'Hello, Mr.Gumby : 2016/10/26'
m = re.search(', (?P<name>\w+\.\w+).*?(\d+)', s)
# 本次搜索的结束位置索引
print(m.endpos)
# output> 28
# 本次搜索匹配到的最后一个分组的别名
# 本次匹配最后一个分组没有别名
print(m.lastgroup)
# output> None
# 本次搜索匹配到的最后一个分组的索引
print(m.lastindex)
# output> 2
# 本次搜索开始位置索引
print(m.pos)
# output> 0
# 本次搜索使用的 SRE_Pattern 对象
print(m.re)
# output> <_sre.SRE_Pattern object at 0x000000000277E158>
# 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置 第一个元组为正则表达式匹配范围
print(m.regs)
# output> ((7, 22), (7, 15), (18, 22))
# 本次搜索操作的字符串
print(m.string)
# output> Hello, Mr.Gumby : 2016/10/26函数:
- end([group=0]) 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引
- expand(template) 根据模版返回相应的字符串,类似与sub函数里面的repl,可使用\1或者\g
来选择分组 - group([group1, …]) 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组
- groupdict([default=None]) 返回 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key为组名,value为匹配到的内容,参数default为没有参与本次匹配的命名分组提供默认值
- groups([default=None])以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为default
- span([group]) 返回指定分组的起止位置组成的元组,默认返回由start()和end()组成的元组
- start([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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40s = 'Hello, Mr.Gumby : 2016/10/26'
m = re.search('''(?: # 构造一个不捕获分组 用于使用 |
(?P<name>\w+\.\w+) # 匹配 Mr.Gumby
| # 或
(?P<no>\s+\.\w+) # 一个匹配不到的命名分组
)
.*? # 匹配 :
(\d+) # 匹配 2016
''',
s, re.X)
# 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引
print(m.end())
# output> 22
# 根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g<name> 来选择分组
print(m.expand("my name is \\1"))
# output> my name is Mr.Gumby
# 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组
print(m.group())
# output> Mr.Gumby : 2016
print(m.group(1,2))
# output> ('Mr.Gumby', None)
# 返回 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值
print(m.groupdict('default_string'))
# output> {'name': 'Mr.Gumby', 'no': 'default_string'}
# 以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default
print(m.groups('default_string'))
# output> ('Mr.Gumby', 'default_string', '2016')
# 返回指定分组的起止未知组成的元组,默认返回由 start() 和 end() 组成的元组
print(m.span(3))
# output> (18, 22)
# 返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引
print(m.start(3))
# output> 18
6 分组用法
python 的正则表达式中用小括号 “(” 表示分组,按照每个分组中前半部分出现的顺序 “(” 判定分组的索引,索引从 1 开始,每个分组在访问的时候可以使用索引,也可以使用别名
1 | s = 'Hello, Mr.Gumby : 2016/10/26' |
有时候可能只是为了把正则表达式分组,而不需要捕获其中的内容,这时候可以使用非捕获分组
1 | s = 'Hello, Mr.Gumby : 2016/10/26' |
如果你在写正则的时候需要在正则里面重复书写某个表达式,那么你可以使用正则的引用分组功能,需要注意的是引用的不是前面分组的 正则表达式 而是捕获到的 内容,并且引用的分组不算在分组总数中。
1 | s = 'Hello, Mr.Gumby : 2016/2016/26' |
7 环视用法
环视还有其他的名字,例如界定、断言、预搜索等。
环视是一种特殊的正则语法,它匹配的不是字符串,而是 位置,其实就是使用正则来说明这个位置的左右应该是什么或者应该不是什么,然后去寻找这个位置。
环视的语法有四种,具体见元字符,基本用法如下。
1 | s = 'Hello, Mr.Gumby : 2016/10/26 Hello,r.Gumby : 2016/10/26' |
To my family,with love
To my friend,with encourage
To my teacher,with gratitude