about
关于正则表达式
正则表达式(Regular Expression,简写regex、re),又称之为正则表示式、正则表示法、规则表达式等,它是计算机科学的一个概念。
正则表达式可以通过特殊字符+普通字符来实现一组规则,用于匹配字符串中符合规则的字串,从而达到将匹配的字串取值、替换的目的。
许多的程序设计语言都开发出自己的正则表达式引擎以支持利用正则表达式进行字符串操作。
如何学好正则表达式
无他,唯手熟尔。
对于新手来说,这里推荐几个在线的正则匹配网站,来帮助你快速编写正则规则:
基础的元字符
先来看组成表达式的最基础的元字符。
元字符是具有特殊含义的字符:
字符 | 描述 | 示例 |
---|---|---|
[] | 一组字符 | "[a-m]" |
\ | 表示特殊序列(也可用于转义特殊字符) | "\d" |
. | 换行符除外的任何字符 | "he..o" |
^ | 起始于 | "^hello" |
$ | 结束于 | "world$" |
* | 零次或多次出现 | "aix*" |
+ | 一次或多次出现 | "aix+" |
{} | 确切地指定的出现次数 | "al{2}" |
| | 两者任一 | "falls|stays" |
() | 捕获和分组 |
后续的字符集和特殊序列、量词都是在这些基础的元字符上展开的细节描述。
特殊序列
特殊序列指的是 \
后跟下表中的某个字符,拥有特殊含义:
字符 | 描述 | 备注 |
---|---|---|
\A | 如果指定的字符位于字符串的开头,则返回匹配项 | 只在Python中测试可用 |
\b | 返回指定字符位于单词的开头或末尾的匹配项 | |
\B | 返回指定字符存在的匹配项,但不在单词的开头(或结尾处) | 在Python和JavaScript中稍有不同 |
\d | 返回字符串包含数字的匹配项(数字 0-9) | |
\D | 返回字符串不包含数字的匹配项 | |
\s | 返回字符串包含空白字符的匹配项 | |
\S | 返回字符串不包含空白字符的匹配项 | |
\w | 返回一个匹配项,其中字符串包含任何单词字符 (从 a 到 Z 的字符,从 0 到 9 的数字和下划线 _ 字符) | |
\W | 返回一个匹配项,其中字符串不包含任何单词字符 | |
\Z | 如果指定的字符位于字符串的末尾,则返回匹配项 |
一些示例,但不是全部
\A
import re
print(re.findall('\AThe', 'The rain in Spain')) # ['The']
注意,我只在Python中使用返回了正确的结果。在在线正则表达式测试网站上测试无效,经查,在线正则表达式测试网站基于JavaScript来做的正则匹配,而JavaScript中不支持\A
。
\b
# 规则:
kai\b
# 待匹配字符串:
zhangkai kaizhang
# 匹配到 1 条结果:
kai # 匹配的是 zhangkai 的 kai
匹配以kai
结尾的单词,也可以匹配以kai
开头的单词:
# 规则:
\bkai
# 待匹配字符串:
zhangkai kaizhang
# 匹配到 1 条结果:
kai # 匹配的是 kaizhang 的 kai
用的不多。
\B
先来看在JavaScript中:
# 规则:
\Bain
# 待匹配字符串:
The rain in Spain ainxxx ooainxxx
# 匹配到 3 条结果:
ain # rain 中的 ain
ain # Spain 中的 ain
ain # ooainxxx 中的 ain
如果\B
在前面,匹配非ain
开头的单词,但允许ain
在单词的中间和结尾。
# 规则:
ain\B
# 待匹配字符串:
The rain in Spain ainxxx ooainxxx
# 匹配到 2 条结果:
ain # ainxxx 中的 ain
ain # ooainxxx 中的 ain
如果\B
在后面,匹配非ain
结尾的单词,但允许ain
在单词的中间和开头。
在Python中:
import re
# 如果 \B 在前面,匹配非 ain 开头的单词,但允许以 ain 结尾
print(re.findall('\Bain', 'The rain in Spain ainxxx')) # ['ain', 'ain']
# 如果 \B 在后面,匹配非 ain 结尾的单词,但允许 ain 在单词的开头和中间
print(re.findall('ain\B', 'The rain in Spain')) # []
print(re.findall('ain\B', 'The rain in Spain ainxxx')) # ['ain']
print(re.findall('ain\B', 'The rain in Spain ainxxx ooainxxx')) # ['ain', 'ain']
\d
# 规则:
\d
# 待匹配字符串:
zhangkai123kaizhang456
# 匹配到 6 条结果:
1
2
3
4
5
6
每次匹配一个数字项。
\D
# 规则:
\D
# 待匹配字符串:
zhangkai123kaizhang456
# 匹配到 16 条结果:
z
h
a
n
g
k
a
i
k
a
i
z
h
a
n
g
每次匹配一个非数字项。
\s
# 规则:
\s
# 待匹配字符串:
zhang kai
# 匹配到 1 条结果:
# 是的,匹配到了一个空格,zhang和kai中的空格
每次匹配一个空格。
字符集
字符集,也称之为集合或方括号,是一对方括号 [] 内的一组字符,具有特殊含义:
集合 | 描述 |
---|---|
[arn] | 返回一个匹配项,其中存在指定字符(a,r 或 n)之一 |
[a-n] | 返回字母顺序 a 和 n 之间的任意小写字符匹配项 |
[^arn] | 返回除 a、r 和 n 之外的任意字符的匹配项 |
[0123] | 返回存在任何指定数字(0、1、2 或 3)的匹配项 |
[0-9] | 返回 0 与 9 之间任意数字的匹配 |
[a-zA-Z] | 返回字母顺序 a 和 z 之间的任何字符的匹配,小写或大写 |
[+] | 在集合中,+、*、.、|、()、$、{} 没有特殊含义,因此 [+] 表示:返回字符串中任何 + 字符的匹配项 |
一些示例,但不是全部
[arn]
# 规则:
[arn]
# 待匹配字符串:
zhangkai
# 匹配到 3 条结果:
a
n
a
# 其他规则形式:
[123]
[0123456789]
[ABC]
[\d]
因为[]
每次只返回一个匹配项,所以有三个匹配结果。
[a-n]
# 规则:
[a-n]
# 待匹配字符串:
zhangkai
# 匹配到 7 条结果:
h
a
n
g
k
a
i
# 其他规则形式:
[0-9]
[a-z]
[A-Z]
[a-zA-Z0-9] 可以匹配所有大小写英文字符和数字
每次匹配的范围是a-n
之间的任意小写英文字母。
[^arn]
# 规则:
[^arn]
# 待匹配字符串:
^zhangkai
# 匹配到 6 条结果:
^
z
h
g
k
i
每次匹配非a
、r
、n
之外的任意字符,包括^
。
+ * . | () $ {}
# 规则:
[+*.|()${}a]
# 待匹配字符串:
a+bc*de.f|g(h)ig$kl{m}n
# 匹配到 10 条结果:
a
+
*
.
|
(
)
$
{
}
注意,上面列举的这些特殊字符,在[]
中使用的话,就是一个普通的字符。
量词
量词约束字符或者字符组的重复规则。
符号 | 描述 | 备注 |
---|---|---|
* | 重复零次或多次 | |
+ | 重复一次或者多次 | |
? | 重复零次或者一次 | |
{n} | 重复n次 | |
{n,} | 重复n次或者多次 | 没有{,n} 的用法 |
{n,m} | 重复n到m次 |
"""
重复零次或多次
"""
import re
text = "他是大B个,确实是个大2B。"
data_list = re.findall("大2*B", text)
print(data_list) # ['大B', '大2B']
"""
重复一次或者多次
"""
import re
text = "他是大B个,确实是个大2B,大3B,大66666B。"
data_list = re.findall("大\d+B", text)
print(data_list) # ['大2B', '大3B', '大66666B']
"""
重复零次或者一次
"""
import re
text = "他是大B个,确实是个大2B,大3B,大66666B。"
data_list = re.findall("大\d?B", text)
print(data_list) # ['大B', '大2B', '大3B']
"""
重复n次
"""
import re
text = "楼主太牛逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("151312\d{5}", text)
print(data_list) # ['15131255789']
"""
重复n次或更多次
"""
import re
text = "楼主太牛逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("\d{9,}", text)
print(data_list) # ['442662578', '15131255789']
"""
重复n到m次
"""
import re
text = "楼主太牛逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("\d{10,15}", text)
print(data_list) # ['15131255789']
分组
提取数据区域
import re
text = "楼主太牛逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("15131(2\d{5})", text)
print(data_list) # ['255789']
text = "楼主太牛逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来15131266666呀"
data_list = re.findall("15(13)1(2\d{5})", text)
print(data_list) # [('13', '255789'), ('13', '266666')]
text = "楼主太牛逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("(15131(2\d{5}))", text)
print(data_list) # [('15131255789', '255789')]
import re
text = "楼主15131root太牛15131alex逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("15131(2\d{5}|r\w+太)", text)
print(data_list) # ['root太', '255789']
text = "楼主15131root太牛15131alex逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("(15131(2\d{5}|r\w+太))", text)
print(data_list) # [('15131root太', 'root太'), ('15131255789', '255789')]
re模块
python中提供了re模块,可以处理正则表达式并对文本进行处理。
"""
findall,获取匹配到的所有数据
"""
import re
text = "楼主15131root太牛15131alex逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
data_list = re.findall("15131(2\d{5}|r\w+太)", text)
print(data_list) # ['root太', '255789']
"""
finditer,以迭代器的形式返回匹配结果
"""
import re
text = "逗2B最逗3B欢乐"
data = re.finditer("\dB", text)
for item in data:
print(item.group())
"""
2B
3B
"""
text = "逗2B最逗3B欢乐"
data = re.finditer("(?P<xx>\dB)", text) # 命名分组
for item in data:
print(item.groupdict())
"""
{'xx': '2B'}
{'xx': '3B'}
"""
text = "dsf130429191912015219k13042919591219521Xkk"
data_list = re.finditer("\d{6}(?P<year>\d{4})(?P<month>\d{2})(?P<day>\d{2})\d{3}[\d|X]", text)
for item in data_list:
info_dict = item.groupdict()
print(info_dict)
"""
{'year': '1919', 'month': '12', 'day': '01'}
{'year': '1959', 'month': '12', 'day': '19'}
"""
"""
match,从字符串的起始位置开始匹配,匹配成功返回一个对象,未匹配成功返回None
"""
import re
text = "大小逗2B最逗3B欢乐"
data = re.match("逗\dB", text)
print(data) # None # 因为开头不是逗
text = "逗2B最逗3B欢乐"
data = re.match("逗\dB", text)
if data:
content = data.group() # "逗2B"
print(content)
# match匹配到就返回了,后面再有也不匹配了
print(re.findall("逗\dB", text)) # ['逗2B', '逗3B']
"""
search,浏览整个字符串去匹配符合条件的,匹配到就返回,后面再有符合条件的也不管了,未匹配成功返回None
search跟match的区别就是,match必须要求从开头,search不需要
"""
import re
text = "大小逗2B最逗3B欢乐"
data = re.search("逗\dB", text)
print(data) # <re.Match object; span=(2, 5), match='逗2B'>
text = "逗2B最逗3B欢乐"
data = re.search("逗\dB", text)
if data:
content = data.group() # 逗2B
print(content)
# search匹配到就返回,后面再有符合条件的也不管了
print(re.findall("逗\dB", text)) # ['逗2B', '逗3B']
"""
sub,替换匹配成功的位置
"""
import re
text = "逗2B最逗3B欢乐"
data = re.sub("\dB", "沙雕", text)
print(data) # 逗沙雕最逗沙雕欢乐
text = "逗2B最逗3B欢乐"
# cont指定替换次数
data = re.sub("\dB", "沙雕", text, 1)
print(data) # 逗沙雕最逗3B欢乐
"""
split,根据匹配成功的位置分割
"""
import re
text = "逗2B最逗3B欢乐"
data = re.split("\dB", text)
print(data) # ['逗', '最逗', '欢乐']
text = "逗2B最逗3B欢乐"
# 只分割一次
data = re.split("\dB", text, 1)
print(data) # ['逗', '最逗3B欢乐']
"""
re.compile的主要作用于将正则表达式的字符串编译成一个正则表达式对象。
re.compile的返回值是一个正则表达式对象,这个对象可以被用于匹配和搜索字符串。
使用re.compile的好处在于,可以预编译正则表达式,从而提高程序的运行效率。
当需要对多个字符串进行匹配或搜索时,可以先使用re.compile编译正则表达式,
然后多次使用编译好的正则表达式对象,避免了每次都需要重新编译正则表达式的开销。
"""
import re
text_list = ["127.0.0.1", "192.168.1.1", "10.0.2.1", "172.16.0.1"]
pattern = re.compile(r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")
def code1(text_list):
# 精确的匹配给定的字符串是否是IP地址
li = []
for text in text_list:
data = re.search('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', text)
li.append(data.group())
return li
def code2(text_list):
li = []
for text in text_list:
data = pattern.search(text)
li.append(data.group())
return li
data = code1(text_list)
print(data)
data = code2(text_list)
print(data)
"""
['127.0.0.1', '192.168.1.1', '10.0.2.1', '172.16.0.1']
['127.0.0.1', '192.168.1.1', '10.0.2.1', '172.16.0.1']
"""
# 它到底提高了多少性能开销呢,我们可以用jupyter notebook提供的%%timeit来算一下
%%timeit
code1(text_list)
"""
4.21 µs ± 41.6 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
"""
%%timeit
code2(text_list)
"""
2.56 µs ± 71.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
"""
常用正则表达式
利用正则匹配QQ号码
import re
text = "1207180815"
data = re.search("[1-9]\d{4,}", text)
print(data) # <re.Match object; span=(0, 10), match='1207180815'>
身份证号码
text = "dsf130429191912015219k13042919591219521Xkk"
data_list = re.findall("\d{17}[\dX]", text)
print(data_list) # ['130429191912015219', '13042919591219521X']
手机号
import re
text = "我的手机哈是15133377892,你的手机号是1171123啊?"
data_list = re.findall("1[3-9]\d{9}", text)
print(data_list) # ['15133377892']
邮箱地址
import re
text = "楼主太牛逼了,在线想要 442662578@qq.com和xxxxx@live.com谢谢楼主,手机号也可15131255789,搞起来呀"
email_list = re.findall("[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+", text, re.ASCII)
print(email_list) # ['442662578@qq.com', 'xxxxx@live.com']
ip地址
import re
# 精确的匹配给定的字符串是否是IP地址
text_list = ["127.0.0.1", "192.168.1.1", "10.0.2.1", "172.16.0.1"]
for text in text_list:
data = re.search('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$', text)
print(data.group())
"""
127.0.0.1
192.168.1.1
10.0.2.1
172.16.0.1
"""
# 从长文本中提取中提取ip地址
text = "这是一个测试文本,包含IP地址192.168.1.1和10.0.2.1"
data = re.findall(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', text)
print(data) # ['192.168.1.1', '10.0.2.1']
that's all, see also:
维基百科:正则表达式 | 正则表达式 |