正则表达式使用基础
前言:
最近在学习python爬虫,基础需要学习正则表达式,正好借此机会详细学习一下这部分内容,也是比较简单,本篇文章介绍一下常用的一些正则规则和函数,后面附有一些练习代码。
正则各语言中使用都大同小异,由于最近学的是py,所以代码实例就用python3.8。
话不多说下面开干。
一、正则基础:
概述: 正则表达式,又称规则表达式
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern)
正则匹配是一个 模糊的匹配(不是精确匹配)
在python中使用正则,需要用到re模块,各语言各不相同,但是正则用法大同小异,只需根据语言不同调用不同的包即可。
**re:**python自1.5版本开始增加了re模块,该模块提供了perl风格的正则表达式模式,re模块使python语言拥有了所有正则表达式的功能。
如下四个方法经常使用,后面会详细介绍四个方法的使用。
- match()
- search()
- findall()
- finditer()
二、正则表达式
1、匹配单个字符与数字
匹配 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符,当flags被设置为re.S时,可以匹配包含换行符以内的所有字符 |
[] | 里面是字符集合,匹配[]里任意一个字符 |
[0123456789] | 匹配任意一个数字字符 |
[0-9] | 匹配任意一个数字字符 |
[a-z] | 匹配任意一个小写英文字母字符 |
[A-Z] | 匹配任意一个大写英文字母字符 |
[A-Za-z] | 匹配任意一个英文字母字符 |
[A-Za-z0-9] | 匹配任意一个数字或英文字母字符 |
[^text] | []里的^称为脱字符,表示非,匹配不在[]内的任意一个字符 |
^[text] | 以[]中内的某一个字符作为开头 |
\d | 匹配任意一个数字字符,相当于[0-9] |
\D | 匹配任意一个非数字字符,相当于[^0-9] |
\w | 匹配字母、下划线、数字中的任意一个字符,相当于[0-9A-Za-z_] |
\W | 匹配非字母、下划线、数字中的任意一个字符,相当于[^0-9A-Za-z_] |
\s | 匹配空白符(空格、换页、换行、回车、制表),相当于[ \f\n\r\t] |
\S | 匹配非空白符(空格、换页、换行、回车、制表),相当于[^ \f\n\r\t] |
2、匹配锚字符
锚字符:用来判定是否按照规定开始或者结尾
匹配 | 说明 |
---|---|
^ | 行首匹配,和[]里的^不是一个意思 |
$ | 行尾匹配 |
\A | 匹配字符串的开始,和^的区别是\A只匹配整个字符串的开头,即使在re.M模式下也不会匹配其他行的行首 |
\Z | 匹配字符串的结尾,和$的区别是\Z只匹配整个字符串的结尾,即使在re.M模式下也不会匹配其他行的行尾 |
3、限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。
匹配 | 说明 |
---|---|
(xyz) | 匹配括号内的xyz,作为一个整体去匹配 一个单元 子存储 |
x? | 匹配0个或者1个x,非贪婪匹配 |
x* | 匹配0个或任意多个x |
x+ | 匹配至少一个x |
x{n} | 确定匹配n个x,n是非负数 |
x{n,} | 至少匹配n个x |
x{n,m} | 匹配至少n个最多m个x |
x|y | |表示或的意思,匹配x或y |
三、正则中的贪婪与非贪婪
-
贪婪模式
贪婪概念:匹配尽可能多的字符,有多少就匹配多少
- .+ 匹配换行符以外的字符至少一次
- .* 匹配换行符以外的字符任意次
- 例:
import re
str = "<b>加粗</b><b>加粗的</b><b>加粗的的</b>"
# 贪婪 一次性拿出符合要求的所有内容,findall也是当做一个内容取出
print(re.search('<b>.*</b>', str))# <re.Match object; span=(0, 30), match='<b>加粗</b><b>加粗的</b><b>加粗的的</b>'>
print(re.findall('<b>.*</b>', str)) # ['<b>加粗</b><b>加粗的</b><b>加粗的的</b>']
print(re.search('<b>.+</b>', '<b></b><b>b标签</b>')) # <re.Match object; span=(0, 17), match='<b></b><b>b标签</b>'>
print(re.search('<b>.*</b>', '<b>b标签</b><b>b标签</b><b>b标签</b><b>b标签</b>'))# <re.Match object; span=(0, 40), match='<b>b标签</b><b>b标签</b><b>b标签</b><b>b标签</b>'>
-
非贪婪模式
非贪婪概念:尽可能少的匹配称为非贪婪匹配,*?、+?即可
-
.+? 匹配换行符以外的字符至少一次 拒绝贪婪
-
.*? 匹配换行符以外的字符任意次 拒绝贪婪
-
- 例
import re
str = "<b>加粗</b><b>加粗的</b><b>加粗的的</b>"
# 非贪婪 一次只拿出一个
print(re.search('<b>.*?</b>', str)) # <re.Match object; span=(0, 9), match='<b>加粗</b>'>
print(re.findall('<b>.*?</b>', str)) # ['<b>加粗</b>', '<b>加粗的</b>', '<b>加粗的的</b>']
print(re.search('<b>.+?</b>', '<b>b标签</b><b>b标签</b>')) # <re.Match object; span=(0, 10), match='<b>b标签</b>'>
print(re.search('<b>.*?</b>', '<b>b标签</b><b>b标签</b><b>b标签</b><b>b标签</b>')) # <re.Match object; span=(0, 10), match='<b>b标签</b>'>
如上,我们感觉到,使用.* 与.*? 的情况比较多,.+与.+? 使用情况较少
四、re模块中常用函数
通用flags(修正符)
值 | 说明 |
---|---|
re.I | 是匹配对大小写不敏感 |
re.M | 多行匹配,影响到^和$ |
re.S | 使.匹配包括换行符在内的所有字符 |
通用函数
-
获取匹配结果
-
使用group()方法 获取到匹配的值
-
groups() 返回一个包含所有小组字符串的元组(也就是自存储的值),从 1 到 所含的小组号。
-
1、match()函数
-
原型
def match(pattern, string, flags=0)
-
功能
匹配成功返回 匹配的对象
匹配失败 返回 None
-
获取匹配结果
-
使用group()方法 获取到匹配的值
-
groups() 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。
-
-
*注意:从第一位开始匹配 只匹配一次
-
参数
参数 说明 pattern 匹配的正则表达式(一种字符串的模式) string 要匹配的字符串 flags 标识位,用于控制正则表达式的匹配方式 -
代码
res = re.match('\d{2}', '123') # 正则含义:\d匹配数字 {2}匹配两个 print(res) # match本身返回一个match对象 <re.Match object; span=(0, 2), match='12'> print(res.group()) # group 只返回匹配到的结果 输出:12 print(res.span()) # span 取到的字符区间 输出:(0, 2)
2、searce()函数
-
原型
def search(pattern, string, flags=0)
-
功能
扫描整个字符串string,并返回第一个pattern模式成功的匹配
匹配失败 返回 None
-
参数
参数 说明 pattern 匹配的正则表达式(一种字符串的模式) string 要匹配的字符串 flags 标识位,用于控制正则表达式的匹配方式 -
注意:
只要字符串包含就可以
*只匹配一次
-
示例
import re res = re.search('[a-z]', '131A3ab889s') # 正则含义:匹配小写a-z,只匹配一次(因为没限制次数,默认一次) print(res) # 输出:<re.Match object; span=(5, 6), match='a'> print(res.group()) # 输出: a # 为什么只显示一个字母呢,因为正则写几个就是几次,因为只写了一个[a-z]所以就是1次,如果写[a-z][a-z] 或者[a-z]{2} 那么就是2次,输出就是ab
-
注意
与search的区别
相同点:
都只匹配一次
不同点:
- search是在要匹配的字符串中 包含正则表达式的内容就可以
- match 必须第一位就开始匹配 否则匹配失败
3、findall()函数(返回列表)
-
原型
def findall(pattern, string, flags=0)
-
功能
扫描整个字符串string,并返回所有匹配的pattern模式结果的字符串列表
-
参数
参数 说明 pattern 匹配的正则表达式(一种字符串的模式) string 要匹配的字符串 flags 标识位,用于控制正则表达式的匹配方式 - 示例
# 先定义一个测试的字符串 模拟一个有换行符,大小写,网址的html字符串 myStr = """ <a herf = "http://www.baidu.com">百度</a> <A herf = "http://www.baidu.com">百度</A> <a herf = 'http://www.baidu.com'>百度</a> <a herf = 'http://www.tengxun.com'>腾 讯</a> """ # (1)给正则里面匹配的 加上圆括号会将括号里面的内容进行单独的返回 # .*? 也叫非贪婪匹配 print(re.findall('<a herf = "(.*?)">(.*?)</a>', myStr)) # 输出[('http://www.baidu.com', '百度')] 正则含义:取出<a herf = " 开头 ">结尾中间的一个内容 与 ">开头</a>'结尾 中间的一个内容,也就是herf属性里的值 和 a标签里的值 # 匹配大小写 多行匹配 可以匹配换行符 print(re.findall('(<a herf = [\'"](.*?)[\'"]>(.*?)</a>)', myStr, re.I|re.M|re.S)) # [('<a herf = "http://www.baidu.com">百度</a>', 'http://www.baidu.com', '百度'), ('<A herf = "http://www.baidu.com">百度</A>', 'http://www.baidu.com', '百度'), ("<a herf = 'http://www.baidu.com'>百度</a>", 'http://www.baidu.com', '百度'), ("<a herf = 'http://www.tengxun.com'>腾\n讯</a>", 'http://www.tengxun.com', '腾\n讯')] # 因为字符串中的herf标签同时含有双引号和单引号,所以我们要做一个区分,[\'"]表示匹配'或" 单引号放到正则里需要加\转义,否则和外面字符串的单引号就冲突了,(.*?)表示非贪婪匹配,匹配到的内容单独返回 用()包裹住的,都单独返回 #(2) 不区分大小写的匹配 print(re.findall('(<a herf = "(.*?)">(.*?)</a>)', myStr, re.I)) # [('<a herf = "http://www.baidu.com">百度</a>', 'http://www.baidu.com', '百度'), ('<A herf = "http://www.baidu.com">百度</A>', 'http://www.baidu.com', '百度')]
4、finditer()函数
-
原型
def finditer(pattern, string, flags=0)
-
功能
与findall()类似,返回一个迭代器
-
参数
参数 说明 pattern 匹配的正则表达式(一种字符串的模式) string 要匹配的字符串 flags 标识位,用于控制正则表达式的匹配方式 - 代码
import re
obj = re.findall("[a-z]", "abcdefg")
print(obj) # ['a', 'b', 'c', 'd', 'e', 'f', 'g']
obj = re.finditer("[a-z]", "abcdefg")
for i in obj:
print(i.group()) # 循环输出 a b c d e f g
print(obj)
5、split()函数
-
作用:切割字符串
-
原型:
def split(patter, string, maxsplit=0, flags=0)
-
参数
pattern 正则表达式
string 要拆分的字符串
maxsplit 最大拆分次数 默认拆分全部
flags 修正符
- 代码
import re
myStr = "abc1\rde\tdd23fg"
# 按照数字进行拆分
print(re.split("\d",myStr)) # ['abc', '\rde\tdd', '', 'fg']
print(re.split("\d",myStr,maxsplit=2)) # 最大拆的次数:2次 ['abc', '\rde\tdd', '3fg']
print(re.findall("\s",myStr)) # 匹配空白符 ['\r', '\t']
print(re.findall("\S",myStr)) # 取反 也就是匹配非空白符的 ['a', 'b', 'c', '1', 'd', 'e', 'd', 'd', '2', '3', 'f', 'g']
# 按照空白符进行拆分
print(re.split('\s',myStr)) # ['abc1', 'de', 'dd23fg']
6、修正符
-
作用
对正则进行修正
-
使用
search/match/findall/sub/subn/finditer 等函数 flags参数的使用
-
修正符
re.I 不区分大小写匹配
re.M 多行匹配 影响到^ 和 $ 的功能
re.S 使.可以匹配换行符 匹配任意字符
- 特别讲一下 re.M 影响到 ^和$ 的效果
在使用re.M时,有/n换行符的时候,^ 和 $会匹配到以换行符为分割的内容, 但是使用\A和\Z的话,就只会匹配一次,具体看代码:
# # \A 和 \Z 和^ $ 一样的 只有在re.M时有区别
myStr = """asadasdd1\nbsadasdd2\ncsadasdd3"""
print(re.findall('^[a-z]',myStr, )) # ['a']
print(re.findall('\A[a-z]',myStr)) # ['a']
print(re.findall('\d$',myStr)) # ['3']
print(re.findall('\d\Z',myStr)) # ['3']
# re.M
print(re.findall('^[a-z]',myStr, flags=re.M)) # ['a', 'b', 'c']
print(re.findall('\A[a-z]',myStr, flags=re.M)) # ['a']
print(re.findall('\d$',myStr, flags=re.M)) # ['1', '2', '3']
print(re.findall('\d\Z',myStr, flags=re.M)) # ['3']
五、正则高级
1、分组&起名称
-
概念
处理简单的判断是否匹配之外,正则表达式还有提取子串的功能,用()表示的就是要提取的分组
起名称语法:?P<要起的名称> -
代码
import re
myStr = "abcd1"
print(re.search("(?P<number>\d+)",myStr).group(0))
# 当有多个子存储的时候,使用别名比较方便
print(re.search("(?P<number>\d+)",myStr).group('number'))
-
说明
- 正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来
- group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串
2、编译
-
概念
当在python中使用正则表达式时,re模块会做两件事,一件是编译正则表达式,如果表达式的字符串本身不合法,会报错。另一件是用编译好的正则表达式提取匹配字符串
-
编译优点
如果一个正则表达式要使用几千遍,每一次都会编译,出于效率的考虑进行正则表达式的编译,就不需要每次都编译了,节省了编译的时间,从而提升效率
-
compile()函数
-
原型
def compile(pattern, flags=0)
-
作用
将pattern模式编译成正则对象
-
参数
参数 说明 pattern 匹配的正则表达式(一种字符串的模式) flags 标识位,用于控制正则表达式的匹配方式 -
flags
值 说明 re.I 是匹配对大小写不敏感 re.M 多行匹配,影响到^和$ re.S 使.匹配包括换行符在内的所有字符 -
返回值
编译好的正则对象
-
示例
import re pattern = re.compile("^1[3-9][0-9]{9}") # 用compile编译一个正则 print(pattern.search('15012022202a')) # 直接用编译好的正则调用函数,里面放要匹配的字符串即可 <re.Match object; span=(0, 11), match='15012022202'> print(pattern.search('15012022202a').group()) # 15012022202
-
-
编译后其他方法的使用
原型
def match(self, string, pos=0, endpos=-1) def search(self, string, pos=0, endpos=-1) def findall(self, string, pos=0, endpos=-1) def finditer(self, string, pos=0, endpos=-1)
参数
参数 说明 string 待匹配的字符串 pos 从string字符串pos下标开始 endpos 结束下标
练习
需求,我先从这个html文件中通过正则提取一下这个html中的图片
import re
# 读取这个文件
f = open ('./素材/db.html','rb')
data = f.read().decode('UTF-8')
f.close()
# 需要匹配的图片 格式,有规律,都是这样的
# <img src="https://img3.doubanio.com/mpic/s29535271.jpg"/>
# <img src="https://img3.doubanio.com/mpic/s29554772.jpg"/>
# <img src="https://img3.doubanio.com/mpic/s29578984.jpg"/>
# 编译一个正则,用来匹配这个html文档里的标签,规则:匹配 img 标签里,mpic/后面的,规则为任意个数的字符(非空白符类型的字符 \w+)
pattern = re.compile('<img src="https://img\d\.doubanio.com/mpic/\w+\.jpg"/>')
print(pattern.findall(data)) # findall取出是一个列表,里面有很多
# 存储到文件中
with open('./img.html','w') as f :
f.writelines(pattern.findall(data)) # 写入列表中的内容
六、总结
好了,正则的学习到这里就结束了,希望大家看完有所收获,值得注意的是:大家一定要好好掌握贪婪与非贪婪,还有search、match、findall、finditer的区别,以及各种正则的格式。希望大家学习愉快,共同进步!有问题,我们可以私信交流~