正则表达式

正则表达式在线验证工具:https://regex101.com/

正则表达式在线验证工具:https://regexr-cn.com/

python官方文档:https://docs.python.org/3/howto/regex.html

参考博客:https://www.byhy.net/tut/py/extra/regex/

练习网站:https://codejiaonang.com/#/course/regex_chapter1/0/0

练习讲解:https://www.bilibili.com/video/BV19t4y1y7qP?share_source=copy_web

总览

  • 特殊字符,术语叫做metacharacters(元字符)
1
. * + ? \ [ ] ^ $ { } | ( )
  • 特殊字符表
实例 描述
. 匹配除 \n 之外的任何单个字符。要匹配包括 \n 在内的任何字符,请使用象 [.\n] 的模式。
? 匹配一个字符零次或一次,另一个作用是非贪婪模式
+ 匹配1次或多次
* 匹配0次或多次
\| 或者
`^` 开头
$ 结尾
[^...]
(...) 分组匹配
\b 匹配单词边界
\B 匹配非单词边界,\b的补集
\d 匹配一个数字字符。等价于[0-9]
\D 匹配一个非数字字符。等价于 [^0-9]
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。(注意方括号开头跟着一个空格)
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]
\w 匹配数字字母和下划线。等价于[A-Za-z0-9_]
\W 匹配任何非数字字母和下划线。等价于 [^A-Za-z0-9_]

.——匹配所有字符

例如要从下面的文本中选出所有的颜色

1
2
3
4
苹果是绿色的
橙子是橙色的
香蕉是黄色的
乌鸦是黑色的

观察文本后可以发现,也就是要找到所有以“色”字结尾,并且包括前面一个字发词语,那么可以写成

1
.色

其中.代表了任意的一个字符,注意只是一个字符。.色合起来就表示要找任意一个字符后面是“色“这个字。

写在python代码中如下

1
2
3
4
5
6
7
8
9
content = '''苹果是绿色的
橙子是橙色的
香蕉是黄色的
乌鸦是黑色的'''

import re
p = re.compile(r'.色') # r表示raw,表示原始字符串,不对字符串中的特殊字符进行转义
for one in p.findall(content):
print(one)

运行结果如下

1
2
3
4
绿色
橙色
黄色
黑色

*——重复匹配任意次

*表示匹配前面的子表达式任意此,包括0次。

例如,在下面的文本中,选择每行逗号后面的字符串内容,包括逗号本身。(注意,这里的逗号是中文的逗号)

1
2
3
4
5
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
猴子,

此时可以使用正则表达式

1
,.*  #中文逗号

从逗号”,“开始,.*匹配任意字符任意次,即从逗号开始匹配到行末(注意匹配都是以行为单位的,.*是到行末结束不是到段末结束)

python代码

1
2
3
4
5
6
7
8
9
10
content = '''苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
猴子,'''

import re
p = re.compile(r',.*')
for one in p.findall(content):
print(one)

运行结果

1
2
3
4
5
,是绿色的
,是橙色的
,是黄色的
,是黑色的

注意,.*在正则表达式中是非常常见的,表示匹配任意字符任意次数。当然*星号前面的不是非得是点.,也可以是其它字符,比如绿色*,”绿“字开头后接任意次数”色“字

image-20220705103412595

+——重复匹配多次

+表示匹配前面的子表达式一次或多次,不包括0次。

例如,从下面文本中选择逗号后面的内容(包括逗号本身),如果逗号后面没有内容就不匹配。

1
2
3
4
5
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
猴子,

正则表达式如下设计

1
,.+

image-20220705104211943

.+.*都是常见的组合

{}——匹配指定次数

花括号表示匹配前面的字符指定次数

例如下面的文本

1
红彤彤,绿油油,黑乎乎,绿油油油,绿油油油油

表达式绿油{2,4}表示“绿”字开头后接绿字2到4次

