正则表达式
- 正则表达式是什么?
它是约束字符串匹配某种形式的规则 - 正则表达式有什么用?
1.检测某个字符串是否符合规则.比如:判断手机号,身份证号是否合法
2.提取网页字符串中想要的数据.比如:爬虫中,提取网站天气,信息,股票代码,星座运势等具体关键字 - 在线测试工具 http://tool.chinaz.com/regex/
正则表达式所包含的元素种类
正则表达式由一些[普通字符]
和一些[元字符]
组成:
- 普通字符包括大小写字母和数字
- 元字符具有特殊含义,大体种类分为如下:
- 预定义字符集,字符组
- 量词
- 边界符
- 分组
匹配单个字符
[元字符] 预定义字符集
预定义字符集 | 匹配内容 |
---|---|
. | 匹配任意字符,除了换行符\n |
\d | 匹配数字 |
\D | 匹配非数字 |
\w | 匹配字母或数字或下划线 (正则函数中,支持中文的匹配) |
\W | 匹配非字母或数字或下划线 |
\s | 匹配任意的空白符 |
\S | 匹配任意非空白符 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
[] | 匹配中括号内列举的字符 |
字符组格式 | 说明 [默认必须从字符组中选一个] |
---|---|
[…] | 匹配字符组中的字符 |
[^…] | 匹配除了字符组内所有内容,之外的所有字符 |
字符组内容 | 待匹配字符 | 匹配结果 | 说明 |
---|---|---|---|
[0123456789] | 8 | True | 字符组里枚举的各种字符,必须满足一个,否则返回假,不匹配 |
[abcdefg] | 9 | False | 由于字符组中没有"9"字符,所以不匹配 |
[0-9] | 7 | True | 可用 - 表示范围,[0-9] 和 [0123456789]是一个意思 |
[a-z] | s | True | [a-z]匹配所有的小写字母 |
[A-Z] | B | True | [A-Z]就表示所有的大写字母 |
[0-9a-fA-F] | e | True | 可以匹配数字,大小写形式的a-f. 该正则可验证十六进制 |
- findall 匹配字符串中相应内容, 返回列表
re.findall(正则表达式,要匹配的字符串)
import re
- \d 匹配数字
\D 匹配非数字
lst = re.findall("\d","sadfsdfsa 98&*^&^&92sdf3sdf839 sdf_as 神秘男孩 89")
print(lst)
lst = re.findall("\D","sadfsdfsa 98&*^&^&92sdf3sdf839 sdf_as 神秘卫星 89")
print(lst)
- \w 字母,数字,下划线,包含中文(正则函数当中支持中文的匹配)
\W 匹配非字母或数字或下划线
lst = re.findall("\w","09090sdf909sdf_+()()&*&*%^%^你好sd")
print(lst)
lst = re.findall("\W","09090sdf909sdf_+()()&*&*%^%^你好sd")
print(lst)
- \s 匹配任意空白符
\S 匹配任意非空白符
strvar = """1223
afdasdf
"""
lst = re.findall("\s"," ")
print(lst)
lst = re.findall("\s",strvar)
print(lst)
lst = re.findall("\S"," lklkl sdfsdf sdfsdf")
print(lst)
- \n 匹配换行
\t 匹配一个制表符
strvar = """
今天 拉 肚 子
"""
lst = re.findall(r"\n",strvar)
print(lst)
lst = re.findall(r"\t",strvar)
print(lst)
字符组练习
必须从字符组选一个,如果一个都没有则匹配不成功
lst = re.findall("[123]","s3dafsf2s1")
print(lst)
print(re.findall('a[abc]b','aab abb acb adb'))
print(re.findall('a[0123456789]b','a1b a2b a3b acb ayb a9090909009b'))
# 优化版:0123456789 => 0-9 等价
print(re.findall('a[0-9]b','a1b a2b a3b acb ayb a9090909009b'))
print(re.findall('a[abcdefg]b','a1b a2b a3b acb ayb adb aabababbababb')) #['acb', 'adb']
# 优化版:a b c d .... z => a-z
print(re.findall('a[a-g]b','a1b a2b a3b acb ayb adb aabababbababb'))
print(re.findall('a[a-z]b','a1b a2b a3b acb ayb adb aabababbababb'))
print(re.findall('a[ABCDEFG]b','a1b a2b a3b aAb aDb aYb'))
# 优化版: A B C ... Z => A-Z
print(re.findall('a[A-G]b','a1b a2b a3b aAb aDb aYb'))
print(re.findall('a[A-Z]b','a1b a2b a3b aAb aDb aYb'))
print(re.findall('a[0-9a-zA-Z]b','a-b aab aAb aWb aqba1b'))
# 优化版: a-zA-Z => A-z 可以匹配所有大小写,但是有缺陷,一些特殊符号也匹配到了
print(re.findall('[A-z]','('))
print(re.findall('[0-z]','9')) # 语法上允许,但是别这么用,没意义
print(re.findall('a[0-9][*#/]b','a1/b a2b a29b a56b a456bab'))
# ^ 字符组中的^ 代表除了的意思
print(re.findall('a[^-+*/]b',"a%b ccaa*bda&bd"))
# 单独匹配一个特殊字符,用\让他的意思失效 转义字符
print(re.findall('a[\^]b',"a^b"))
匹配多个字符
[元字符] 量词符号
量词 | 用法说明 |
---|---|
? | 重复0次或1次 |
+ | 重复1次或多次 (至少1次) |
* | 重复0次或多次 (任意次) |
{n} | 重复n次 |
{n,} | 重复n次或更多次 (至少n次) |
{n,m} | 重复n到m次 |
.* .+ | 贪婪模式匹配 |
.*? .+? | 非贪婪模式匹配 |
import re
'''1) ? 匹配0个或者1个a '''
print(re.findall('a?b','abbzab abb aab'))
'''2) + 匹配1个或者多个a '''
print(re.findall('a+b','b ab aaaaaab abb'))
'''3) * 匹配0个或者多个a '''
print(re.findall('a*b','b ab aaaaaab abbbbbbb'))
'''4) {m,n} 匹配m个至n个a '''
print(re.findall('a{1,3}b','aaab ab aab abbb aaz aabb aaaaaaaaaabb'))
print(re.findall('a{1}b','aaab ab aab abbb aaz aabb aaaaaaaaaabb'))
print(re.findall('a{1,}b','aaab ab aab abbb aaz aabb aaaaaaaaaabb'))
贪婪匹配 与 非贪婪匹配
[语法:量词的后面加?号]
-
贪婪匹配: 默认向更多次数匹配 底层用的是回溯算法;
-
非贪婪匹配: 默认向更少次数匹配:
在量词的后面加上一个?号,就是非贪婪匹配 例如: .*? .?? .+? .{m,n}? .*?用的更多
如果遇到了’子’这个字,在非贪婪模式下,匹配到第一个就返回.回溯算法:
从左向右进行匹配,一直向后找,直到再也找不到了,回头,拿离右侧最近的那个值
strvar = "刘能和刘铁锤和刘大棍子12313子"
lst = re.findall("刘.",strvar)
# 贪婪匹配
print(lst)
lst = re.findall("刘.?",strvar)
print(lst)
lst = re.findall("刘.+",strvar)
print(lst)
lst = re.findall("刘.*",strvar)
print(lst)
lst = re.findall("刘.*子",strvar)
print(lst)
lst = re.findall("刘.{1,20}子",strvar)
print(lst,"<==>")
# 非贪婪匹配
strvar = "刘能和刘铁锤和刘大棍子12313子"
lst = re.findall("刘.??",strvar)
print(lst)
lst = re.findall("刘.+?",strvar)
print(lst)
lst = re.findall("刘.*?",strvar)
print(lst)
# 匹配到第一个子就直接返回
lst = re.findall("刘.*?子",strvar)
print(lst)
lst = re.findall("刘.{1,20}?子",strvar)
print(lst)
匹配开头结尾
[元字符] 边界符号
边界符 | 说明 |
---|---|
\b | 匹配一个字符的边界 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结尾 |
- \b 用来匹配边界 word 匹配d为右边界 d\b 匹配w为左边界\bw
\b 退格 backspace 是一个转义字符
一般写正则表达式的时候,字符串的前面加上一个r,让转义字符失效,
# 贪婪匹配
lst = re.findall(r".*d\b","word pwd abc")
print(lst)
# 非贪婪匹配
lst = re.findall(r".*?d\b","word pwd abc")
print(lst)
# 优化版:舍掉空格
lst = re.findall(r"\S*?d\b","word pwd abc")
print(lst)
# 匹配单词的左边界
lst = re.findall(r"\bw.* ","word abc")
print(lst)
- ^ 必须以某个字符开头,后面的字符无所谓
$ 必须以某个字符结尾,前面的字符无所谓
如果正则里面包含了^或者$ 意味着要把字符串看成一个整体
strvar = "大哥大嫂大爷"
print(re.findall('大.',strvar))
print(re.findall('^大.',strvar))
print(re.findall('大.$',strvar))
print(re.findall('^大.$',strvar))
print(re.findall('^大.*?$',strvar))
print(re.findall('^大.*?大$',strvar))
print(re.findall('^大.*?爷$',strvar))
# 把字符串看成一个整体,只要一个结果
print(re.findall('^g.*? ' , 'giveme 1gfive gay '))
print(re.findall('five$' , 'aassfive'))
print(re.findall('five$' , 'aassfive00'))
print(re.findall('^giveme$' , 'giveme'))
print(re.findall('^giveme$' , 'givemeq'))
print(re.findall('^giv.me$' , 'giveme'))
print(re.findall('^giveme$' , 'giveme giveme'))
print(re.findall('giveme' , 'giveme giveme'))
print(re.findall("^g.*e",'giveme 1gfive gay'))
匹配分组
[元字符] 分组符号
分组 | 用法说明 |
---|---|
a|b | 匹配字符a 或 字符b (如果两个当中有重合部分,把更长的那个放前面) |
(ab) | 匹配括号内的表达式 ,将()作为一个分组 |
\num | 引用分组num匹配到的字符串 |
(?P) | 给分组命名 |
(?P=name) | 引用别名: 把name分组所匹配到的内容引用到这里 |
正常分组
-
正常情况下用
()
圆括号进行分组 可以用\1
反向引用第一个圆括号匹配的内容。 -
(?:正则表达式)
表示取消优先显示的功能(正则表达式) 代表分组 findall 把所有分组的内容显示到列表里
(?:正则表达式) 代表分组 findall 把所有分组的内容不显示到列表里
import re
print(re.findall('.*?_good','wusir_good alex_good 神秘男孩_good'))
#['wusir_good', ' alex_good', ' 神秘男孩_good']
print(re.findall('(.*?)_good','wusir_good alex_good 神秘男孩_good'))
#['wusir', ' alex', ' secret男']
print(re.findall('(?:.*?)_good','wusir_good alex_good 神秘男孩_good'))
|
代表或
既想匹配abc 还要匹配abcd
lst = re.findall("abc|abcd","abc234abcd234")
print(lst) # 有缺陷,不能够都匹配到
#任意 把字符串长的写在前面,字符串短的写在后面
lst = re.findall("abcd|abc","abc234abcd234")
print(lst)
- 匹配小数
用\
让原本有意义的字符变得无意义.
.
代表任意字符(除了\n
)
如果想要让正则匹配一个.
用\.
让点的意思失效.
# 3.14 34. .2342 234. . 2344.234
lst = re.findall("\d+\.\d+","3.14 34. .2342 234. . 2344.234")
print(lst)
- 匹配小数和整数
lst = re.findall("\d+\.\d+|\d+","3.14 34. .2342 234. . 2344.234 343 sdfsdf ")
print(lst)
- 使用分组的形式来匹配小数和整数
lst = re.findall("\d+(\.\d+)?","3.14 34. .2342 234. . 2344.234 343 sdfsdf ")
print(lst)
lst = re.findall("\d+(?:\.\d+)?","3.14 34. .2342 234. . 2344.234 343 sdfsdf ")
print(lst)
- 匹配135或171的手机号
lst = re.findall("135|^171\d{8}","171skdjfksjkf 11312312317178787887")
print(lst)
lst = re.findall("135|171[0-9]{8}","171skdjfksjkf 17178787887")
print(lst)
- 反向引用 拿已经匹配到的值,在引用一次
import re
strvar = "<h1>sdfsdfsdfsdf</h1>"
lst = re.findall("<(.*?)>(.*?)<(.*?)>",strvar)
print(lst)
strvar = "123<h1>sdfsdfsdfsdf</h1> 123<h2>ppoo</h2>"
lst = re.findall("<(.*?)>(.*?)<(.*?)>",strvar)
print(lst)
# 反向引用 拿已经匹配到的值,在引用一次
# 引用第一个括号里面的内容 \1
# 引用第二个括号里面的内容 \2 依次类推...
strvar = "123<h1>sdfsdfsdfsdf</h1> 123<h2>ppoo</h2>"
lst = re.findall(r"<(.*?)>(.*?)</\1>",strvar)
print(lst)
命名分组
(?P<组名>正则表达式)
给这个组起一个名字(?P=组名)
引用之前组的名字,把该组名匹配到的内容放到当前位置
strvar = "d3j5sdj"
obj = re.search(r"(.*?)\d(.*?)\d(.*?)\1\2",strvar)
print(obj)
res = obj.group()
print(res)
# 用命名分组进行反向引用
obj = re.search(r"(?P<tag1>.*?)\d(?P<tag2>.*?)\d(?P<tag3>.*?)(?P=tag1)(?P=tag2)",strvar)
print(obj)
res = obj.group()
print(res)
obj = re.search(r"(?P<tag1>.*?)\d(?P<tag2>.*?)\d(?P<tag3>.*?)\1\2",strvar)
print(obj)
res = obj.group()
print(res)
search
通过正则匹配出第一个对象返回,通过group取出对象中的值
- findall 把满足条件的所有值都找出来放到列表里面返回
- search 找到一个满足条件的值就直接返回,扔到一个对象当中
想要获取对象中的值,用group 对象.group()
obj = re.search("\d+","jsdfusudf8989sdfsdf89")
print(obj)
res = obj.group()
print(res)
- 匹配 www.baidu.com 或者 www.oldboy.com
obj = re.search("(www)\.(baidu|oldboy)\.(com)","www.baidu.com www.oldboy.com")
res = obj.group()
print(res)
# 数字1 代表拿第一个小括号里面的内容
res = obj.group(1)
print(res)
# 数字2 代表拿第一个小括号里面的内容
res = obj.group(2)
print(res)
# 数字3 代表拿第一个小括号里面的内容
res = obj.group(3)
print(res)
# groups 一次性把所有小括号里面的内容匹配出来
res = obj.groups()
print(res)
- “5*6-7/3” 匹配 5*6 或者 7/3
strvar = "5*6-7/3"
obj = re.search("\d+[*/]\d+",strvar)
res = obj.group()
print(res)
lst = re.findall("\d+[*/]\d+",strvar)
print(lst)
正则计算器-小项目
要求:用正则计算字符串:a = ‘1-2*((60 - 30+(-40/5)( 9- 25/3+7/399/42998+10568/14))-(-43)/(16-3*2))’ 的值
import re
# 计算乘除的方法
def parse_exp(exp):
if "*" in exp:
a,b = exp.split("*")
# print(a,b)
return str(float(a) * float(b))
if "/" in exp:
a,b = exp.split("/")
return str(float(a) / float(b))
# 去除++ +- -- -+ bug情况
def exp_format(exp):
exp = exp.replace("+-","-")
exp = exp.replace("--","+")
exp = exp.replace("-+","-")
exp = exp.replace("++","+")
return exp
# 实际计算
def exp_calc(strvar):
# 计算乘除
while True:
res_obj = re.search("\d+(\.\d+)?[*/][+-]?\d+(\.\d+)?",strvar)
if res_obj:
res = res_obj.group()
# print(res) #"5*-2"
res2 = parse_exp(res)
# print(res2)
strvar = strvar.replace(res,res2)
else:
break
# print(strvar)
# 计算加减
res = exp_format(strvar)
# print(res)
lst = re.findall("[+-]?\d+(?:\.\d+)?",res)
# print(lst)
count = 0
for i in lst:
count += float(i)
# print(count)
return count
# 去除括号
def remove_bracket(strvar):
while True:
res_obj = re.search("\([^()]+\)",strvar)
if res_obj:
res_exp = res_obj.group()
# print(res_exp)
# 计算括号里面的值,exp_calc
res = str(exp_calc(res_exp))
# print(res,type(res))
# 把算好的实际数值转换成字符串,替换以前的圆括号
strvar = strvar.replace(res_exp,res)
else:
# 直接返回替换好的字符串
return strvar
# 主函数
def main(strvar):
# 先把所有的空格去掉
strvar = strvar.replace(" ","")
# 去除括号
res = remove_bracket(strvar)
# print(res)
# 计算最后结果
return exp_calc(res)
# strvar = "- 30 +( 40+5*-2)* 2"
a = '1-2*((60-30+(-40/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))'
res = main(a)
print(res)
# 验证结果
res = eval(a)
print(res)