一、正则表达式作用
提取文本信息。

正则表达式验证网站:https://regex101.com/
Python学习网站:https://www.byhy.net/tut/py/extra/regex/
二、正则表达式基础语法
2.1 点-匹配所有字符
. 表示要匹配除了 换行符 之外的任何 单个 字符。

content = '''苹果是绿色的
橙子是橙色的
香蕉是黄色的
乌鸦是黑色的'''
import re
p = re.compile(r'.色')
for one in p.findall(content):
    print(one)2.2 星号-重复匹配任意次
* 表示匹配前面的子表达式任意次,包括0次。
比如,你要从下面的文本中,选择每行逗号后面的字符串内容,包括逗号本身。注意,这里的逗号是中文的逗号。
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
猴子,就可以这样写正则表达式 ,.* 。
在这里,紧跟在.后面,表示任意字符可以出现任意次, 所以整个表达式的意思就是在逗号后面的所有字符,包括逗号

2.3 加号-重复匹配多次
+ 表示匹配前面的子表达式一次或多次,不包括0次。
比如,下面的文本中,最后一行逗号后面 没有内容,就不要选择了。
苹果,是绿色的
橙子,是橙色的
香蕉,是黄色的
乌鸦,是黑色的
猴子,就可以这样写正则表达式 ,.+ 。

2.4 问号-匹配0-1次
? 表示匹配前面的子表达式0次或1次。
比如,还是上面的例子,你要从文本中,选择每行逗号后面的1个字符,也包括逗号本身。
苹果,绿色的
橙子,橙色的
香蕉,黄色的
乌鸦,黑色的
猴子,就可以这样写正则表达式 ,.? 。

2.5 花括号-匹配指定次数
花括号表示前面的字符匹配指定的次数 。
比如 ,下面的文本
红彤彤,绿油油,黑乎乎,绿油油油油表达式 油{3} 就表示匹配 连续的 油 字 3次
表达式 油{3,4} 就表示匹配 连续的 油 字 至少3次,至多 4 次
就只能匹配 后面的,如下所示:

2.6 贪婪模式和非贪婪模式
我们要把下面的字符串中的所有html标签都提取出来,
source = '<html><head><title>Title</title>'得到这样的一个列表
['<html>', '<head>', '<title>', '</title>']很容易想到使用正则表达式 <.*>
写出如下代码
source = '<html><head><title>Title</title>'
import re
p = re.compile(r'<.*>')
print(p.findall(source))但是运行结果,却是
['<html><head><title>Title</title>']怎么回事? 原来 在正则表达式中, ‘*’, ‘+’, ‘?’ 都是贪婪地,使用他们时,会尽可能多的匹配内容,
所以, <.*> 中的 星号(表示任意次数的重复),一直匹配到了 字符串最后的 </title> 里面的e。
解决这个问题,就需要使用非贪婪模式,也就是在星号后面加上 ? ,变成这样 <.*?>
代码改为
source = '<html><head><title>Title</title>'
import re
# 注意多出的问号
p = re.compile(r'<.*?>')
print(p.findall(source))再运行一遍,就可以了。
2.7 对元字符的转义
反斜杠 \ 在正则表达式中有多种用途。
2.7.1 要搜索的内容包含元字符
比如,我们要在下面的文本中搜索 所有点前面的字符串,也包含点本身
苹果.是绿色的
橙子.是橙色的
香蕉.是黄色的如果,我们这样写正则表达式 .*. ,显然是有问题的。
因为 点 是一个 元字符, 直接出现在正则表达式中,表示匹配任意的单个字符, 不能表示 . 这个字符本身的意思了。
怎么办呢?
如果我们要搜索的内容本身就包含元字符,就可以使用 反斜杠进行转义。
这里我们就应用使用这样的表达式: .*\.
示例,Python程序如下
content = '''苹果.是绿色的
橙子.是橙色的
香蕉.是黄色的'''
import re
p = re.compile(r'.*\.')
for one in  p.findall(content):
    print(one)运行结果如下
