Python 进阶:正则表达式

一、前言

本文主要梳理正则的匹配规则和常用的匹配方法,并通过大量小例子辅助说明。

环境说明:Anaconda3、Python 3.9、windows11 64位

二、匹配规则

2.1 关于匹配模式

模式(patterns)和被搜索的字符串既可以是 Unicode 字符串 (str) ,也可以是8位字节串 (bytes)。 但是二者不能混用:也就是说,不能用字节串模式匹配 Unicode 字符串,反之亦然;同理,替换操作时,替换字符串的类型也必须与所用的模式和搜索字符串的类型一致。

关于\的规则说明:
首先需要捋清楚,Python是Python,正则是正则,二者分别有一套关于字符的规则,其实功能上大同小异,都是使得字符拥有某种“超能力”或失去某种“超能力”。

  • Python中使用\作为转义符号,表示字符的特殊功能(如\n表示换行)或不引发特殊字符的功能(\\表示无特殊含义的反斜杠);
  • 正则表达式使用 \表示字符的特殊形式(如\d表示数字),或是允许在使用特殊字符时,不引发它们的特殊含义(如\\表示无特殊含义的反斜杠)。

由于Python和正则的规则,要匹配一个反斜杠字面值,用户可能必须写成 \\\\ 来作为模式字符串,因为正则表达式必须为\\,而每个反斜杠在普通 Python 字符串字面值中又必须表示为\\。 (该表示方式或许会导致理解障碍。)
官方推荐方法】当然,也可以使用Python 的原始字符串表示法r''r"\n"表示包含\n两个字符的字符串)去掉Python的\的特殊含义,再传递给正则表达式模式(patterns)。

import re
content = 'Python12\\34中文'

re_1 = re.findall(r'\d',content) # 使用Python的原始字符串表示法
re_2 = re.findall('\\d',content) # 不使用Python的原始字符串表示法
re_3 = re.findall('\d',content)  # 该写法也可以,具体逻辑未详,建议使用上面两种方法

print('%s\n%s\n%s'%(re_1,re_2,re_3))
# 结果都是:['1', '2', '3', '4']

注意:虽然以上re_3可以使用单个\,但是在匹配content\时,一定要用\\\\

2.2 关于flags参数

后续介绍的几个方法中,都有flags参数,该参数主要是控制匹配字符的规则变化。相关参数值和含义如下表:

