7.1行定位符
行定位符就是用来描述字符串的边界。“^”表示行的开始;“$”表示行的结尾。
7.2元字符
7.3重复
7.4转义字符
7.5常用正则表达式
下面是网络上收集的一些常用正则表达式,请参考使用。 PS:各位在复制粘贴的时候务必要小心前后多余的空格!
校验数字的相关表达式:
校验字符的相关表达式:
特殊场景的表达式:
7.6贪婪模式
在重复匹配时,正则表达式默认总是尽可能多的匹配,这被称为贪婪模式。比如,针对文本,表达式中的将匹配第一个和最后一个之间的所有字符。可见,在匹配的时候,总是尽可能多的匹配符合它规则的字符。同理,带有、和的重复匹配表达式都是尽可能地多匹配。
但是有时候,这种模式不是我们想要的结果,比如最常见的HTML标签匹配。假设有如下的字符串:
我们的意图是获取每个标签中的元素内容,那么如果你将正则表达式写成的话,你得到的是这么个东西,而不是“苹果”、“桃子”、“香蕉”。
那么怎么办呢?使用非贪婪模式!
在修饰匹配次数的特殊符号后再加上一个问号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的"不匹配"。如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,非贪婪模式会最小限度的再多匹配一些,以使整个表达式匹配成功。
表达式匹配上面的字符串时,将只得到,再次匹配下一个时,可以得到,以此类推。
针对文本"dxxxdxxxd"举例:
表达式中的将尽可能少的匹配第一个之后的字符,结果是只匹配了一个"x",整体只匹配了。
表达式为了让整个表达式匹配成功,不得不匹配才可以让后边的匹配,从而使整个表达式匹配成功。因此,结果是匹配了,整体匹配了。
7.7反向引用
表达式在匹配时,表达式引擎会将小括号包含的表达式所匹配到的字符串记录下来。在获取匹配结果的时候,小括号包含的表达式所匹配到的字符串可以单独获取。这是一个非常有用也非常重要的特性。在实际应用场合中,当用某种边界来查找,而所要获取的内容又不包含边界时,必须使用小括号来指定所要的范围。比如前面的 。
其实,“小括号包含的表达式所匹配到的字符串"不仅是在匹配结束后才可以使用,在匹配过程中也可以使用。表达式后边的部分,可以引用前面"括号内的子匹配已经匹配到的字符串”。引用方法是加上一个数字。引用第1对括号内匹配到的字符串, 引用第2对括号内匹配到的字符串……以此类推,如果一对括号内包含另一对括号,则外层的括号先排序号。换句话说,哪一对的左括号"("在前,那这一对就先排序号。举例如下:
表达式在匹配时,匹配结果是:成功;匹配到的内容是。再次匹配下一个时,可以匹配到 。这里的,动态的引用了匹配到的结果。
表达式在匹配时,匹配结果是:成功;匹配到的内容是。再次匹配下一个时,将得到。这个表达式要求范围的字符至少重复5次,注意与之间的区别。
表达式在匹配时,匹配结果是成功。如果与不配对,则会匹配失败;如果改成其他配对,也可以匹配成功。这就是常用的HTML标签匹配方法。
7.8re模块应用
一、compile(pattern, flags=0)
这个方法是re模块的工厂方法,用于将字符串形式的正则表达式编译为Pattern模式对象,可以实现更高效率的匹配。第二个参数flag是匹配模式。
使用完成一次转换后,再次使用该匹配模式的时候就不用进行转换了。经过转换的正则表达式对象也能使用普通的re方法。其用法如下:
经过compile()方法编译过后的返回值是个re对象,它可以调用match()、search()、findall()等其他方法,但其他方法不能调用compile()方法。实际上,match()和search()等方法在使用前,Python内部帮你进行了compile的步骤。
那么是使用compile()还是直接使用呢?看场景!如果你只是简单的匹配一下后就不用了,那么这种简便的调用方式无疑来得更简单快捷。如果你有个模式需要进行大量次数的匹配,那么先compile编译一下再匹配的方式,效率会高很多。
以下的内容,都采用直接通过re模块调用方法的形式。
二、match(pattern, string, flags=0)
match()方法会在给定字符串的开头进行匹配,如果匹配不成功则返回None,匹配成功返回一个匹配对象,这个对象有个group()方法,可以将匹配到的字符串给出。
对于一个对象,span指的是匹配到的字符在字符串中的位置下标,分别对应start和end。需要注意的是不包括end位置的下标,它是右开口的。具体如下:
三、search(pattern, string, flags=0)
在文本内查找,返回第一个匹配到的字符串。它的返回值类型和使用方法与match()是一样的,唯一的区别就是查找的位置不用固定在文本的开头。
四、findall(pattern, string, flags=0)
作为re模块的三大搜索函数之一,findall()和match()、search()的不同之处在于,前两者都是单值匹配,找到一个就忽略后面,直接返回不再查找了。而findall是全文查找,它的返回值是一个匹配到的字符串的列表。这个列表没有group()方法,没有start、end、span,更不是一个匹配对象,仅仅是个列表!如果一项都没有匹配到那么返回一个空列表。
五、split(pattern, string, maxsplit=0, flags=0)
re模块的split()方法和字符串的split()方法很相似,都是利用特定的字符去分割字符串。但是re模块的split()可以使用正则表达式,因此更灵活,更强大,而且还有“杀手锏”。看下面这个例子,匹配模式是加减乘除四个运算符中的任何一种,通过split()将字符串分割成一个一个的数字:
split有个参数,用于指定分割的次数:
利用分组的概念,方法还可以保存被匹配到的分隔符,这个功能非常重要!为什么呢?比如,你要计算8+7,是不是要同时获得三个字符?就如同下面的例子,字符串,想要计算字符串内的表达式的值,你必须获得其中加减乘除的符号。
六、sub(pattern, repl, string, count=0, flags=0)
sub()方法类似字符串的replace()方法,用指定的内容替换匹配到的字符,可以指定替换次数。
sub()方法有一个高级功能——“分组引用”,这是一个非常强大的功能,运用好了能发挥巨大的作用!举例如下:
运行结果:
其实现机制是首先在正则表达式里用括号建立了一个分组,然后在要替换进去的字符串里用“1”引用了这个分组匹配到的内容。PS:还记得正则语法里反向引用的知识点吗?
七、flag匹配模式
Python的re模块提供了一些可选的标志修饰符来控制匹配的模式。可以同时指定多种模式,通过与符号来设置多种模式共存。如被设置成和模式。
模式:
正则表达式是区分字母大小写的,但在模式下,则忽略大小写。
split()方法用flag时的问题,尝试运行代码,但是输出结果并不是我们预想的不区分大小写的a,将字符串分割。这是因为方法的定义体中默认是四个参数,当我们传入三个参数的时候,实际是被当做第三个参数传递进去了,所以没起到该有的作用。如果想让这里的起作用,应当写成,也就是。
模式:
多行匹配模式。默认,元字符会匹配字符串的开始处,元字符会匹配字符串的结束位置和字符串后面紧跟的换行符之前(如果存在这个换行符)。如果指定了这个选项,则将会匹配字符串的开头和每一行的开始处,紧跟在每一个换行符后面的位置。类似的,会匹配字符串的最后和每一行的最后,在接下来的换行符的前面的位置。
re.X模式:
Python的re模块还有一个很有趣的X模式,也就是VERBOSE。该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。当该标志被指定时,在正则表达式字符串中的空白、tab、换行符将被忽略,除非该空白符在字符类中或在反斜杠之后;这可以让你更清晰地组织和缩进表达式。它也允许你将注释写入表达式,这些注释会被引擎忽略;注释用 "#"号来标识,不过该符号不能在字符串或反斜杠之后。巴拉巴拉一堆,你只需要记住它的两个作用:一是让冗长难懂的表达式更易读;二是给表达式加注释。
re.U模式:
re.UNICODE是种兼容模式。在字符串模式下被忽略(默认模式),在字节类型模式下被禁止。我们知道,在Python3之后,string和bytes被独立成了两种不同的数据类型。在re模块中,不能用bytes去匹配string或者用string去匹配bytes,只能用string匹配string,bytes匹配bytes。下面是一个bytes匹配bytes的例子:
运行结果:
当使用UNICODE模式时,将强制禁止使用bytes类型,一但使用将报错。在string类型中,UNICODE是默认设置。
运行结果:
re.S模式:
让圆点这个通配符能够从默认的不支持换行符,变得可以匹配换行符。这在我们使用网络爬虫,爬取HTML文本的时候,非常重要。我们都知道,比如技术文章,网络小说中,文本通常都是大段大段的,并且包含很多换行符,为了将整个文本爬取下来,我们常常使用匹配核心文本内容,看下面的例子:
代码运行的结果不是我们想象的那样,根本就没匹配到任何东西。原因就是圆点默认不匹配换行符,导致整个匹配的失败。
解决方法就是使用模式!如下所示替换代码中国的语句,运行后就能看到想要的结果。
八、分组功能
Python的re模块有一个分组功能。所谓的分组就是去已经匹配到的内容里面再筛选出需要的内容,相当于二次过滤。实现分组靠圆括号,而获取分组的内容靠的是group()、groups()和groupdict()方法,其实前面我们已经展示过。re模块里的几个重要方法在分组上,有不同的表现形式,需要区别对待。
例一:match()方法,不分组时的情况:
例二:match()方法,有分组的情况(注意圆括号!)
分析一下上面的代码,正则表达式中有2个小括号,表示它分了2个小组,在匹配的时候是拿整体的表达式去匹配的,而不是拿小组去匹配的。表示这个小组内是1到多个字母数字字符,中是个正则表达式的特殊语法,表示给这个小组取了个叫的名字,是固定写法。在获取分组值的时候,和是对等的,都表示整个匹配到的字符串,从开始,分别是从左往右的小组序号,按位置顺序来。
有时候括号会存在嵌套情况,那怎么确定组的顺序1,2,3?要么用取名字的方法,要么就数左括号,第几个左括号就是第几个分组,例如,0表示表达式本身,不参加数左括号的动作。
例三,search()方法,有分组的情况:
表现得和match()方法基本一样。
例四,findall()方法,没有分组的情况:
注意到了没有?我根本没有调用group相关的方法,因为findall()的返回值是个列表,根本就没有group()、groups()、groupdict()的概念!
例五,findall()方法,有一个分组的情况:
相比较前面未分组的例子,有没有发现什么?对了!没有圈在分组内的内容被抛弃了,比如这里的字符’h’。
例六,findall()方法,有两个以上分组的情况:
注意到了返回值是什么了吗?元组组成的列表!
例七,sub()方法,有分组的情况:
看到没有?sub()没有分组的概念!这是因为sub()方法是用正则表达式整体去匹配,然后又整体的去替换,分不分组对它没有意义。这里一定要注意了!
例八,split()方法,有一个分组的情况:
事实上,在前面我们已经展示过这个例子,通过分组,我们可以拿到split匹配到的分隔符。
例九,split()方法,有两个分组,并且嵌套:
例十,split()方法,有多个分组,并且嵌套: