Date: 2019-07-03
Author: Sun
本节目的:
(1)掌握正则表达式和re模块使用
(2)python操作正则表达式,匹配贪婪和非贪婪模式使用
(3)掌握常见函数find, findall, search, match, split等用法
正则表达式
正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。
正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
1 为什么使用正则表达式?
列举几个比较鲜明的例子帮助你理解。
(1)判断一个字符串里是否包含数字,如果有,返回true;否则返回false;
(2)给定字符串 str,检查其是否包含连续重复的字母(a-zA-Z),包含返回 true,否则返回 false
(3) 从一个大文本里面,提取出我们想要数据。
再者比如在工作中我们经常遇到这样的需求:
1.给你一个字符串,把字符串里面的链接、数字、电话等显示不同的颜色;
2.给你一个包含自定义表情的文字,找出里面的表情,替换成本地的表情图片;
3.根据用户的输入内容,判断是否是微信号、手机号、邮箱、纯数字等;
提示:
对于1和2的情景,我们使用正则表达式+富文本 便可以轻松应对。
对于3,我们只需根据正则表达式的规则,封装好自己的正则库,就可以做到一劳永逸了!
常用的正则匹配工具
在线匹配工具:
1 http://www.regexpal.com/
2 http://rubular.com/
正则匹配软件
McTracer (https://pan.baidu.com/s/19Yn49)
2 什么是正则表达式?
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
例如:
- runoo+b,可以匹配 runoob、runooob、runoooooob 等,+ 号代表前面的字符必须至少出现一次(1次或多次)。
构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
普通字符
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
非打印字符
非打印字符也可以是正则表达式的组成部分。下表列出了表示非打印字符的转义序列:
字符 | 描述 |
---|---|
\cx | 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。 |
\f | 匹配一个换页符。等价于 \x0c 和 \cL。 |
\n | 匹配一个换行符。等价于 \x0a 和 \cJ。 |
\r | 匹配一个回车符。等价于 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于 \x09 和 \cI。 |
\v | 匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
特殊字符
所谓特殊字符,就是一些有特殊含义的字符,如上面说的 runoob 中的 ,简单的说就是表示任何字符串的意思。如果要查找字符串中的 * 符号,则需要对 * 进行转义,即在其前加一个 : runo*ob 匹配 runo*ob。
许多元字符要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符"转义",即,将反斜杠字符 放在它们前面。下表列出了正则表达式中的特殊字符:
特别字符 | 描述 |
---|---|
$ | 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,请使用 $。 |
( ) | 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 ( 和 )。 |
* | 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 *。 |
+ | 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 +。 |
. | 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 . 。 |
[ | 标记一个中括号表达式的开始。要匹配 [,请使用 [。 |
? | 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 ?。 |
\ | 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\' 匹配 "",而 '(' 则匹配 "("。 |
^ | 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 ^。 |
{ | 标记限定符表达式的开始。要匹配 {,请使用 {。 |
| | 指明两项之间的一个选择。要匹配 |,请使用 |。 |
特殊字符
(1)^ $ * ? + {2} {2, } {2, 5} |
(2)[], [^], [a-z], [0-9], [4|5]
(3) \s, \S, \w, \W
(4) [\u4E00-\u9FA5] () \d
限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。
正则表达式的限定符有:
字符 | 描述 |
---|---|
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 |
? | 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do"(or does) 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。 |
{n,} | n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。 |
{n,m} | m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。 |
3. 正则表达式元字符和语法
符号 | 说明 | 实例 |
---|---|---|
. | 表示任意字符,如果说指定了 DOTALL 的标识,就表示包括新行在内的所有字符。 | 'abc' >>>'a.c' >>>结果为:'abc' |
^ | 表示字符串开头。 | 'abc' >>>'^abc' >>>结果为:'abc' |
**\(** | 表示字符串结尾。 | 'abc' >>>'abc\)' >>>结果为:'abc' | ||
*, +, ? | '*'表示匹配前一个字符重复 0 次到无限次,'+'表示匹配前一个字符重复 1次到无限次,'?'表示匹配前一个字符重复 0 次到1次 | 'abcccd' >>>'abc*' >>>结果为:'abccc''abcccd' >>>'abc+' >>>结果为:'abccc''abcccd' >>>'abc?' >>>结果为:'abc' |
*?, +?, ?? | 前面的*,+,?等都是贪婪匹配,也就是尽可能多匹配,后面加?号使其变成惰性匹配即非贪婪匹配 | 'abc' >>>'abc*?' >>>结果为:'ab''abc' >>>'abc??' >>>结果为:'ab''abc' >>>'abc+?' >>>结果为:'abc' |
{m} | 匹配前一个字符 m 次 | 'abcccd' >>>'abc{3}d' >>>结果为:'abcccd' |
{m,n} | 匹配前一个字符 m 到 n 次 | 'abcccd' >>> 'abc{2,3}d' >>>结果为:'abcccd' |
{m,n}? | 匹配前一个字符 m 到 n 次,并且取尽可能少的情况 | 'abccc' >>> 'abc{2,3}?' >>>结果为:'abcc' |
**** | 对特殊字符进行转义,或者是指定特殊序列 | 'a.c' >>>'a.c' >>> 结果为: 'a.c' |
[] | 表示一个字符集,所有特殊字符在其都失去特殊意义,只有: ^ - ] 含有特殊含义 | 'abcd' >>>'a[bc]' >>>结果为:'ab' |
| | 或者,只匹配其中一个表达式 ,如果|没有被包括在()中,则它的范围是整个正则表达式 | 'abcd' >>>'abc|acd' >>>结果为:'abc' |
( … ) | 被括起来的表达式作为一个分组. findall 在有组的情况下只显示组的内容 | 'a123d' >>>'a(123)d' >>>结果为:'123' |
(?#...) | 注释,忽略括号内的内容 特殊构建不作为分组 | 'abc123' >>>'abc(?#fasd)123' >>>结果为:'abc123' |
(?= … ) | 表达式’…’之前的字符串,特殊构建不作为分组 | 在字符串’ pythonretest ’中 (?=test) 会匹配’ pythonre ’ |
(?!...) | 后面不跟表达式’…’的字符串,特殊构建不作为分组 | 如果’ pythonre ’后面不是字符串’ test ’,那么 (?!test) 会匹配’ pythonre ’ |
(?<= … ) | 跟在表达式’…’后面的字符串符合括号之后的正则表达式,特殊构建不作为分组 | 正则表达式’ (?<=abc)def ’会在’ abcdef ’中匹配’ def ’ |
(?:) | 取消优先打印分组的内容 | 'abc' >>>'(?:a)(b)' >>>结果为'[b]' |
?P<> | 指定Key | 'abc' >>>'(?Pa)>>>结果为:groupdict{n1:a} |
表 2. 正则表达式特殊序列
特殊表达式序列 | 说明 |
---|---|
\A | 只在字符串开头进行匹配。 |
\b | 匹配位于开头或者结尾的空字符串 |
\B | 匹配不位于开头或者结尾的空字符串 |
\d | 匹配任意十进制数,相当于 [0-9] |
\D | 匹配任意非数字字符,相当于 [^0-9] |
\s | 匹配任意空白字符,相当于 [ \t\n\r\f\v] |
\S | 匹配任意非空白字符,相当于 [^ \t\n\r\f\v] |
\w | 匹配任意数字和字母,相当于 [a-zA-Z0-9_] |
\W | 匹配任意非数字和字母的字符,相当于 [^a-zA-Z0-9_] |
\Z | 只在字符串结尾进行匹配 |
4. 正则匹配re模块
Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。
re 模块使 Python 语言拥有全部的正则表达式功能。
compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。
4.1、 re.findall(pattern, string[, flags]):
方法能够以列表的形式返回能匹配的子串。先看简单的例子:
import re
a = 'one1two2three3four4'
ret = re.findall(r'(\d+)', a)
print(ret)
['1', '2', '3', '4']
从上面的例子可以看出返回的值是个列表,并且返回字符串中所有匹配的字符串。
上述写法也可以写成如下方式,效果一样:
import re
p = re.compile(r"(\d+)")
a = 'one1two2three3four4'
res = p.findall(a)
print(res)
['1', '2', '3', '4']
a = 'hello alex alex adn acd'
n = re.findall('(a)(\w+)',a)
print(n) #从左到右,从外到内
#[('a', 'lex'), ('a', 'lex'), ('a', 'dn'), ('a', 'cd')]
4.2 re.match函数
re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
函数语法:
re.match(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
匹配成功re.match方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
匹配对象方法 | 描述 |
---|---|
group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
实例
# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/7/03 上午9:48'
import re
line = "liu dehua was older than you"
matchObj = re.match(r'^liu (.*) was (.*?) .*', line, re.M | re.I)
if matchObj:
print("matchObj.group() : ", matchObj.group())
print("matchObj.group(1) : ", matchObj.group(1))
print("matchObj.group(2) : ", matchObj.group(2))
else:
print("No match!!")
执行结果如下:
matchObj.group() : liu dehua was older than you
matchObj.group(1) : dehua
matchObj.group(2) : older
4.3 re.search 函数
re.search 扫描整个字符串并返回第一个成功的匹配。
函数语法:
re.search(pattern, string, flags=0)
函数参数说明:
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串。 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
匹配成功re.search方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
匹配对象方法 | 描述 |
---|---|
group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/7/03 上午9:48'
import re
line = "liu dehua was older than you"
matchObj = re.search(r'^liu (.*) was (.*?) .*', line, re.M | re.I)
if matchObj:
print("matchObj.group() : ", matchObj.group())
print("matchObj.group(1) : ", matchObj.group(1))
print("matchObj.group(2) : ", matchObj.group(2))
else:
print("No match!!")
运行效果:
matchObj.group() : liu dehua was older than you
matchObj.group(1) : dehua
matchObj.group(2) : older
re.match与re.search的区别
re.match是从字符串的起始点开始进行匹配,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;
re.search匹配整个字符串,直到找到一个匹配。
import re
ret_match = re.match("c", "abcde") # 从字符串开头匹配,匹配到返回match的对象,匹配不到返回None
if (ret_match):
print("ret_match:" + ret_match.group())
else:
print("ret_match:None")
ret_search = re.search("c", "abcde") # 扫描整个字符串返回第一个匹配到的元素并结束,匹配不到返回None
if (ret_search):
print("ret_search:" + ret_search.group())
返回的结果:
ret_match:None
ret_search:c
group()方法可以指定组号,如果组号不存在则返回indexError异常看如下例子:
import re
a = "123abc456"
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(0) # 123abc456,返回整体默认返回group(0)
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(1) # 123
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(2) # abc
re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(3) # 456
总结:
match: 从字符串的第一个字符匹配查找
search: 从整个串找符合条件的。
4.4 re.sub和re.subn 函数
两种方法都是用来替换匹配成功的字串,值得一提的时,sub不仅仅可以是字符串,也可以是函数。subn函数返回元组,看下面例子:
import re
# sub
ret_sub = re.sub(r'(one|two|three)', 'ok', 'one word two words three words')
print(ret_sub)
# subn
import re
ret_subn = re.subn(r'(one|two|three)', 'ok',
'one word two words three words')
print(ret_subn)
# ok word ok words ok words
# ('ok word ok words ok words', 3) 3,表示替换的次数
4.5 re.split 函数
re.split(pattern, string, maxsplit=0)
通过正则表达式将字符串分离。如果用括号将正则表达式括起来,那么匹配的字符串也会被列入到list中返回。maxsplit是分离的次数,maxsplit=1分离一次,默认为0,不限制次数。
import re
ret = re.split('\d+',
'one1two2three3four4')
print(ret)
####output####
# 匹配到1的时候结果为'one'和'two2three3four4',匹配到2的时候结果为'one',
# 'two'和'three3four4', 所以结果为:
#['one', 'two', 'three', 'four', '']
4.6 python正则匹配贪婪和非贪婪模式
正则表达式通常用于在文本中查找匹配的字符串。Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪则相反,总是尝试匹配尽可能少的字符。在"*","?","+","{m,n}"后面加上?,使贪婪变成非贪婪。
>>> s="This is a number 234-235-22-423"
>>> r=re.match(".+(\d+-\d+-\d+-\d+)",s) #贪婪
>>> r.group(1)
'4-235-22-423'
>>> r=re.match(".+?(\d+-\d+-\d+-\d+)",s) #非贪婪
>>> r.group(1)
'234-235-22-423'
正则表达式模式中使用到通配字,那它在从左到右的顺序求值时,会尽量“抓取”满足匹配最长字符串,在我们上面的例子里面,“.+”会从字符 串的启始处抓取满足模式的最长字符,其中包括我们想得到的第一个整型字段的中的大部分,“\d+”只需一位字符就可以匹配,所以它匹配了数字“4”,而“.+”则匹配了从字符串起始到这个第一位数字4之前的所有字符。
解决方式:非贪婪操作符“?”,这个操作符可以用在"*","+","?"的后面,要求正则匹配的越少越好。
#贪婪
>>> re.match(r"aa(\d+)","aa2343ddd").group(1)
'2343'
#非贪婪
>>> re.match(r"aa(\d+?)","aa2343ddd").group(1)
'2'
>>> re.match(r"aa(\d+)ddd","aa2343ddd").group(1)
'2343'
>>> re.match(r"aa(\d+?)ddd","aa2343ddd").group(1)
'2343'
>>>
#贪婪
ret_greed= re.findall(r'a(\d+)','a23b')
print(ret_greed)
['23']
#非贪婪
ret_no_greed= re.findall(r'a(\d+?)','a23b')
print(ret_no_greed)
['2']
如下例子可以看看加入了?和不加?的区别
str = "i love 2,45 china v5 , 6666, yes"
res = re.findall(r".*?(.*)yes$", str)
print(res)
如何匹配一个数字串,满足
开头的一个数是6,中间五个是整数,后一位是1,2或者4,代码如下:
p = re.compile(r"^(6\d{5}[1,2,4])")
print(p.match("6256432"))
5. 正则表达式案例分析
(1)校验密码强度
密码的强度必须是包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间。
^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
(2)校验中文
字符串仅能是中文。
^[\u4e00-\u9fa5]+$
(3) 由数字、26个英文字母或下划线组成的字符串
^\\w+$
(4)校验E-Mail 地址
同密码一样,下面是E-mail地址合规性的正则检查语句。
^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$
(5)校验身份证号码
下面是身份证号码的正则校验。 18位。
18位:
^([1-9]\d{5}[12]\d{3}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])\d{3}[0-9xX])$
(6)校验金额
金额校验,精确到2位小数。
^[0-9]+(.[0-9]{2})?$
(7)校验手机号
下面是国内 13、15、18开头的手机号正则表达式。(可根据目前国内收集号扩展前两位开头号码),假设是11位的手机号码
^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
(^(13\d|14[57]|15[^4\D]|17[13678]|18\d)\d{8}|170[^346\D]\d{7})$
(8)校验IP-v4地址
^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
(9)检查URL的前缀
应用开发中很多时候需要区分请求是HTTPS还是HTTP,通过下面的表达式可以取出一个url的前缀然后再逻辑判断。
if (!s.match(/^[a-zA-Z]+:\\/\\//))
{
s = 'http://' + s;
}
(10)提取URL链接
下面的这个表达式可以筛选出一段文本中的URL。
^(f|ht){1}(tp|tps):\\/\\/([\\w-]+\\.)+[\\w-]+(\\/[\\w- ./?%&=]*)?
(11) 提取网页图片
假若你想提取网页中所有图片信息,可以利用下 面的表达式。
\\< *[img][^\\\\>]*[src] *= *[\\"\\']{0,1}([^\\"\\'\\ >]*)
(12)提取页面超链接
提取html中的超链接。
(]*)(href="https?:\\/\\/)((?!(?:(?:www\\.)?'.implode('|(?:www\\.)?', $follow_list).'))[^"]+)"((?!.*\\brel=)[^>]*)(?:[^>]*)>
综合案例:
输入一个字符串,判断是否是身份证号码(校验身份证号码)
输入字符串:422101198808100412 (18位)
校验身份证号码的合法性?
# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/7/03 下午3:24'
import re
def check_card_isvalid(card_str):
p = re.compile(r"^([1-9]\d{5}[12]\d{3}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])\d{3}[0-9xX])$")
return p.match(card_str)
card_str = "422101198808100412"
res = check_card_isvalid(card_str)
print(res)
案例扩展:
采用正则表达式匹配日志文件内容,获取每个日志部分
[DEBUG][2018-09-10 09:10:34][192.169.11.34][function1][this is our log file, has error]
代码如下:
'''
正则表达式匹配
'''
regstr = "[DEBUG][2018-09-10 09:10:34][192.169.11.34][function1]" \
"[this is our log file, has error]"
p = re.compile(r"\[(?P.*)\]\[(?P.*)\]"
r"\[(?P\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\]")
res = p.findall(regstr)
print(res)
print(dir(res))
参考书籍:
《精通正则表达式 (第三版).pdf》