参数值 含义 说明
re.A ASCII 让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII字符,只对Unicode样式有效,会被byte样式忽略。
re.I IGNORECASE 忽略大小写,[A-Z][a-z]同义
re.L LOCALE 语言依赖
re.M MULTILINE 多行模式,字符^匹配字符串的开始,和每一行的开始(换行符后面紧跟的符号);样式字符$匹配字符串尾,和每一行的结尾(换行符前面那个符号)。
re.S DOTALL .匹配全部字符,包含\\n
re.U 0,默认值 Unicode匹配,根据Unicode字符集解析字符。
re.X VERBOSE 冗长模式,支持注释和忽略空格,如以下两种形式结果相同
a = re.compile(r"“”
\d + # the integral part
\. # the decimal point
\d * # some fractional digits"“”, re.X)
b = re.compile(r"\d+\.\d*")
import re
content = 'Python\n12\\34中文'

re_1 = re.findall(r'\w+',content)               # 结果为:['Python', '12', '34中文']
re_2 = re.findall(r'\w+',content,flags=re.A)    # 结果为:['Python', '12', '34']
re_3 = re.findall(r'.+',content)                # 结果为:['Python', '12\\34中文']
re_4 = re.findall(r'.+',content,flags=re.S)     # 结果为:['Python\n12\\34中文']
re_5 = re.findall(r'[A-Z]+',content)            # 结果为:['P']
re_6 = re.findall(r'[A-Z]+',content,flags=re.I) # 结果为:['Python']

print('%s\n%s\n%s\n%s\n%s\n%s'%(re_1,re_2,re_3,re_4,re_5,re_6))

2.3 字符集[]

字符集[]中间的字符之间的关系是关系,只要满足其中一个即可定义为匹配成功。

匹配规则 释义
[abf] 表示该位置上的字符为a或者b或者f,即匹配成功
[a-z] 表示该位置上的字符在a~z之间,即匹配成功
[A-Z] 表示该位置上的字符在A~Z之间,即匹配成功,注意大小写之分
[a-zA-Z] 表示该位置上的字符在az或AZ之间,即匹配成功
[^a-z] 表示该位置上的字符不在a-z之间,即匹配成功,^为非的意思
[^1-9] 表示该位置上的字符在1~9之间,即匹配成功

注意:^看其放置的位置,会有不同的含义,放在字符集中表示的意思;放在匹配字符的开头还可以表示以跟在后面的字符串为开头。

2.4 概括字符集

返回结果,一次只表示一个字符

匹配规则 释义 等价于
. 默认模式下,除\\n的任意字符;re.S模式下任意字符
\d 表示该位置上的字符是数字,即匹配成功 [0-9]
\D 表示该位置上的字符是数字,即匹配成功 [^0-9]
\w 表示该位置上的字符是字母(含大小写)或_或数字或中文,即匹配成功。注:\\u4e00-\\u9fff是utf-8编码的中文字符。 [A-Za-z0-9_\u4e00-\u9fff]
\W 表示该位置上的字符是字母或_或数字或中文,即匹配成功 [^A-Za-z0-9_\u4e00-\u9fff]
\s 表示该位置上是不可见字符(空格、制表符\t、垂直制表符\v、回车符\r、换行符\n、换页符\f),即匹配成功 [\f\n\t\r\v]
\S 表示该位置上是不可见字符,即匹配成功 [^\f\n\t\r\v]
\b 匹配空字符串,但只在单词开始或结尾的位置。一个单词被定义为一个单词字符的序列。注意,通常\\b定义为\\w\\W字符之间,或者\\w和字符串开始/结尾的边界。(见3.5 re.split()的样式的空匹配)
\B \\b,匹配空字符串,但能在词的开头或者结尾,边界不在\\w\\W之间,而是在多个\\w之间或多个\\W之间。(见3.5 re.split()的样式的空匹配)

2.5 数量词

匹配规则 释义
{3} 表示{3}前面的一个字符出现3次
{3,8} 表示{3,8}前面的一个字符出现3-8次
? 表示?前面的一个字符出现0次或1次
+ 表示+前面的一个字符出现1次或无限多次
* 表示*前面的一个字符出现0次或无限多次

2.6 边界匹配符

匹配规则 释义
^ 表示只要是以^后面的字符开头的,即匹配成功
$ 表示只要是以$前面的字符结尾的,即匹配成功
() ()内的内容构成一个组,只要符合匹配规则就匹配成功,返回()内匹配成功的内容,如(\\d)表示匹配一个数字

2.7 衍生

普通模式下:
. 表示一个除换行符\n以外的所有字符,表示 . 重复0次或无限多次
.
放在一起就是匹配除换行符以外的任意字符无限多次

扫描二维码关注公众号,回复: 15634561 查看本文章
匹配规则 命名 释义
.* 贪婪模式 表示匹配除换行符以外的任意字符无限多次,一般是匹配后面跟的字符或组直到出现的最后一个
.*? 非贪婪模式 一般是匹配后面跟的字符或组直到出现的第一个

注:这两种匹配方式需要结合其后接的字符或组。
在进行对字符串进行匹配时,可以先使用replace("\n","")把换行符先去掉,或者改用re.S模式。

示例:

import re
content = '发布时间:2022年10月24日。'
# 贪婪模式 .* 会匹配到最后一个符号要求的值,也就是数字4,所以匹配到:2022年10月24
re_1 = re.findall(r'(\d.*\d)', content)    # 结果为:['2022年10月24']
# 非贪婪模式 .*? 从匹配到第一个数字开始,就寻找下一个数字,一找到便返回,所以最小单位是两个数字,也可以是两个数字间夹一些其他的字符
re_2 = re.findall(r'(\d.*?\d)', content)   # 结果为:['20', '22', '10', '24']
print('%s\n%s'% (re_1,re_2))

re_3 = re.findall(r'(\d.*?\d)', '9月10日') # 结果为:['9月1'],0之后没有满足要求的数字,所以匹配不成功
print(re_3)

# 混合使用
# .*? 碰到第一个数字2(括号里第一个\d),就停止匹配,接着 .* 一直匹配到最后一个数字4
re_4 = re.findall(r'.*?(\d.*\d)', content) # 结果为:['2022年10月24']
# .* 一直匹配到最后满足括号里要求的24,这里可以倒着看,先看括号里的内容(两个数字,中间可以有其他字符),然后到字符串中倒着看,最后两个数字是24,那匹配结果就是它了
re_5 = re.findall(r'.*(\d.*?\d)', content) # 结果为:['24']
print('%s\n%s'% (re_4,re_5))

三、常用六种方法

3.1 re.findall(‘匹配规则’ , ‘字符串’, flags=0)

只要符合匹配规则,就找出所有符合的字符串以列表形式返回。

3.1.1 直接匹配

可以只写需要匹配的字符所对应的规则。

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

# 普通字符
re_1 = re.findall(r'Python',content)    # 匹配Python
# 元字符c
re_2 = re.findall(r'P[ab]n',content)    # 匹配Pan或Pbn
re_3 = re.findall(r'P[a-c]n',content)   # 匹配Pan或Pbn或Pcn
re_4 = re.findall(r'P[^ab]n',content)   # 匹配P和n之间存在一个非a和b的字符的字符串,可以是大写字母、数字、_等
re_5 = re.findall(r'P[^a-c]n',content)  # 匹配P和n之间存在一个非a、b和c的字符的字符串
# 元字符+数量词
re_6 = re.findall(r'P[ab]{1,10}n',content)     # P和n之间的[ab]有1~10个均可匹配成功
re_7 = re.findall(r'P[a-c]{1,10}n',content)    # P和n之间的[a-c]有1~10个均可匹配成功
re_8 = re.findall(r'P[^ab]{1,10}n',content)    # P和n之间的非[ab]有1~10个均可匹配成功,Python可以匹配成功
re_9 = re.findall(r'P[^a-c]{1,10}n',content)   # P和n之间的非[a-c]有1~10个均可匹配成功,Python可以匹配成功
re_10 = re.findall(r'P[a-z]{1,10}n',content)   # P和n之间的有1~10个小写英文字母均可匹配成功,Python可以匹配成功
# 字符集
re_11 = re.findall(r'P\w{4}n',content)  # P和n之间的有4个大小写英文字母或_或数字均可匹配成功,Python可以匹配成功
re_12 = re.findall(r'P\w+n',content)    # P和n之间的有1到无限个大小写英文字母或_或数字均可匹配成功,Python可以匹配成功
# 边界匹配
re_13 = re.findall(r'^《P\w+n',content) # 开头是《P\w+n即匹配成功
re_14 = re.findall(r'P\w+n。$',content) # 结尾是P\w+n。即匹配成功
# 衍生匹配
re_15 = re.findall(r'P.*n',content)     # P后匹配到最后一个n才返回
re_16 = re.findall(r'P.*?n',content)    # P后匹配到第一个n即返回


print('1%s\n2%s\n3%s\n4%s\n5%s\n6%s\n7%s\n8%s\n9%s\n10%s\n11%s\n12%s\n13%s\n14%s\n15%s\n16%s'
      % (re_1,re_2,re_3,re_4,re_5,re_6,re_7,re_8,re_9,re_10,re_11,re_12,re_13,re_14,re_15,re_16))
# 结果如下:
# 1['Python', 'Python']
# 2[]
# 3[]
# 4[]
# 5[]
# 6[]
# 7[]
# 8['Python', 'Python']
# 9['Python', 'Python']
# 10['Python', 'Python']
# 11['Python', 'Python']
# 12['Python', 'Python']
# 13['《Python']
# 14['Python。']
# 15['Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python']
# 16['Python', 'Python']

3.1.2 组合查询的形式

没有组则返回整一个匹配结果,有组则返回组内的匹配结果。
没组和有一个组都返回一个列表,元素都是匹配成功的字符;如果是多个组,则将多个组的每一次成功匹配以元组返回作为列表的元素,结构如[(一组,二组,……),(一组,二组,……)……]

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

# 单个组
re_1 = re.findall(r'(\d.*\d)',content)    # 匹配组(\d.*\d),表示以数字开头,以数字结尾,中间可以是除换行以外的任意字符,匹配到最后一个数字
re_2 = re.findall(r'(\d.*?\d)',content)   # 匹配组(\d.*?\d),表示以数字开头,以数字结尾,中间可以是除换行以外的任意字符,遇到第二个数字即返回
print('1%s\n2%s'% (re_1,re_2,))
# 结果如下:
# 1['13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24']
# 2['13', '45', '47', '64', '28', '12', '74', '82', '59', '3,发布时间:2', '02', '2年1', '0月2']

re_3 = re.findall(r'(https://[\./\w]+)\W',content)  # 匹配链接,https:后面跟上 . / A-Za-z0-9_ 字符,直到第一个非 A-Za-z0-9_ 字符
print(re_3)  # 结果为:['https://blog.csdn.net/qq_45476428/article/details/127482593']

# 多个组
re_4 = re.findall(r'(https://[\./\w]+)\W.*?(\d+年.*?日)',content) # 匹配链接和日期
print(re_4)  # 结果为:[('https://blog.csdn.net/qq_45476428/article/details/127482593', '2022年10月24日')]

3.2 re.match(‘匹配规则’ , ‘字符串’, flags=0)

match()方法的参数和findall()是一样的,返回的结果是SRE_Match对象。

在匹配的时候,match()从字符串的第一个字符开始匹配,如果第一个未匹配到,直接返回None。所以如果将刚刚findall()的例子将findall都改为match,只有re_13 = re.findall('^《P\w+n',content)这个改为re_13 = re.match('^《P\w+n',content)之后可以匹配到结果,其他的都不可以,因为字符串不是以P开头,所以匹配不到。
基于该逻辑,一般使用match()方法,更倾向于使用..?放匹配规则的开头加组合查询的方法,将匹配的目标字符规则放到小括号中。

查看match()匹配的结果,一般需要借助group()groups()方法,直接打印会是对应的对象信息。

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

re_1 = re.match(r'.*?(Python)',content)    # 匹配第一个Python
re_2 = re.match(r'.*?(https://[\./\w]+)\W.*?(\d+年.*?日)',content) # 匹配链接和日期
print(re_1)               # 结果为:<re.Match object; span=(0, 7), match='《Python'>
print(re_1.group())       # 结果为:《Python
print(re_1.group(0))      # 结果为:《Python
print(re_1.group(1))      # 结果为:Python
print(re_1.groups())      # 结果为:('Python',)

print(re_2)               # 结果为:<re.Match object; span=(0, 115), match='《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://bl>
print(re_2.group())       # 结果为:《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日
print(re_2.group(0))      # 结果为:《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日
print(re_2.group(1))      # 结果为:https://blog.csdn.net/qq_45476428/article/details/127482593
print(re_2.group(2))      # 结果为:2022年10月24日
print(re_2.groups())      # 结果为:('https://blog.csdn.net/qq_45476428/article/details/127482593', '2022年10月24日')

关于group()group()的对比说明:

方法 说明
group() 整一个匹配规则,即引号内的所有内容
group(0) 同group()
group(1) 引号内的第1个小括号内匹配的内容(如有)
group(2) 引号内的第2个小括号内匹配的内容(如有)
group(3) 引号内的第3个小括号内匹配的内容(如有)
groups() 以元组返回所有小括号内的匹配结果

3.3 re.search(‘匹配规则’ , ‘字符串’, flags=0)

search()match()语法和用法一样。
不同的是,match()只要字符串的【第一个字符】不符合【匹配规则】就返回None。而search() 则是对整一个字符串进行匹配,整个字符串没有符合【匹配规则】才返回None。

可以将match()中的例子中,改方法名为search,并去掉小括号前面的.*?,然后对比一下结果。
从结果中可以看到,search()match()一样只匹配了一次,便返回了。而findall()会匹配所有可能的结果。

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

re_1 = re.search(r'(Python)',content)    # 匹配第一个Python
re_2 = re.search(r'(https://[\./\w]+)\W.*?(\d+年.*?日)',content) # 匹配链接和日期
print(re_1)               # 结果为:<re.Match object; span=(1, 7), match='Python'>
print(re_1.group())       # 结果为:Python
print(re_1.group(0))      # 结果为:Python
print(re_1.group(1))      # 结果为:Python
print(re_1.groups())      # 结果为:('Python',)

print(re_2)               # 结果为:<re.Match object; span=(39, 115), match='https://blog.csdn.net/qq_45476428/article/details>
print(re_2.group())       # 结果为:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日
print(re_2.group(0))      # 结果为:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日
print(re_2.group(1))      # 结果为:https://blog.csdn.net/qq_45476428/article/details/127482593
print(re_2.group(2))      # 结果为:2022年10月24日
print(re_2.groups())      # 结果为:('https://blog.csdn.net/qq_45476428/article/details/127482593', '2022年10月24日')

3.4 re.sub(‘匹配规则’ , ‘替换后字符’ , ‘字符串’, count = 0, flags=0)

sub()能实现的功能是匹配出结果并替换掉内容,使用的匹配机制也是全文匹配所有可能的结果,同findall()。如果是一个比较简单的字符,也可以通过replace()替换。

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

# 将所有Python替换为java
re_1 = re.sub(r'Python','java',content)
re_2 = content.replace('Python','java')
print('%s\n%s'%(re_1,re_2))  # 结果一致:《java 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:java。

sub()的第四个参数是count,默认为0,表示无论匹配成功多少个字符,都替换成指定的字符;当不为0时,假设为整数n,则表示无论匹配成功多少个字符,最多只将前n个匹配成功的字符替换成指定字符。

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

# 通过参数count=1将第一个Python替换为java
re_3 = re.sub(r'Python','java',content,1) # 一般建议使用count=1,以免忘记参数值含义
print(re_3))  # 结果为:《java 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。

