python3正则表达式的几个高级用法
一、 概述
本文举例说明python3正则表达式的一些高级级法,主要是各类分组,可应用于
1、复杂网页文件中的有用数据
例如,采用爬虫技术取得网页后,对网页内任何数据进行提取分析
2、 各类配置文件
可能是属性文件,读取属性文件中的键值对
可能是数据库各类查询的复杂匹配的参数解析,类似mybatis的动态sql语句解析,hibernate动态参数sql分析
二、 理解正则表达式的贪婪与非贪婪
1、 生活中的贪婪与非贪婪
例如公司员工餐厅发小西红柿,每人一次可以领取1—10个
如果有的人每次都领取10个,则这个人属于贪婪,就是在不犯错时,每次取最多
如果有的人每次都领取1个,则这个人属于不贪婪,就是在不犯错时,每次取最少
2、 正则表达式的贪婪与非贪婪定义
只有前面的字符串内容可多可少时,才存在贪婪与非贪婪
1) 贪婪语法
.*,取尽可能多的任意字符
\w+,取尽可能多的任意英文字母与数字一次以上
\d{2,5},尽可能取到2--5个数字字母
\s+,},尽可能取到任意多个空格一次以上
.?,任意字符取0次,或1次,尽可能取1次
2) 非贪婪语法
就是在贪婪定义后面加一个?
.*?,取尽可能少的任意字符,尽可能不取
\w+?,取尽可能少的任意英文字母与数字,尽可能只取1个
\d{2,5},尽可能少数字字母,尽可能只取2个
\s+,},尽可能取到最少空格,尽可能只取1个空格
.??,任意字符取0次,或1次,尽可能取0次
3) 贪婪与贪婪的最终匹配
无论贪婪,还是非贪婪,都要与后面内容继续匹配,才能最终确定本次匹配内容,有时给合后面匹配内容时,两都取值相同
3、 示例分析如下
python语法如下
import re
匹配到的结果列表=re.findall(r’正则字符串’,要匹配的字符串,re.I|re.S)
re.I,表示忽略大小写
re.S,表示忽略回行,所有字符包括回行字符
字符串 | 贪婪正则 | 非贪婪正则 |
---|---|---|
www.baidu.com/num | 正则: www\.baidu\.com\/.* 则能匹配到 www.baidu.com/num 有num |
正则 www\.baidu\.com\/.*? 则能匹配到 www.baidu.com/ 无num |
www.baidu.com/num/456 | www\.baidu\.com\/.*\/\d+ 则能匹配到 www.baidu.com/num/456 |
www\.baidu\.com\/.*?\/\d+ 则能匹配到 www.baidu.com/num/456 此时与贪婪正则匹配相同 |
一段网页文本,希望能求出每个div的innerHTML <div>….</div> <div>….</div> <div>….</div> <div>….</div> |
正则 <div>.*</div> 则不能匹配出每个<div>标签之间的文本innerText |
正则 <div>.*?</div> 则能匹配出每个<div>标签之间的文本innerText |
4、示例代码如下
import re
s1=re.findall(r'\D+\d+','abc123456') #结果为:['abc123456']
s2=re.findall(r'\D+\d+?','abc123456') #结果为:['abc1']
s2=re.findall(r'\D+\d*','abc123456') #结果为:['abc123456']
s2=re.findall(r'\D+\d*?','abc123456') #结果为:['abc']
s2=re.findall(r'\D+\d{2,4}','abc123456') #结果为:['abc1234']
s2=re.findall(r'\D+\d{2,4}?','abc123456',re.I) #结果为:['abc12']
s2=re.findall(r'\D+\d?','abc123456',re.I) #结果为:['abc1']
s2=re.findall(r'\D+\d??','abc123456',re.I) #结果为:['abc']
三、 正则表达式的自定义命名分组,(?P)
1、 语法说明
一个正则表达式可以有多个自定义名称的分组,可以能过分组名称提取到匹配的字符串
每一个分组定义是(?P<自定义分组名称>正则字符串)
例如
pattern=r’正则1(?P<组1>组1正则)正则2(?P<组2>组2正则)正则3(?P<组3>组3正则)’
2、 需求如下
这是一段java代码字符串,有下面几种分析需求
1) 需求1,求3个参数,,,,3组实现
a) 每个变量的数据类型
类型名称前后有数量不相同的空格
b) 每个变量名称
变量名称前有数量不相同的空格,后面有等号,等号前后有数量不相同的空格
c) 每个变量的值
值的前后有数量不相同的空格
变量最后一定有分号
2) 需求2,求2个参数, ,,2组实现
a) 每个变量名称
要清除变量前后空格
b) 每个变量的值
要清除值前后空格
3) 需求3,求1个参数,,1组实现
每个变量的值
要清除值前后空格
4) 需求4,求1个参数,,1组实现
每个变量名称
要清除变量名称前后空格
3、 示例代码
str='''
String s1="学习java";
String s2= " ";
Float 价格=24000.89;
String desc = "用于找工作技能提升。。。" ;
Integer num = 12567 ;
'''
import re
#需求1,,分3组:<type>,<name>,<value>,求数据类型,变量名称,变量的值,下面3种求法,结果相同
s1=re.findall(r'(?=String|Float|Integer)(?P<type>\w+)\s+(?P<name>.*?)\s*?=\s*?(?P<value>.*?)\s*?;',str,re.I|re.S); #结果是:[('String', 's1', '"学习java"'), ('String', 's2', ' " "'), ('Float', '价格', '24000.89'), ('String', 'desc', ' "用于找工作技能提升。。。"'), ('Integer', 'num', ' 12567')]
s1=re.findall(r'(?P<type>String|Float|Integer\s+?\w+)\s+(?P<name>.*?)\s*?=\s*?(?P<value>["\d].*?)\s*?;',str,re.I|re.S); #结果是:[('String', 's1', '"学习java"'), ('String', 's2', ' " "'), ('Float', '价格', '24000.89'), ('String', 'desc', ' "用于找工作技能提升。。。"'), ('Integer', 'num', ' 12567')]
#优化上面,当变量前面有空格时,要清除["\d],表示双引号或数字开头,匹配结果自动加入到<value>组,正则语法为:不消耗前缀
s1=re.findall(r'(?=String|Float|Integer)(?P<type>\w+)\s+(?P<name>.*?)\s*?=\s*?(?=["\d])(?P<value>.*?)\s*?;',str,re.I|re.S); #结果是:[('String', 's1', '"学习java"'), ('String', 's2', '" "'), ('Float', '价格', '24000.89'), ('String', 'desc', '"用于找工作技能提升。。。"'), ('Integer', 'num', '12567')]
#需求2,分2组:<name>,<value>,求变量名称,变量的值,
s1=re.findall(r'\s+?(?P<name>\S+?)\s*?=\s*?(?P<value>["\d].*?)\s*?;',str,re.I|re.S); #结果是:[('s1', '"学习java"'), ('s2', '" "'), ('价格', '24000.89'), ('desc', '"用于找工作技能提升。。。"'), ('num', '12567')]
#需求3,分1组<value>,求每个变量的值,要清除首尾空格,给出2种求法
s1=re.findall( r'=\s*?(?P<value>[\d"].*?)\s*?;',str,re.I|re.S); #结果是:['"学习java"', '" "', '24000.89', '"用于找工作技能提升。。。"', '12567']
s1=re.findall( r'=\s*?(?=[\d"])(?P<value>.*?)\s*?;',str,re.I|re.S); #结果是:['"学习java"', '" "', '24000.89', '"用于找工作技能提升。。。"', '12567']
#需求4,分1个<name>只取变量名称
s1=re.findall(r'.*?\s+(?P<name>\w+?)\s*?=.*?;',str,re.I|re.S); #结果是:['s1', 's2', '价格', 'desc', 'num']
四、 应用扩展
可以采用类似的方法,对任意复杂业务的sql语句进行配置分析,可能有任意多个字段条件操作,每个字段的值可能有或无,或在某一个范围,这样结合前台提交,后台用正则分组,可以提取任意复杂的sql条件查询,适用于hibernate的动态查询,及springjdbc自定义sql
1、 实现需求
当一个分组内容,重复出现2次或以上时,第2次起,可以引用前面定义的分组匹配,即
r’(P?pattern1)…(P?pattern2)…(P?pattern2)…(?P=name1)…(P=name2)…(?P=name3)’
即(?P=name1)重复匹配前面定义的(?Ppattern1),
(?P=name2)重复匹配前面定义的(?Ppattern2)
2、 示例如下
import re
#假设下面的每门课的学费,例如oracle:500元,java:1550元
str='''
oracle:500,
java:1550,
bigdata:2000,
php:500
<oracle>500</oracle>
<java>1550</java>
<bigdata>2000</bigdata>
<php>500</php>
'''
#下面2行代码结果相同
s1=re.findall(r'oracle:(?P<name1>\d+),.*?java:(?P<name2>\d+),.*?bigdata:(?P<name3>\d+),.*?php:(?P<name4>\d+).*?<oracle>(?P=name1)</oracle>.*?<java>(?P=name2)</java>.*?<bigdata>(?P=name3)</bigdata>.*?<php>(?P=name4)</php>.*?',str,re.I|re.S) #结果是:['500', '1800', '2000', '555']
s1=re.findall(r'oracle:(?P<name1>\d+),.*?java:(?P<name2>\d+),.*?bigdata:(?P<name3>\d+),.*?<oracle>(?P=name1)</oracle>.*?<java>(?P=name2)</java>.*?',str,re.I|re.S) #结果是:['500', '1800', '2000', '555']
五、 数字分组,\number
1、 语法说明
每一段正则用一个加圆括起来时,便自动构成一个组,包括(?Ppattern)自定义命名组,也加入到分组序号中
如果后面有前面圆括中相同部分,则用数字序号表示匹配相同部分
r’(正则1)…(正则2)…(正则3) 。。。\1….\2….\3…’,
这里出现\1,表示匹配前面第1个圆括号正则内容,
这里出现\2,表示匹配前面第2个圆括号正则内容
2、 可以用数组分组取得自定义命名分组
例如用:\2 取得前面第2个圆括号(?Ppattern123)的内容
3、 示例如下
import re
#假设下面的每门课的学费,例如oracle:500元,java:1550元,bigdata:2000元,php:500元
str='''
oracle:500,
java:1550,
bigdata:2000,
php:500
<oracle>500</oracle>
<java>1550</java>
<bigdata>2000</bigdata>
<php>500</php>
'''
#下面需求求所有每门课的学费,只求具体数字,体会(?P<name>)用法,下面2行代码结果相同
s1=re.findall(r'\D+:(?P<name>\d+),??',str,re.I|re.S) #结果是:['500', '1550', '2000', '500']
s1=re.findall(r'(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
#下面需求,求出课程名称,及费用,4门课
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
#下面3行代码结果相同,括号的序号从1开始,(?P<name>...)也算一个括号
s1=re.findall(r'(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?<\1>(?P=name1)</\1>.*?<\3>(?P=name2)</\3>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550')]
s1=re.findall(r'(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?<\1>\2</\1>.*?<\3>(?P=name2)</\3>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550')]
s1=re.findall(r'(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?<\1>\2</\1>.*?<\3>\4</\3>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550')]
#下面2行结果相同
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>\2</\1>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>(?P=name1)</\1>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
#下面2行结果相同
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>(?P=name1)</\1>.*?<\3>\4</\3>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>(?P=name1)</\1>.*?<\3>(?P=name2)</\3>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
#下面2行结果相同
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>(?P=name1)</\1>.*?<\3>(?P=name2)</\3>.*?<\5>\6</\5>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>(?P=name1)</\1>.*?<\3>(?P=name2)</\3>.*?<\5>(?P=name3)</\5>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
#下面2行结果相同
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>(?P=name1)</\1>.*?<\3>(?P=name2)</\3>.*?<\5>\6</\5>.*?<\7>\8</\77>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
s1=re.findall(r'.*?(\w+):(?P<name1>\d+),.*?(\w+):(?P<name2>\d+),.*?(\w+):(?P<name3>\d+),.*?(\w+):(?P<name4>\d+).*?<\1>(?P=name1)</\1>.*?<\3>(?P=name2)</\3>.*?<\5>\6</\5>.*?<\7>(?P=name4)</\7>.*?',str,re.I|re.S) #结果是:[('oracle', '500', 'java', '1550', 'bigdata', '2000', 'php', '500')]
s1=re.findall(r'(\D+):(?P<name1>\d+),.*?(\D+):(?P<name2>\d+),.*?(\D+):(?P<name3>\d+),.*?(\D+):(?P<name4>\d+),.*?<\1>(?P=name1)<\1>.*?',str,re.I|re.S) #结果是:['500', '1800', '2000', '555']
#下面
s1=re.findall(r'oracle:(?P<name1>\d+),.*?java:(?P<name2>\d+),.*?bigdata:(?P<name3>\d+),.*?php:(?P<name4>\d+).*?<oracle>(?P=name1)</oracle>.*?<java>(?P=name2)</java>.*?<bigdata>(?P=name3)</bigdata>.*?<php>(?P=name4)</php>.*?',str,re.I|re.S) #结果是:['500', '1800', '2000', '555']
s1=re.findall(r'oracle:(?P<name1>\d+),.*?java:(?P<name2>\d+),.*?bigdata:(?P<name3>\d+),.*?<oracle>(?P=name1)</oracle>.*?<java>(?P=name2)</java>.*?',str,re.I|re.S) #结果是:['500', '1800', '2000', '555']
六、 前置肯定分组,(?=pattern)
1、 实现需求
表示以。。。开头,不消耗匹配内容,而是加入后面正则表达式中,所以也称为前置不消耗分组
r’…(?=pattern1)(?Ppattern123)…’等效于
r’… (?Ppattern1pattern123)…’
2、 示例如下
#前置肯定(?=pattern)
import re
#查询url是否以http://www.开头
s1=re.findall(r'(?=http:\/\/www\.)(?P<name>.*)','http://www.baidu.com') #结果是:['http://www.baidu.com']
s1=re.findall(r'(?=http:\/\/www\.)(?P<name>.*)','https://www.baidu.com') #结果是:[]
七、 前置否定分组,(?!pattern)
1、 实现需求
表示不包含。。。开头的其余部分,
r’…(!pattern1)(?Ppattern123)…’等效于
r’… (?P!pattern1pattern123)…’,pattern1的内容只是一个最小正则内容
2、 示例如下
#前置否定(?!pattern)
import re
#查询url不包含http://开头以外的其余部分
s1=re.findall(r'(?!http:\/\/)(?P<name>www.*)','http://www.baidu.com') #结果是:['www.baidu.com']
#查询所有非数字部分,即前面不包含数字,后面是字母
s1=re.findall(r'(?!\d+)(?P<name>\D+)','123java456oracle367bigdata478') #结果是:['java', 'oracle', 'bigdata']
八、 后置肯定分组,(?<=pattern)
1、 实现需求
表示包含以。。。结尾的所有部分,不消耗匹配内容,而是加入前面分组中
r’… (?Ppattern123)(?<=pattern1)…’等效于
r’… (?Ppattern123pattern1)…’
2、 示例如下
#后置肯定(?<=pattern)
#下面匹配前面是数字一组,后面包含数字结尾的所有分组
import re
s=re.findall(r'(?P<name>\d+)(?<=\d)','987java678abc891abe2345stu2454dy')#结果是:['987', '678', '891', '2345', '2454']
#下面匹配前面是字母一组,后面包含字母的所有分组
s=re.findall(r'(?P<name>\D+)(?<=\D)','java678abc891abe2345stu2454dy')#结果是:['java', 'abc', 'abe', 'stu', 'dy']
九、 后置否定分组,(?
#后置否定(?<=pattern)
#下面匹配前面是数字一组,后面不包含字母的所有分组
s=re.findall(r'(?P<name>\d+)(?<!\D)','987java678abc891abe2345stu2454dy')#结果是:['987', '678', '891', '2345', '2454']
#下面匹配前面是字母一组,后面不包含数字的所有分组
s=re.findall(r'(?P<name>\D+)(?<!\d)','java678abc891abe2345stu2454dy')#结果是:['java', 'abc', 'abe', 'stu', 'dy']
十、 消耗—不捕获-不参与分组的圆括号,(?:pattern)
1、 实现需求
参与匹配,不捕获,即不返回结果,不将匹配结果送给后面
类比前置肯定匹配(?=pattern)也不捕获结果,但将匹配结果送给后面分组
2、 示例如下
str='''
s=http://www-1.baidu.com
s=https://www-2.baidu.com
s=ftp://www-3.baidu.com
'''
#请注意,下面的str后面,没有re.S,否则操作有错,这里只对每一行进行正则匹配捕获
s1=re.findall(r'(?:http|https|ftp):\/\/(?P<name>.*)',str)
#结果是:['www-1.baidu.com', 'www-2.baidu.com', 'www-3.baidu.com']
s1=re.findall(r'(?:http|https|ftp)(?P<name>:\/\/.*)',str)
#结果是:['://www-1.baidu.com', '://www-2.baidu.com', '://www-3.baidu.com']
s1=re.findall(r'(http|https|ftp):\/\/(?P<name>.*)',str)
#结果是:[('http', 'www-1.baidu.com'), ('https', 'www-2.baidu.com'), ('ftp', 'www-3.baidu.com')]
s1=re.findall(r'(?:https:)(?P<name>.*)',str,re.I)
#结果是:['//www-2.baidu.com']
十一、 前置—后置位置颠倒及对比(?:pattern)
1、 实现需求
r‘…(?<=pattern1)mypattern(?=pattern2) …’
将后置放在前面,将前置放在后面,结果是
(?<=pattern1)后置参与匹配、不捕获、消耗
(?=pattern2)前置参与匹置、不捕获、消耗
2、 语法结果理解
1) 将后置放在前面时
因为他只参与后置前一个正则表达式的匹配、捕获、消耗,所以不参与
2) 将前置放在后面时
因为前置只参与他后面的一个前置,对后面的内容捕获,所以本段内容匹配、消耗、不捕获
3) mypattern有无分组,即圆括号,结果是一样的
4) 实际测试时,如果mypattern有圆括号,则此时的前置颠到效果与加入(?:pattern)一样
3、 示例如下
#前置肯定与后置肯定颠倒颠颠位置后,则匹配、不捕获、消耗
import re
str = r'<div class="test1"><h1><span>学习大数据bigData</span></h1></div>'
s1 = re.findall(r"(?<=<h1>).+?(?=</h1>)",str)
#前置与后置颠倒时,则只匹配、不捕获,可以理解为后置己经参与前面一个正则的捕获了,而前置放在后面,则对前面来说,不捕获结果
#结果是:['<span>学习大数据bigData</span>']
s1 = re.findall(r"(?:<h1>).+?(?=</h1>)",str)
#结果是:['<h1><span>学习大数据bigData</span>'],说明(?:pattern)不参与分组,但后面无分组时,则参与消耗
s1 = re.findall(r"(?:<h1>)(?P<id123>.+?)(?=</h1>)",str)
#结果是:['<span>学习大数据bigData</span>'],说明(?:pattern)不参与分组,但后面有分组时,则不参与消耗
s1 = re.findall(r"(?:<h1>)(?P<id123>.+?)(?:</h1>)",str)
#结果是:['<span>学习大数据bigData</span>'],说明(?:pattern)不参与分组,前后有分组时,则不参与消耗
s1 = re.findall(r"(?=<h1>).+?(?=</h1>)",str)
#结果是:['<h1><span>学习大数据bigData</span>'],前置发挥正常作用,前置放在后面时,匹配,不对前面消耗
s1 = re.findall(r"(?:<h1>).+?(?=</h1>)",str)
#结果是:['<h1><span>学习大数据bigData</span>'],说明(?:pattern)对后面无分组时,参与捕获、消耗
s1 = re.findall(r"(?:<h1>)(.+?)(?=</h1>)",str)
#结果是:['<span>学习大数据bigData</span>'],说明(?:pattern)对后面有分组时,消耗、但不参与捕获
created by 刘明
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341