image-20220705105002395

  • 典型应用场景——提取数字
1
2
3
zwxQQ1379347218
zggQQ1051570475
zxfQQ3340225021

正则表达式\d{10},其中\d表示数字

image-20220705105751138

  • 开闭区间
{x,} 表示x\ge x
{1,} 表示1\ge 1,等价于+
{0,} 表示0\ge 0,等价于*

**?——贪婪模式和非贪婪模式

* 贪婪模式——匹配尽可能长的字符串
*? 非贪婪模式——匹配尽可能短的字符串

我们要把下面的字符串中的所有html标签都提取出来,

1
source = '<html><head><title>Title</title>'

得到这样的一个列表

1
['<html>', '<head>', '<title>', '</title>']

很容易想到使用正则表达式 <.*>

写出如下代码

1
2
3
4
5
6
source = '<html><head><title>Title</title>'

import re
p = re.compile(r'<.*>')

print(p.findall(source))

但是运行结果,却是

1
['<html><head><title>Title</title>']

image-20220705111504393

怎么回事? 原来 在正则表达式中, ‘*’, ‘+’ 都是贪婪地,使用他们时,会尽可能多的匹配内容,

所以, <.*> 中的 星号(表示任意次数的重复),一直匹配到了 字符串最后的 </title> 里面的e。

解决这个问题,就需要使用非贪婪模式,也就是在星号后面加上 ? ,变成这样 <.*?>

代码改为

1
2
3
4
5
6
7
source = '<html><head><title>Title</title>'

import re
# 注意多出的问号
p = re.compile(r'<.*?>')

print(p.findall(source))

再运行一遍,就可以了

image-20220705111425393

?——指定字符为可选字符

有时,我们可能想要匹配一个单词的不同写法,比如colorcolour,或者honorhonour

这个时候我们可以使用 ? 符号指定一个字符、字符组或其他基本单元可选,这意味着正则表达式引擎将会期望该字符出现零次或一次

例如:

img

在这里 u? 表示u是可选的,即可以出现也可以不出现,可以匹配的是 honorhonour

通过这个案例可以知道?的作用就是匹配它之前的字符0次或1次。

|——或者条件

例如要提取所有图片文件的后缀名,可以在各个后缀名之间加上一个 |符号:

1
(.jpg|.gif|.jpeg|.png)

image-20220706152954637

\——对元字符的转义

在下面的文本中匹配点“.”前面的字符串,也包含点“.”本身

1
2
3
苹果.是绿色的
橙子.是橙色的
香蕉.是黄色的

正则表达式如下设计

1
.*\.

其中\.表示对.的转义

python代码如下

1
2
3
4
5
6
7
8
content = '''苹果.是绿色的
橙子.是橙色的
香蕉.是黄色的'''

import re
p = re.compile(r'.*\.')
for one in p.findall(content):
print(one)

运行结果如下

1
2
3
苹果.
橙子.
香蕉.

\——匹配某种类型的字符

快捷方式 描述
\w 与任意单词字符匹配,任意单词字符表示 [A-Z][a-z][0-9]_
\W 匹配任意一个非文字字符,等价于表达式 [^a-zA-Z0-9_]
\d 与任意数字匹配[0-9]
\D 匹配任意一个不是0-9之间的数字字符,等价于表达式 [^0-9]
\s 匹配任意一个空白字符,包括 空格、tab、换行符等,等价于表达式 [\t\n\r\f\v]
\S 匹配任意一个非空白字符,等价于表达式 [^ \t\n\r\f\v]
Flag Meaning
ASCII, A Makes several escapes like \w, \b, \s and \d match only on ASCII characters with the respective property.
DOTALL, S Make . match any character, including newlines.
IGNORECASE, I Do case-insensitive matches.
LOCALE, L Do a locale-aware match.
MULTILINE, M Multi-line matching, affecting ^ and $.
VERBOSE, X (for ‘extended’) Enable verbose REs, which can be organized more cleanly and understandably.