由于sub()是将规则全部匹配,不论是否加括号(可以通过以下代码re_4re_5对比),所以当匹配规则不能直接通过单个字符匹配时,需要加一些标识,在替换后的字符串也要加上相关标识,如下代码re_6识别出文章的名称然后替换为另外一个名称,但是要保留书名号。

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

# 对比括号作用
re_4 = re.sub(r'《Python.*。','java',content)
re_5 = re.sub(r'《(Python).*。','java',content)
print('%s\n%s'% (re_4,re_5))
# 结果都是:java

# 替换文章名称
re_6 = re.sub(r'《.*?》','《正则讲义》',content)
print(re_6)   # 结果为:《正则讲义》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。

sub()的匹配规则中的括号虽然不能直接匹配出来,但是并非无用。可以通过编码(\+数字)来指定对应的匹配结果,注意,这里的**\**表示一个反斜杠,由于在Python中它有转义含义,所以,需要使用**\\**表示一个反斜杠。
具体例子如下:
\1表示第1个括号的匹配结果,\2表示第2个括号的匹配结果,以此类推,\N表示第N个括号的匹配结果。当然前提是要存在对应数量的匹配组。
替换字符串在调用前面的匹配结果的时候,也可以使用\g<N>N表示第N个括号的匹配结果。
指定匹配组,还可以在编写规则时指定自定义名称,如下re_9,使用(?P<自定义名称>匹配规则)定义匹配组的名称。
除了在替换字符串中使用别名指定匹配组,还可以在匹配规则中使用,如下代码re_10re_11,需要注意的是不能使用自定义名称。
简单来讲,**\N**是通用的,可以记住该用法即可。

