正则表达式
在处理字符串时,经常会有查找符合某些复杂规则的字符串的需求。正则表达式就是用于描述这些规则的工具
在线正则表达式测试
| import re
a = 'C|C++|Python'
r = re.findall('Python', a)
print(r)
print(re.findall('PHP', a))
"""
['Python']
[]
"""
|
元字符
元字符 |
说明 |
\d |
匹配数字字符 |
\D |
匹配非数字字符 |
\w |
匹配字母、数字、下划线或汉字 |
\W |
匹配除字母、数字、下划线或汉字以外的字符 |
\s |
匹配单个空白字符(包括Tab键和换行符) |
\S |
匹配单个空白字符以外的所有字符 |
. |
匹配除换行符以外的任意字符 |
\b |
匹配单词的开始或结束,单词的分界符通常是空格、标点符号或者换行 |
^ |
匹配字符串的开始 |
$ |
匹配字符串的结束 |
\A |
匹配字符串开头 |
\Z |
匹配字符串结尾,如果存在换行,只匹配到换行前的结束字符串 |
\z |
匹配字符串结尾,结果存在换行,同时还会匹配换行符 |
\G |
匹配最后匹配完成的位置 |
\n |
匹配一个换行符 |
\t |
匹配一个制表符 |
| In [3]: a = 'C/c++0,7,333,Pythonhhh2333,666'
In [4]: print(re.findall('\d', a))
['0', '7', '3', '3', '3', '2', '3', '3', '3', '6', '6', '6']
In [5]: print(re.findall('\D', a))
['C', '/', 'c', '+', '+', ',', ',', ',', 'P', 'y', 't', 'h', 'o', 'n', 'h', 'h', 'h', ',']
|
字符集/字符类
1
2
3
4
5
6
7
8
9
10
11
12
13 | In [6]: s = 'abc, acc, adc, aec, afc, ahc'
In [7]: print(re.findall('a[cf]c', s))
['acc', 'afc']
In [8]: print(re.findall('[cf]', s))
['c', 'c', 'c', 'c', 'c', 'f', 'c', 'c']
In [9]: print(re.findall('a[^cf]c', s))
['abc', 'adc', 'aec', 'ahc']
In [10]: print(re.findall('a[c-f]c', s))
['acc', 'adc', 'aec', 'afc']
|
概括字符集
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 | In [1]: a = '123hhh996ai'
In [4]: print(re.findall('[^0-9]', a))
['h', 'h', 'h', 'a', 'i']
In [5]: print(re.findall('[0-9]', a))
['1', '2', '3', '9', '9', '6']
In [6]: print(re.findall('\w', a))
['1', '2', '3', 'h', 'h', 'h', '9', '9', '6', 'a', 'i']
In [7]: a = '123hhh996ai&23333'
In [8]: print(re.findall('\w', a))
['1', '2', '3', 'h', 'h', 'h', '9', '9', '6', 'a', 'i', '2', '3', '3', '3', '3']
In [10]: a = '123hhh996ai&23___333'
In [11]: print(re.findall('[A-Za-z0-9_]', a))
['1', '2', '3', 'h', 'h', 'h', '9', '9', '6', 'a', 'i', '2', '3', '_', '_', '_', '3', '3', '3']
In [12]: print(re.findall('\w', a))
['1', '2', '3', 'h', 'h', 'h', '9', '9', '6', 'a', 'i', '2', '3', '_', '_', '_', '3', '3', '3']
In [13]: print(re.findall('\W', a))
['&']
In [14]: a = '123hhh996ai&23___3 3\n3'
In [15]: print(re.findall('\W', a))
['&', ' ', '\n']
# 要想匹配给定字符串中任意一个汉字,可以使用 '[\u4e00-\u9fa5]'
In [62]: print(re.findall('[\u4e00-\u9fa5]', "哈哈哈,666,很强"))
['哈', '哈', '哈', '很', '强']
|
数量词
| In [16]: print(re.findall('[a-z][a-z][a-z]', "python 111111java c++hhhphhhhp"))
['pyt', 'hon', 'jav', 'hhh', 'phh', 'hhp']
In [17]: print(re.findall('[a-z]{3}', "python 111111java c++hhhphhhhp"))
['pyt', 'hon', 'jav', 'hhh', 'phh', 'hhp']
In [19]: print(re.findall('[a-z]{3,6}', "python 111111java php")) # 匹配前面的字符最少 3 次,最多 6 次
['python', 'java', 'php']
In [60]: print(re.findall('[a-z]{3,}', "python 111111java php")) # 匹配前面的字符最少 3 次
['python', 'java', 'php']
|
贪婪与非贪婪
| In [19]: print(re.findall('[a-z]{3,6}', "python 111111java php")) # 贪婪
['python', 'java', 'php']
In [20]: print(re.findall('[a-z]{3,6}?', "python 111111java php")) # 非贪婪
['pyt', 'hon', 'jav', 'php']
|
使用.*
进行匹配时,可能有时候匹配到的并不是我们想要的结果
| import re
content = 'Hello 1234567 World This is a Regex Demo'
result = re.match('^He.*(\d+).*Demo$', content)
print(result)
print(result.group(1))
"""
<re.Match object; span=(0, 40), match='Hello 1234567 World This is a Regex Demo'>
7
"""
|
这里我们依然想获取中间的数字,所以中间依然写的是(\d+)
。而数字两侧由于内容比较杂乱,所以想省略来写,都写成.*
。最后,组成^He.*(\d+).*Demo$
,看样子并没有什么问题
但是运行后发现,只得到了一个数字 7,这是怎么回事?
这里涉及一个贪婪匹配和非贪婪匹配的问题。在贪婪匹配下,.*
会匹配尽可能多的字符。正则表达式中.*
后面是\d+
,也就是至少一个数字,并没有指定具体多少个数字,因此.*
就尽可能匹配多的字符,这里就把123456
匹配了,给\d+
留下一个可满足条件的数字 7,最后得到的内容就只有数字 7 了
但这很明显会给我们带来很大的不便。有时候,匹配结果会莫名其妙少了一部分内容。其实,这里只需要使用非贪婪匹配就好了。非贪婪匹配的写法是.*?
,多了一个?
,那么它可以达到怎样的效果?
| import re
content = 'Hello 1234567 World This is a Regex Demo'
result = re.match('^He.*?(\d+).*Demo$', content)
print(result)
print(result.group(1))
"""
<re.Match object; span=(0, 40), match='Hello 1234567 World This is a Regex Demo'>
1234567
"""
|
此时就可以成功获取1234567
了。原因可想而知,贪婪匹配是尽可能匹配多的字符,非贪婪匹配就是尽可能匹配少的字符。当.*
匹配到Hello
后面的空白字符时,再往后的字符就是数字了,而\d+
恰好可以匹配,那么这里.*?
就不再进行匹配,交给\d+
去匹配后面的数字。所以这样.*?
匹配了尽可能少的字符,\d+
的结果就是1234567
了
所以说,在做匹配的时候,字符串中间尽量使用非贪婪匹配,也就是用.*?
来代替.*
,以免出现匹配结果缺失的情况
但这里需要注意,如果匹配的结果在字符串结尾,.*?
就有可能匹配不到任何内容了,因为它会匹配尽可能少的字符
| import re
content = 'http://weibo.com/comment/kEraCN'
result1 = re.match('http.*?comment/(.*?)', content)
result2 = re.match('http.*?comment/(.*)', content)
print('result1', result1.group(1))
print('result2', result2.group(1))
"""
result1
result2 kEraCN
"""
|
可以观察到.*?
没有匹配到任何结果,而.*
则尽量匹配多的内容,成功得到了匹配结果
匹配0次1次或者无限次
*
表示匹配前面的字符零次或无限多次
| In [22]: print(re.findall('python*', "pytho0python1pythonn2"))
['pytho', 'python', 'pythonn']
|
+
匹配1次或者无限多次
| In [23]: print(re.findall('python+', "pytho0python1pythonn2"))
['python', 'pythonn']
|
?
匹配0次或1次
| In [24]: print(re.findall('python?', "pytho0python1pythonn2"))
['pytho', 'python', 'python']
|
| In [25]: print(re.findall('python{1,2}', "pytho0python1pythonn2")) # 贪婪
['python', 'pythonn']
In [26]: print(re.findall('python{1,2}?', "pytho0python1pythonn2")) # 非贪婪
['python', 'python']
|
边界匹配符
^
表示行的开始;$
表示行的末尾
该表达式表示要匹配子串 tm 的开始位置是行头,如"tm equal Tomorrow Moon"
就可以匹配,而"Tomorrow Moon equal tm"
则不匹配。但如果使用
后者可以匹配而前者不能匹配。如果要匹配的子串出现在字符串的任意部分,那么可以直接写成
这样两个字符串就都可以匹配了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | In [27]: print(re.findall('\d{4,8}', "100001"))
['100001']
In [28]: print(re.findall('\d{4,8}', "101"))
[]
In [29]: print(re.findall('\d{4,8}', "123456789"))
['12345678']
In [30]: print(re.findall('^\d{4,8}$', "123456789"))
[]
In [31]: print(re.findall('^000', "100000001"))
[]
In [32]: print(re.findall('000$', "100000001"))
[]
|
匹配模式参数
findall() 的语法格式为:re.findall(pattern, string, flags=0)
其中 flags 为可选参数,表示标志位,用于控制匹配方式,如是否区分大小写
常用标志
标志 |
说明 |
I 或 IGNORECASE |
执行不区分大小写的匹配 |
S 或 DOTALL |
使用 (.) 字符匹配所有字符,包括换行符 |
M 或 MULTILINE |
将 ^ 和 $ 用于包括整个字符串的开始和结尾的每一行(默认情况下,仅适用于整个字符串的开始和结尾处) |
X 或 VERBOSE |
忽略模式字符串中未转义的空格和注释 |
A 或 ASCII |
对于 \w、\W、\b、\B、\d、\D、\s 和 \S 只进行 ASCII 匹配 |
U |
根据 Unicode 字符集解析字符。这个标志影响 \w, \W, \b 和 \B |
| In [44]: print(re.findall('c#', "PythonC#JavaPHP"))
[]
In [45]: print(re.findall('c#', "PythonC#JavaPHP", re.I))
['C#']
In [50]: print(re.findall('c#.{1}', "PythonC#\nJavaPHP", re.I))
[]
In [51]: print(re.findall('c#.{1}', "PythonC#\nJavaPHP", re.I | re.S))
['C#\n']
|
search和match函数
re.match(pattern, string, flags=0)
从字符串的开始处进行匹配,如果在起始位置匹配成功,则返回 Match 对象,否则返回 None
Match 对象中包含了匹配值的位置和匹配数据。其中,要获取匹配值的起始位置可以使用 Match 对象的 start() 方法;要获取匹配值的结束位置可以使用 end() 方法;通过 span() 方法可以返回匹配位置的元组;通过 string 属性可以获取要匹配的字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | pattern = r"mr_\w+"
string = "MR_SHOP mr_shop"
match = re.match(pattern, string, re.I)
print(f"匹配值的起始位置: {match.start()}")
print(f"匹配值的结束位置: {match.end()}")
print(f"匹配位置的元组: {match.span()}")
print(f"要匹配的字符串: {match.string}")
print(f"匹配数据: {match.group()}")
"""
匹配值的起始位置: 0
匹配值的结束位置: 7
匹配位置的元组: (0, 7)
要匹配的字符串: MR_SHOP mr_shop
匹配数据: MR_SHOP
"""
|
| import re
content = 'Hello 1234567 World This is a Regex Demo'
result = re.match('^Hello\s(\d+)\sWorld', content)
print(result.group())
print(result.group(1))
print(result.span())
"""
Hello 1234567 World
1234567
(0, 19)
"""
|
group() 会输出完整的匹配结果,group() 会输出第一个被 () 包围的匹配结果。如果正则表达式后面还有 () 包含的内容,那么可以依次用 group(2)、group(3) 等来获取
re.search(pattern, string, flags=0)
用于在整个字符串中搜索第一个匹配的值,如果匹配成功,则返回 Match 对象,否则返回 None
re.compile(pattern, flags=0)
将正则表达式的样式编译为一个 正则表达式对象 (正则对象),可以用于匹配,通过这个对象的方法 match(), search()
向 re.compile 传入一个字符串值,表示正则表达式,它将返回一个 Regex 模式对象(或者就简称为 Regex 对象)
序列
| prog = re.compile(pattern)
result = prog.match(string)
|
等价于
| result = re.match(pattern, string)
|
如果需要多次使用这个正则表达式的话,使用 re.compile() 和保存这个正则对象以便复用,可以让程序更加高效。
Pattern.search(string[, pos[, endpos]])
扫描整个 string 寻找第一个匹配的位置, 并返回一个相应的匹配对象。如果没有匹配,就返回 None
可选的第二个参数 pos 给出了字符串中开始搜索的位置索引;默认为 0,它不完全等价于字符串切片; '^' 样式字符匹配字符串真正的开头,和换行符后面的第一个字符,但不会匹配索引规定开始的位置。
可选参数 endpos 限定了字符串搜索的结束;它假定字符串长度到 endpos , 所以只有从 pos 到 endpos - 1 的字符会被匹配。如果 endpos 小于 pos,就不会有匹配产生;另外,如果 rx 是一个编译后的正则对象, rx.search(string, 0, 50) 等价于 rx.search(string[:50], 0)。
| pattern = re.compile("d")
s1 = pattern.search("dog") # Match at index 0
print(s1)
print(s1.group())
s2 = pattern.search("dog", 1) # No match; search doesn't include the "d"
print(s2)
"""
<_sre.SRE_Match object; span=(0, 1), match='d'>
d
None
"""
|
Pattern.match(string[, pos[, endpos]])
如果 string 的开始位置能够找到这个正则样式的任意个匹配,就返回一个相应的匹配对象。如果不匹配,就返回 None
| pattern = re.compile("o")
m1 = pattern.match("dog") # No match as "o" is not at the start of "dog".
print(m1)
m2 = pattern.match("dog", 1) # Match as "o" is the 2nd character of "dog".
print(m2)
print(m2.group())
"""
None
<_sre.SRE_Match object; span=(1, 2), match='o'>
o
"""
|
利用括号分组
用 findall() 方法进行匹配,如果在指定的模式字符串中,包含分组,则返回与分组匹配的文本列表
| pattern = r"[1-9]{1,3}(\.[0-9]{1,3}){3}"
str1 = "127.0.0.1 192.168.1.66"
match = re.findall(pattern, str1)
print(match)
"""
['.1', '.66']
"""
|
得到的结果是分根据分组进行匹配的结果,即"(\.[0-9]{1,3})"
匹配的结果。如果想获取整个模式字符串的匹配,可以将整个模式字符串使用一对小括号进行分组,然后在获取结果时,只取返回值列表中的每个元素(是一个元组)的第一个元素
| pattern = r"([1-9]{1,3}(\.[0-9]{1,3}){3})"
str1 = "127.0.0.1 192.168.1.66"
match = re.findall(pattern, str1)
print(match)
for item in match:
print(item[0])
"""
[('127.0.0.1', '.1'), ('192.168.1.66', '.66')]
127.0.0.1
192.168.1.66
"""
|
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 | In [68]: print(re.findall('(Python){1}', "PythonPythonPythonPython"))
['Python', 'Python', 'Python', 'Python']
In [69]: print(re.findall('(Python){2}', "PythonPythonPythonPython"))
['Python', 'Python']
In [70]: print(re.findall('(Python){3}', "PythonPythonPythonPython"))
['Python']
In [71]: print(re.findall('(Python){4}', "PythonPythonPythonPython"))
['Python']
In [67]: print(re.findall('(Python)', "PythonPythonPythonPython"))
['Python', 'Python', 'Python', 'Python']
In [72]: print(re.findall('(Python)(Python)', "PythonPythonPythonPython"))
[('Python', 'Python'), ('Python', 'Python')]
In [42]: print(re.findall('(Python)(Python)(Python)', "PythonPythonPythonPython"))
[('Python', 'Python', 'Python')]
In [73]: print(re.findall('((Python)(Python))', "PythonPythonPythonPython"))
[('PythonPython', 'Python', 'Python'), ('PythonPython', 'Python', 'Python')]
In [74]: print(re.findall('((Python){3})', "PythonPythonPythonPython"))
[('PythonPythonPython', 'Python')]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | s = "life is short, i use python, i love python"
r = re.search("life(.*)python(.*)python", s)
print(r.group(0))
print(r.group(1))
print(r.group(2))
print(r.group(0, 1, 2))
print(r.groups())
"""
life is short, i use python, i love python
is short, i use
, i love
('life is short, i use python, i love python', ' is short, i use ', ', i love ')
(' is short, i use ', ', i love ')
"""
|
转义字符
例如用正则表达式匹配诸如"127.0.0.1"这样格式的 IP 地址。如果直接使用点字符是不对的,需要使用转义字符\
括号在正则表达式中也是一个元字符
通过在字符串的第一个引号之前加上 r,可以将该字符串标记为原始字符串,它不包括转义字符
| print(re.findall("\d", "12hh"))
print(re.findall("\bmr\w*\b", "mrsoft"))
print(re.findall("\\bmr\w*\\b", "mrsoft"))
print(re.findall(r"\bmr\w*\b", "mrsoft"))
"""
['1', '2']
[]
['mrsoft']
['mrsoft']
"""
|
我们知道正则表达式定义了许多匹配模式,如.
匹配除换行符以外的任意字符,但是如果目标字符串里面就包含.
,那该怎么办?
这里就需要用到转义匹配了
| import re
content = '(百度)www.baidu.com'
result = re.match('\(百度\)www\.baidu\.com', content)
print(result)
"""
<re.Match object; span=(0, 17), match='(百度)www.baidu.com'>
"""
|
用管道匹配多个分组
字符|
称为“管道”。希望匹配许多表达式中的一个时,就可以使用它。例如,正则表达式r"Batman|Tina Fey"
将匹配"Batman"
或"Tina Fey"
| print(re.findall(r"Batman|Tina Fey", "Batman and Tina Fey."))
"""
['Batman', 'Tina Fey']
"""
|
也可以使用管道来匹配多个模式中的一个,作为正则表达式的一部分。例如,假设希望匹配"Batman"
、"Batmobile"
、"Batcopter"
和"Batbar"
中任意一个。因为所有这些字符串都以"Bat"
开始,所以如果能够只指定一次前缀,就很方便
| bat_regex = re.compile(r"Bat(man|mobile|copter|bat)")
s = bat_regex.search("Batmobile lost a wheel")
print(s.group())
print(s.group(1))
"""
Batmobile
mobile
"""
|
re.sub 正则替换
除了使用正则表达式提取信息外,有时候还需要借助它来修改文本。比如,想要把一串文本中的所有数字都去掉,如果只用字符串的 replace() 方法,那就太繁琐了,这时可以借助 sub() 方法
语法格式:re.sub(pattern, repl, string, count, flags)
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 | In [52]: print(re.sub("C#", "GO", "PythonC#JAVAC++"))
PythonGOJAVAC++
In [53]: print(re.sub("C#", "GO", "PythonC#JAVAC#C++"))
PythonGOJAVAGOC++
In [54]: print(re.sub("C#", "GO", "PythonC#JAVAC#C++", 1))
PythonGOJAVAC#C++
In [55]: def convert(value):
...: pass
In [56]: print(re.sub("C#", convert, "PythonC#JAVAC#C++", 1))
PythonJAVAC#C++
In [57]: print(re.sub("C#", convert, "PythonC#JAVAC#C++"))
PythonJAVAC++
In [58]: def convert(value):
...: matched = value.group()
...: return f"!!{matched}!!"
In [59]: print(re.sub("C#", convert, "PythonC#JAVAC#C++"))
Python!!C#!!JAVA!!C#!!C++
|
把函数作为参数传递
| def convert(value):
matched = value.group()
if int(matched) >= 6:
return "9"
else:
return "0"
print(re.sub("\d", convert, "A8C3721D86"))
"""
A9C0900D99
"""
|
使用正则表达式分割字符串
语法格式:re.split(pattern, string, [maxsplit], [flags])
| pattern = r"[?&]"
url = "http://www.baidu.com/login.html?username='hhh'&pwd='lalala'"
result = re.split(pattern, url)
print(result)
"""
['http://www.baidu.com/login.html', "username='hhh'", "pwd='lalala'"]
"""
|