[]——匹配几个字符串之一

  • 方括号表示要匹配方括号中指定的几个字符中的一个

比如[abc] 表示匹配 a, b, c 三者的任意一个字符

  • 方括号中的-表示范围

例如[a-z] 表示所有的小写字母

  • 一些元字符在方括号内失去了魔法,变得和普通字符一样了

比如[akm.] 匹配 a k m . 里面任意一个字符。

这里 . 在括号里面不在表示匹配任意字符了,而就是表示 . 这个字符本身\.

  • 如果在方括号中使用 ^ , 表示 方括号里面的字符集合。

比如[^a-z]表示匹配所有非小写字母的字符,^非的是整个方括号,而不只是^后接的表达式。

^——匹配文本的起始位置

^ 表示匹配文本的 开头 位置。

正则表达式可以设定 单行模式多行模式

如果是 单行模式 ,表示匹配 整个文本 的开头位置。

如果是 多行模式 ,表示匹配 文本每行 的开头位置。

比如,下面的文本中,每行最前面的数字表示水果的编号,最后的数字表示价格

1
2
3
001-苹果价格-60
002-橙子价格-70,
003-香蕉价格-80,

如果我们要提取所有的水果编号,用这样的正则表达式 ^\d+

  • python代码如下
1
2
3
4
5
6
7
8
content = '''001-苹果价格-60
002-橙子价格-70
003-香蕉价格-80'''

import re
p = re.compile(r'^\d+', re.M) #多行模式
for one in p.findall(content):
print(one)

注意re.compile()的第二个参数re.M指定了多行模式

运行结果如下

1
2
3
001
002
003

如果,去掉re.compile()的第二个参数re.M, 运行结果如下

1
001

就只有第一行了。因为单行模式下,^ 只会匹配整个文本的开头位置。

$——匹配文本的结尾位置

$ 表示匹配文本的 结尾 位置。

如果是 单行模式 ,表示匹配 整个文本 的结尾位置。

如果是 多行模式 ,表示匹配 文本每行 的结尾位置。

比如,下面的文本中,每行最前面的数字表示水果的编号,最后的数字表示价格

1
2
3
001-苹果价格-60
002-橙子价格-70,
003-香蕉价格-80,

如果我们要提取所有的水果编号,用这样的正则表达式 \d+$

  • 对应Python代码
1
2
3
4
5
6
7
8
content = '''001-苹果价格-60
002-橙子价格-70
003-香蕉价格-80'''

import re
p = re.compile(r'\d+$', re.MULTILINE)
for one in p.findall(content):
print(one)

注意,re.compile()的第二个参数re.MULTILINE ,指明了使用多行模式,

  • 运行结果如下
1
2
3
60
70
80

如果,去掉 re.compile()的第二个参数re.MULTILINE, 运行结果如下

1
80

就只有最后一行了。因为单行模式下,$ 只会匹配整个文本的结束位置。

\b\B——匹配边界

\b——匹配数字字母下划线组成的单词的边界,即\w组成的单词的边界

image-20220707150016127

\B——匹配非单词的边界,作为\b的补集

image-20220707150417751

实例——匹配man

  • 如果直接使用man进行匹配就会匹配所有的man
1
man

image-20220707151053475

  • 使用\b可以识别出单词man
1
\bman\b

image-20220707151236835

()——组选择

括号称之为 正则表达式的 组选择。

就是把正则表达式匹配的内容里面 其中的某些部分 标记为某个组。

我们可以在正则表达式中标记 多个

为什么要有组的概念呢?因为我们往往需要提取已经匹配的 内容里面的 某些部分的信息。

前面,我们有个例子,从下面的文本中,选择每行逗号前面的字符串,也包括逗号本身。

1
2
3
苹果,苹果是绿色的
橙子,橙子是橙色的
香蕉,香蕉是黄色的