import re
content = '《Python 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。'

# 替换第一个Python为java
re_7 = re.sub(r'《(Python)(.*。)','《java\\2',content)
re_8 = re.sub(r'《(Python)(.*。)','《java\g<2>',content)
print('%s\n%s'% (re_7,re_8))
# 结果都是:《java 基础合集13:错误的调试和处理》是我最近发布的一篇推文,链接:https://blog.csdn.net/qq_45476428/article/details/127482593,发布时间:2022年10月24日,开发语言:Python。

# 命名匹配规则组
re_9 = re.sub(r'《(?P<language>Python)(?P<other_str>.*。)','《java\g<other_str>',content)
print(re_9)  # 结果同re_8

# 在匹配规则中也可使用\N,匹配两个Python之间的所有字符替换为Python
re_10 = re.sub(r'《(Python).*\\1','\\1',content)
re_11 = re.sub(r'《(?P<language>Python).*\\1','\\1',content)
print('%s\n%s'% (re_10,re_11)) # 结果都是:Python。

sub()的第二个参数还可以是一个函数。
以下代码为例,先定义一个函数判断分数的等级,然后将函数名传递给第二个参数repl,也就是替换字符串参数。
在判断分数时,有两个细节点需要注意,调用sub()时传递的值是需要通过group()获取对应的数字;此时数字是字符串格式,需要转化为整型或浮点型。
这里不是调用(调用需要传值),而是直接把函数名传进去,正则能实现自动给其传递参数并返回。