苹果.
橙子.
香蕉.2.7.2 匹配某种字符类型
反斜杠后面接一些字符,表示匹配 某种类型 的一个字符。
比如:
\d 匹配**0-9之间任意一个数字字符**,等价于表达式 [0-9]
\D 匹配任意一个不是0-9之间的数字字符,等价于表达式 [^0-9]
\s 匹配任意一个空白字符,包括 空格、tab、换行符等,等价于表达式 [\t\n\r\f\v]
\S 匹配任意一个非空白字符,等价于表达式 [^ \t\n\r\f\v]
\w 匹配任意一个文字字符,包括大小写字母、数字、下划线,等价于表达式 [a-zA-Z0-9_]【经常用于用户注册】
缺省情况也包括Unicode文字字符,如果指定ASCII码标记:p = re.compile(r'\w{2,4}',re.A),则只包括ASCII字母
\W 匹配任意一个非文字字符,等价于表达式 [^a-zA-Z0-9_]
反斜杠也可以用在方括号里面,比如[\s,.]表示匹配 : 任何空白字符, 或者逗号,或者点
2.8 方括号-匹配几个字符之一
方括号表示要匹配 指定的几个字符之一 。
比如:
[abc] 可以匹配 a, b或者c里面的任意一个字符。等价于 [a-c] 。
[a-c] 中间的 - 表示一个范围从a到c。
如果你想匹配所有的小写字母,可以使用 [a-z]。
一些元字符在方括号内失去了魔法,变得和普通字符一样了。
比如:
[akm.] 匹配 a k m . 里面任意一个字符
这里 . 在括号里面不再表示匹配任意字符了,而就是表示匹配 . 这个 字符
如果在方括号中使用 ^ , 表示 非 方括号里面的字符集合。
比如
content = 'a1b2c3d4e5'
import re
p = re.compile(r'[^\d]' )
for one in  p.findall(content):
    print(one)[^\d] 表示,选择非数字的字符
输出结果为:
a
b
c
d
e2.9 起始、结尾位置和单行、多行模式
^ 表示匹配文本的 开头 位置,$ 表示匹配文本的 结尾 位置。
正则表达式可以设定 单行模式 和 多行模式
如果是 单行模式 ,表示匹配 整个文本 的开头位置。
如果是 多行模式 ,表示匹配 文本每行 的开头位置。
比如,下面的文本中,每行最前面的数字表示水果的编号,最后的数字表示价格
001-苹果价格-60,
002-橙子价格-70,
003-香蕉价格-80,如果我们要提取所有的水果编号,用这样的正则表达式 ^\d+
上面的正则表达式,使用在Python程序里面,如下所示
content = '''001-苹果价格-60
002-橙子价格-70
003-香蕉价格-80'''
import re
p = re.compile(r'^\d+', re.M)
for one in  p.findall(content):
    print(one)注意:**
compile的第二个参数re.M,指明了使用多行模式**。
运行结果如下
001
002
003如果,去掉compile的第二个参数re.M, 运行结果如下
001就只有第一行了。
因为单行模式下,^ 只会匹配整个文本的开头位置,$ 表示匹配文本的 结尾 位置。
如果是 单行模式 ,表示匹配 整个文本 的结尾位置。
如果是 多行模式 ,表示匹配 文本每行 的结尾位置。
比如,下面的文本中,每行最前面的数字表示水果的编号,最后的数字表示价格
001-苹果价格-60,
002-橙子价格-70,
003-香蕉价格-80,如果我们要提取所有的水果编号,用这样的正则表达式 \d+$
对应代码
content = '''001-苹果价格-60
002-橙子价格-70
003-香蕉价格-80'''
import re
p = re.compile(r'\d+$', re.MULTILINE)
for one in  p.findall(content):
    print(one)注意,compile 的第二个参数re.MULTILINE,指明了使用多行模式,
运行结果如下
60
70
80如果,去掉compile的第二个参数re.MULTILINE, 运行结果如下
80就只有最后一行了。
因为单行模式下,$只会匹配整个文本的结束位置。
2.10 竖线-匹配其中之一
竖线表示匹配其中之一 。
比如 ,