就可以这样写正则表达式 ^.*,

但是,如果我们要求 不包括逗号 呢?当然不能直接 这样写 ^.*,因为最后的逗号是特征所在,如果去掉它,就没法找逗号前面的内容了。但是把逗号放在正则表达式中,又会包含逗号。

解决问题的方法就是使用组选择符——()

我们这样写 ^(.*), ,结果如下

image-20220705141615050

  • 实用场景——提取姓名和手机号
1
2
3
刘玄德,手机号码15945678901
关云长,手机号码13945677701
张翼德,手机号码13845666901
1
^(.+),.+(\d{11})

image-20220705142806644

(?:)——非捕获组

有时候,我们并不需要捕获某个分组的内容,但是又想使用分组的特性。

这个时候就可以使用非捕获组(?:表达式),从而不捕获数据,还能使用分组的功能。例如:

image-20220706162232217

image-20220706162335795

\n——分组的回溯引用

正则表达式还提供了一种引用之前匹配分组的机制,有些时候,我们或许会寻找到一个子匹配,该匹配接下来会再次出现。

例如,要匹配一段 HTML 代码,比如:0123<font>提示</font>abcd,可能会编写出这样一段正则表达式:

image-20220706164839188

这确实可以匹配,不过可能还有另一种情况,如果数据改成这样:<font>提示</bar>

image-20220706164918351

在这里fontbar 明显不是一对正确的标签,但是我们编写的正则表达式还是将它们给匹配了,所以这个结果是错误的。

我们想让后面分组的正则也匹配font,但是现在所有形式的都会匹配。

那如果想让后面分组的正则和第一个分组的正则匹配同样的数据该如何做呢?

可以使用分组的回溯引用,使用\N可以引用编号为N的分组,因此上述例子的代码我们可以改为:

image-20220706165508254

通过这个例子,可以发现 \1 表示的就是第一个分组,在这里第一个分组匹配的是 font 所以\1 就代表font

另外一个例子,匹配<abc>…<cba>这种标签中间的内容

image-20220706165432774

(?=)——正向先行断言

正向先行断言:(?=表达式),指在某个位置向右看,表示所在位置右侧必须能匹配表达式

例如:

1
我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你

如果要取出喜欢两个字,要求这个喜欢后面有你,这个时候就要这么写:喜欢(?=你),这就是正向先行断言

image-20220706175306061

正向先行断言之密码强度验证

(?=.*?b)

给出一串字符串,它是abcba的每个字母间隙插入i构成的

1
iaibicibiai

使用下面的正则表达式对其进行匹配

1
i(?=.*?b)
  • i是要匹配的内容
  • (?=...)这个表示先行断言
  • .*?b这个表示以b结尾的字符串,?表示以非贪婪模式匹配,比如iaibibicib

使用这个正则表达式对该字符串进行匹配的结果如下

image-20220707092124132

它匹配了4个i

  • 第一个i被匹配是因为它后面接着aib,满足先行断言条件(?=.*?b)
  • 第二个i被匹配是因为它后面接着b,满足先行断言条件(?=.*?b)
  • 第三个i被匹配是因为它后面接着cib,满足先行断言条件(?=.*?b)
  • 第四个i被匹配是因为它后面接着b,满足先行断言条件(?=.*?b)
  • 第五个和第六个i没有被匹配,因为它后面没有再接上.*?b

(?=.*b)

换个正则表达式重新对上面的字符串进行匹配,去掉?断言中表达式改用贪婪模式,匹配的结果是一样的但是过程不一样

1
i(?=.*b)

image-20220707093445986

  • 第一个i被匹配是因为它后面接着aibicib,满足先行断言条件(?=.*b)
  • 第二个i被匹配是因为它后面接着bicib,满足先行断言条件(?=.*b)
  • 第三个i被匹配是因为它后面接着cib,满足先行断言条件(?=.*b)
  • 第四个i被匹配是因为它后面接着b,满足先行断言条件(?=.*b)
  • 第五个和第六个i没有被匹配,因为它后面没有再接上.*b