import re
def judge_level(value):
    # 用group方法获取到匹配结果
    value = value.group()
    if float(value) >= 85:
        return '优'
    elif float(value) >= 60:
        return '良'
    else:
        return '差'
content = '{"小A":59, "小B":66, "小C":83, "小D": 98}'
re_12 = re.sub(r'\d+', judge_level, content)
print(re_12)  # 结果为:{"小A":差, "小B":良, "小C":良, "小D": 优}

不知道作为读者,你看到这个返回的结果是有什么感觉?
看似字典,又非字典,这不是个好的数据结构,如果要转化为字典使用该数据还需要进行进一步处理。
一种方法是对转化后的字符串进行替换,如下re_13
一种方法是在函数中给每一个评级加上一个引号,然后再进行转化,如下re_14

# 对转化后的字符串re_12进行替换
re_13 = re.sub(r'(\W+)(\w+)([,}])', ':\\1"\\2"\\3', re_12)
print(re_13)

# 修改判断函数,加上引号
import re
def judge_level(value):
    # 用group方法获取到匹配结果
    value = value.group()
    if float(value) >= 85:
        return '"优"'
    elif float(value) >= 60:
        return '"良"'
    else:
        return '"差"'
content = '{"小A":59, "小B":66, "小C":83, "小D": 98}'
re_14 = re.sub(r'\d+', judge_level, content)
print(re_14)  