特别要注意的是, 竖线在正则表达式的优先级是最低的, 这就意味着,竖线隔开的部分是一个整体
比如 绿色|橙 表示 要匹配是 绿色 或者 橙 ,
而不是 绿色 或者 绿橙。
2.11 括号-分组
括号称之为正则表达式的组选择。
组 就是把正则表达式匹配的内容里面 其中的某些部分 标记为某个组。
我们可以在正则表达式中标记多个组。
为什么要有组的概念呢?因为我们往往需要提取已经匹配的 内容里面的 某些部分的信心。
前面,我们有个例子,从下面的文本中,选择每行逗号前面的字符串,也 包括逗号本身 。
苹果,苹果是绿色的
橙子,橙子是橙色的
香蕉,香蕉是黄色的就可以这样写正则表达式 ^.*, 。
但是,如果我们要求 不要包括逗号 呢?
当然不能直接 这样写 ^.*
因为最后的逗号是特征所在, 如果去掉它,就没法找 逗号前面的了。
但是把逗号放在正则表达式中,又会包含逗号。
解决问题的方法就是使用 组选择符 : 括号。
我们这样写 ^(.*), ,结果如下

可以发现,我们把要从整个表达式中提取的部分放在括号中,这样 水果 的名字 就被单独的放在组group中了。
对应的Python代码如下
content = '''苹果,苹果是绿色的
橙子,橙子是橙色的
香蕉,香蕉是黄色的'''
import re
p = re.compile(r'^(.*),', re.MULTILINE)
for one in  p.findall(content):
    print(one)分组还可以多次使用。
比如,我们要从下面的文本中,提取出每个人的 名字 和对应的 手机号
张三,手机号码15945678901
李四,手机号码13945677701
王二,手机号码13845666901可以使用这样的正则表达式 ^(.+),.+(\d{11})
可以写出如下的代码
content = '''张三,手机号码15945678901
李四,手机号码13945677701
王二,手机号码13845666901'''
import re
p = re.compile(r'^(.+),.+(\d{11})', re.MULTILINE)
for one in p.findall(content):
    print(one)返回的是多个元组,元组包含有多个元素。
当有多个分组的时候,我们可以使用 (?P<分组名>...) 这样的格式,给每个分组命名。
这样做的好处是,更方便后续的代码提取每个分组里面的内容
比如
content = '''张三,手机号码15945678901
李四,手机号码13945677701
王二,手机号码13845666901'''
import re
p = re.compile(r'^(?P<name>.+),.+(?P<phone>\d{11})', re.MULTILINE)
for match in p.finditer(content):
    print(match.group('name'))
    print(match.group('phone'))三、正则表达式高级应用
3.1 切割字符串
字符串 对象的 split 方法只适用于 简单的字符串分割。 有时,你需要更加灵活的字符串切割。
比如,我们需要从下面字符串中提取武将的名字。
names = '关羽; 张飞, 赵云,马超, 黄忠  李逵'我们发现这些名字之间, 有的是分号隔开,有的是逗号隔开,有的是空格隔开, 而且分割符号周围还有不定数量的空格
这时,可以使用正则表达式里面的split方法:
import re
names = '关羽; 张飞, 赵云,   马超, 黄忠  李逵'
namelist = re.split(r'[;,\s]\s*', names)
print(namelist)正则表达式 [;,\s]\s* 指定了,分割符为 分号、逗号、空格 里面的任意一种均可,并且 该符号周围可以有不定数量的空格。
3.2 指定替换函数
我们要求,替换后的内容 的是原来的数字+6, 比如 /av66771949/ 替换为 /av66771955/ 。
这种更加复杂的替换,我们可以把 sub的第2个参数 指定为一个函数 ,该函数的返回值,就是用来替换的字符串。
如下
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.group(0) # 获取整个匹配字符串
match.group(1) # 获取第1个组内字符串
match.group(2) # 获取第2个组内字符串Python 3.6以后的版本 ,写法也可以更加简洁,直接像列表一样使用下标,如下
match[0]
match[1]
match[2]上面这个例子中:
正则表达式re.sub函数执行时, 每发现一个 匹配的子串, 就会:
- 实例化一个 - match对象- 这个 - match对象包含了这次匹配的信息, 比如:整个字符串是什么,匹配部分字符串是什么,里面的各个group分组 字符串是什么
- 调用执行 - sub函数的第2个参数对象,也就是调用回调函数- subFunc- 并且把刚才产生的 - match对象作为参数传递给- subFunc
 
                        
                        