和上面的不同只是先行断言的判断条件改成了贪婪模式,所以会看最远的b

(?=.+b)

再改用正则表达式对字符串进行匹配,将*改为+,此时的第4个i将不会被匹配上

1
i(?=.+b)

image-20220707094028095

  • 第一个i被匹配是因为它后面接着aibicib,满足先行断言条件(?=.+b)
  • 第二个i被匹配是因为它后面接着bicib,满足先行断言条件(?=.+b)
  • 第三个i被匹配是因为它后面接着cib,满足先行断言条件(?=.+b)
  • 第四个i没有被匹配,因为它后面紧接着b.+b要求b前面至少要有一个字符,单独的b是不满足先行断言条件(?=.+b)

匹配null

将上面的i全部去掉,先行断言则会匹配null,因为断言表达式(?=...)前面没有接任何字符

image-20220707095347295

密码强度验证

请编写一个正则表达式进行密码强度的验证,要求密码必须:

  • 至少包含一个大写字母
  • 至少包含一个小写字母
  • 至少包含一个数字
  • 至少8个字符
1
(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.{8})

这个正则表达式是由4个先行断言复合而成,对null进行匹配,依次判断null后面是否满足4个先行断言

  • (?=.*[A-Z])匹配后面接着大写字母的null
  • (?=.*[a-z])匹配后面接着小写字母的null
  • (?=.*[0-9])匹配后面接着数字的null
  • (?=.{8})匹配后面接着8个字符的null
  • 合起来就是匹配后面接着大写字母、小写字母、数字和8个字符的null,即4个先行断言匹配的null的交集

image-20220707101633287

(?!)——反向先行断言

反向先行断言(?!表达式)的作用是保证右边不能出现某字符。

例如: 我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你

如果要取出喜欢两个字,要求这个喜欢后面没有你,这个时候就要这么写:喜欢(?!你),这就是反向先行断言

image-20220707103353049

  • 实例——匹配不是qq邮箱的邮箱
1
@(?!qq)

@作为关键词,匹配后面不接qq@

image-20220707104407976

(?<=)——正向后行断言

正向后行断言:(?<=表达式),指在某个位置向左看,表示所在位置左侧必须能匹配表达式

例如:如果要取出喜欢两个字,要求喜欢的前面有我后面有你,这个时候就要这么写:(?<=我)喜欢(?=你)

image-20220707105639993

(?<!)——反向后行断言

反向后行断言:(?<!表达式),指在某个位置向左看,表示所在位置左侧不能匹配表达式

例如:如果要取出喜欢两个字,要求喜欢的前面没有我后面没有你,这个时候就要这么写:(?<!我)喜欢(?!你)

image-20220707105809723

实例——匹配单dollar符号

请使用正则表达式匹配单dollar符号$...$中的数据。

1
(?<!\$)\$[^$]+\$(?!\$)

(?<!\$)\$表示\$符号左边不能有\$符号

\$(?!\$)表示\$符号右边不能有\$符号

[^$]+表示中间是非\$符号组成的字符串,并且非空

image-20220707110935726

同理匹配双dollar符号$$...$$中间的内容可以使用下面的正则表达式

1
(?<!\$)\$\$[^\$]+\$\$(?!\$)

实例——匹配所有的小数

请使用正则表达式匹配所有的小数

1
(?<!\..*)(?<=\d)\.(?=\d)(?!.*\.)
  • \.以小数点作为标识符
  • (?<!\..*)表示左边不能再有小数点
  • (?<=\d)表示小数点左边要有数字
  • (?=\d)表示小数点右边要有数字
  • (?!.*\.)表示小数点右边不能再有小数点

image-20220707141310824

注意:有些正则表达式网址会对(?<!.*)这个表达式报错

image-20220707141457593