# 结果都是:{"小A":"差", "小B":"良", "小C":"良", "小D": "优"}

# 注意:以上得到的结果是一个字典结构的字符串,转化字典可以通过json.loads()
import json
dict_re_13 = json.loads(re_13)
dict_re_14 = json.loads(re_14)

3.5 re.split(‘匹配规则’ , ‘字符串’, maxsplit=0, flags=0)

如果maxsplit非零, 最多进行maxsplit次分隔, 剩下的字符全部返回到列表的最后一个元素。
如果分隔符匹配规则匹配到字符串的开始,结果将会以一个空字符串开始,对于结尾也是一样(如下例子)。

import re
content = '..Words, words, words.'

re_1 = re.split(r'\W+',content)    # 结果为:['', 'Words', 'words', 'words', '']
re_2 = re.split(r'(\W+)',content)  # 结果为:['', '..', 'Words', ', ', 'words', ', ', 'words', '.', '']
re_3 = re.split(r'\W+',content,2)  # 结果为:['', 'Words', 'words, words.']

print('%s\n%s\n%s'% (re_1,re_2,re_3))

样式的空匹配,仅在与前一个空匹配不相邻时才会拆分字符串。

import re
content = 'Words, words, words.'

re_1 = re.split(r'\b', content) # 结果为:['', 'Words', ', ', 'words', ', ', 'words', '.']
re_2 = re.split(r'\B', content) # 结果为:['W', 'o', 'r', 'd', 's,', ' w', 'o', 'r', 'd', 's,', ' w', 'o', 'r', 'd', 's.', '']
print('%s\n%s'% (re_1,re_2))

3.6 re.compile(‘匹配规则’, flags=0)

以上介绍的5种方法都可以通过re.compile()加上对应函数的方法实现相同的目标。compile()更像是一个临时的容器,存放匹配规则,具体匹配结果需要根据对应的方法来确定。
具体看以下例子:

import re
content = 'Python12\\34中文'