re.split方法——字符串切分

字符串对象的 split 方法只适用于简单的字符串分割。 有时,你需要更加灵活的字符串切割。

比如,我们需要从下面字符串中提取武将的名字。

1
names = '关羽; 张飞, 赵云,马超, 黄忠  李逵'

我们发现这些名字之间, 有的是分号隔开,有的是逗号隔开,有的是空格隔开, 而且分割符号周围还有不定数量的空格

这时,可以使用正则表达式里面的 split 方法:

1
2
3
4
5
6
import re

names = '关羽; 张飞, 赵云, 马超, 黄忠 李逵'

namelist = re.split(r'[;,\s]\s*', names)
print(namelist)

正则表达式 [;,\s]\s* 指定了,分割符为 分号、逗号、空格 里面的任意一种均可,并且该符号周围可以有不定数量的空格。

image-20220705143820079

re.sub方法——字符串替换

字符串 对象的 replace 方法只适应于 简单的 替换。 有时,你需要更加灵活的字符串替换。

比如,我们需要在下面这段文本中 所有的 链接中 找到所以 /avxxxxxx/ 这种 以 /av 开头,后面接一串数字, 这种模式的字符串。

然后,这些字符串全部 替换为 /cn345677/

1
2
3
4
5
6
7
8
9
10
11
12
13
names = '''

下面是这学期要学习的课程:

<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是牛顿第2运动定律

<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是毕达哥拉斯公式

<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是切割磁力线
'''

被替换的内容不是固定的,所以没法用 字符串的replace方法。这时,可以使用正则表达式里面的 sub 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import re

names = '''

下面是这学期要学习的课程:

<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是牛顿第2运动定律

<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是毕达哥拉斯公式

<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是切割磁力线
'''

newStr = re.sub(r'/av\d+?/', '/cn345677/' , names)
print(newStr)

sub 方法也是替换 字符串, 但是被替换的内容 用 正则表达式来表示 符合特征的所有字符串。

第一个参数 /av\d+?/ 这个正则表达式,表示以 /av 开头,后面是一串数字,再以 / 结尾的 这种特征的字符串 ,是需要被替换的内容。

第二个参数,这里 是 '/cn345677/' 这个字符串,表示要替换上的内容。

第三个参数是 源字符串。

re.sub方法——指定替换函数

刚才的例子中,我们用来替换的是一个固定的字符串 /cn345677/。如果,我们要求,替换后的内容 的是原来的数字+6, 比如 /av66771949/ 替换为 /av66771955/ 。怎么办?这种更加复杂的替换,我们可以把 sub的第2个参数 指定为一个函数 ,该函数的返回值,就是用来替换的字符串。如下

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
import re

names = '''

下面是这学期要学习的课程:

<a href='https://www.bilibili.com/video/av66771949/?p=1' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是牛顿第2运动定律

<a href='https://www.bilibili.com/video/av46349552/?p=125' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是毕达哥拉斯公式

<a href='https://www.bilibili.com/video/av90571967/?p=33' target='_blank'>点击这里,边看视频讲解,边学习以下内容</a>
这节讲的是切割磁力线
'''

# 替换函数,参数是Match对象
def subFunc(match):
# Match对象 的 group(0) 返回的是整个匹配上的字符串,
src = match.group(0)

# Match对象 的 group(1) 返回的是第一个group分组的内容
number = int(match.group(1)) + 6
dest = f'/av{number}/'

print(f'{src} 替换为 {dest}')

# 返回值就是最终替换的字符串
return dest

newStr = re.sub(r'/av(\d+?)/', subFunc , names)
print(newStr)

其中的match对象由Python自动创建,每一个匹配都会创建一个match对象

获取组内字符串,如下

1
2
3
match.group(0) # 获取整个匹配字符串
match.group(1) # 获取第1个组内字符串
match.group(2) # 获取第2个组内字符串

Python 3.6 以后的版本 ,写法也可以更加简洁,直接像列表一样使用下标,如下

1
2
3
match[0]
match[1]
match[2]

上面这个例子中:

正则表达式 re.sub 函数执行时, 每发现一个匹配的子串就会:

  • 实例化一个 match对象。这个match 对象包含了这次匹配的信息, 比如:整个字符串是什么,匹配部分字符串是什么,里面的各个group分组 字符串是什么
  • 调用执行 sub函数的第2个参数对象,也就是调用回调函数subFunc,并且把刚才产生的 match 对象作为参数传递给 subFunc

Python re模块

  • 导入模块

    1
    import re
  • findall

    findall返回一个列表

    1
    2
    lst = re.findall(r"\d+", "移动客服10086,电信客服10000")
    print(lst, type(lst))
    1
    2
    # 返回一个列表
    ['10086', '10000'] <class 'list'>
  • finditer

    finditer返回一个迭代器,从迭代器中拿数据需要用.group()

    1
    2
    itr = re.finditer(r"\d+", "移动客服10086,电信客服10000")
    print(itr, type(itr))
    1
    2
    # 返回一个迭代器
    <callable_iterator object at 0x000001C373DF2B20> <class 'callable_iterator'>
    1
    2
    3
    4
    5
    for i in iter:
    print(i.group())
    # 输出
    10086
    10000
  • search

    search返回的结果是match对象,拿数据需要.group()

    search全文匹配,并且只返回找到的第一个结果

    1
    2
    sch = re.search(r"\d+", "移动客服10086,电信客服10000")
    print(sch.group())
    1
    10086
  • match

    match是从头开始匹配的,默认在正则前加了一个^,不是常用方法

    1
    2
    3
    4
    mch = re.match(r"\d+", "移动客服10086,电信客服10000")
    print(mch.group())

    # 输出会报错,因为mch并没有匹配到
  • 预加载正则表达式

    预加载正则表达式可以稍稍提高一点程序运行的效率,并且可以多次使用

    1
    2
    3
    4
    obj = re.compile(r"\d+")

    lst = obj.findall(r"\d+", "移动客服10086,电信客服10000")
    itr = obj.finditer(r"\d+", "移动客服10086,电信客服10000")
  • re.S.能匹配换行符

    1
    obj = re.compile(r"xxx", re.S)
  • 使用组

    使用?P<group_name>可以给组起名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import re

    s = """
    移动客服:10086;
    电信客服:10000;
    联通客服:10010。
    """

    obj = re.compile(r"^(?P<Name>.*客服).(?P<Number>\d{5}).$",re.M)

    itr = obj.finditer(s)

    for i in itr:
    print(i.group("Name"))
    print(i.group("Number"))
    1
    2
    3
    4
    5
    6
    7
    # 输出
    移动客服
    10086
    电信客服
    10000
    联通客服
    10010

单行匹配和多行匹配

1
2
3
4
5
6
7
8
9
re.compile(r"", <参数>)
re.S
# 表示单行模式
# 单行模式下无视换行符,即将整个字符串转换成一行
# 此时元字符点.可以匹配换行符
# 字符串只有一个开头^和一个结尾$
re.M
# 表示多行模式
# 多行模式下,每一行都有一个开头^和一个结尾$

正则替换

标题加一级

将所有的标题符号 #, ##, ###, …, 全部加一级

  • 使用 ()
  • 使用 $1 识别组, 作为替换的内容
1
2
3
4
5
# Find
(#+) ⬅BlankSpace

# Replace
$1# ⬅BlankSpace

替换

多个空行变一行

1
2
3
4
5
# Find
\n\n\n+

# Replace
\n\n

行尾空格

1
2
3
4
5
# Find
$

# Replace

mmseg–>mymmseg

1
2
3
4
# Find
(\bmmseg\b|\bmmengine\b)
# Replace
my$1

\b 用于匹配单词边界