patterns = re.compile(r'([A-Za-z]+)(\d+)')
# findall()
re_1_1 = patterns.findall(content)
re_1_2 = re.findall(r'([A-Za-z]+)(\d+)',content)
print('%s\n%s'%(re_1_1,re_1_2))   # 结果都是:[('Python', '12')]
# match()
re_2_1 = patterns.match(content).groups()
re_2_2 = re.match(r'([A-Za-z]+)(\d+)',content).groups()
print('%s\n%s'%(re_2_1,re_2_2))   # 结果都是:('Python', '12')
# search()
re_3_1 = patterns.search(content).groups()
re_3_2 = re.search(r'([A-Za-z]+)(\d+)',content).groups()
print('%s\n%s'%(re_3_1,re_3_2))   # 结果都是:('Python', '12')
# sub()
re_4_1 = patterns.sub('',content)
re_4_2 = re.sub(r'([A-Za-z]+)(\d+)','',content)
print('%s\n%s'%(re_4_1,re_4_2))   # 结果都是:\34中文
# split()
re_5_1 = patterns.split(content)
re_5_2 = re.split(r'([A-Za-z]+)(\d+)',content)
print('%s\n%s'%(re_5_1,re_5_2))   # 结果都是:['', 'Python', '12', '\\34中文']

# 也可以采用链式表示
re_1_3 = re.compile(r'([A-Za-z]+)(\d+)').findall(content)         # 同re_1_1
re_2_3 = re.compile(r'([A-Za-z]+)(\d+)').match(content).groups()  # 同re_2_1
re_3_3 = re.compile(r'([A-Za-z]+)(\d+)').search(content).groups() # 同re_3_1
re_4_3 = re.compile(r'([A-Za-z]+)(\d+)').sub('',content)          # 同re_4_1
re_4_3 = re.compile(r'([A-Za-z]+)(\d+)').split(content)           # 同re_5_1

四、场景

4.1 匹配数字

import re
content = '评分:9.1分,评论数:123456789'
# 单个数
re_1 = re.findall(r'\d', content)
print(re_1)  # 结果为:['9', '1', '1', '2', '3', '4', '5', '6', '7', '8', '9']

# 多个数
re_2 = re.findall(r'\d+', content)
print(re_2)  # 结果为:['9', '1', '123456789']

# 取完整评分和评论数,没有标准答案~
re_3 = re.findall(r'(\d{1,2}\.\d{1,2}).*?(\d+)', content)
print(re_3)  # 结果为:[('9.1', '123456789')]

4.2 匹配中文

import re

# 所有中文
content = '评分:9.1分,评论数:123456789'
re_1 = re.findall(r'[\u4e00-\u9fff]+', content)
print(re_1)  # 结果为:['评分', '分', '评论数']

# 只取关键中文字眼,评分和 评论数
re_2 = re.findall(r'([\u4e00-\u9fff]+):.*?([\u4e00-\u9fff]+):', content)
print(re_2)  # 结果为:[('评分', '评论数')]

爬过QQ音乐的小伙伴可能会遇到过类似以下的字符串,通过以上方法取出中文便十分简单。

[ti#58;晴天]#10;[ar#58;周杰伦]#10;

4.3 匹配链接

可用于爬虫数据提取网址。

import re
content = '<a href="http://news.baidu.com" target="_blank" class="mnav c-font-normal c-color-t">新闻</a>'

re_1 = re.findall(r'<a href="(http.*com)".*',content)
print(re_1) # 结果为:['http://news.baidu.com']

补充:startswith('匹配字符',begin,end)endswith('匹配字符',begin,end)
二者的参数一样,不同在于startswith()匹配开头,而endswith()匹配结尾。

  • begin:被匹配字符串开始位置的索引,索引逻辑同字符串
  • end:被匹配字符串结束位置的索引,索引逻辑同字符串

beginend相当于是给被匹配字符串进行切片,即被匹配字符串[begin : end]

content = 'http://news.baidu.com'
print(content.startswith('http',0,4)) # True
print(content.endswith('.com',-5,))   # True

4.4 去掉不可见字符

可用于爬虫字符串数据处理。

import re
content = '  评分:  9.1  分  \n  评论数:  123456789   '

re_1 = re.sub(r'\s+','', content) # 匹配不可见字符
print(re_1)  # 结果为:评分:9.1分评论数:123456789

# 将分和评论数分开
re_2 = re.sub(r'\s+','', content.replace('\n',',')) # 匹配不可见字符
print(re_2) # 结果为:评分:9.1分,评论数:123456789

五、小结

正则表达式.png

猜你喜欢

转载自blog.csdn.net/qq_45476428/article/details/127597208