Python正则新解

由于最近在使用BeautifulSoup时,发现文档中有这么一段:

import re for tag in soup.find_all(re.compile("^b")): print(tag.name)

通过正则表达式来查询数据,虽然之前也了解过但是整体还是处于朦胧状态,昨天看了几个视频受益颇多,下面来分析一下.
正则匹配一般匹配什么 百度百科,有句话许多程序设计语言都支持利用正则表达式进行字符串操作,也就是说我们一般都是针对字符串来操作,字符串由字符构成,那么字符又有几种呢.
字符种类 1.a-zA-Z 英文字符 2.0-9 数字字符 3.[].,/? 等符号字符 4.其他 知道了字符的组成,我们在看看如何匹配
表达式分类
1.单个字符匹配(请在使用中添加re模块) . 匹配任意一个字符
#通过.匹配一个英文字符 In [**2**]: ma = re.match(r'.','h')#可以查看ma的类型 In [**3**]: ma Out[**3**]: <_sre.SRE_Match at 0x103ddb098>In [**4**]: ma.group() Out[**4**]: 'h'#通过.匹配一个符号字符 In [**5**]: ma = re.match(r'.','[')In [**6**]: ma.group() Out[**6**]: '['#通过.匹配一个数字字符 In [**7**]: ma = re.match(r'.','7')In [**8**]: ma.group() Out[**8**]: '7'

[...] 匹配一个字符集,这里是匹配的字符集,但是只能匹配一个字符 【Python正则新解】1.当你只想匹配a/b/c时,你会发现使用.是不合适的,这时可以通过[abc]来实现
#通过[abc]来匹配字符a In [**13**]: ma = re.match(r'[abc]','a')In [**14**]: ma.group() Out[**14**]: 'a'#通过[abc]来匹配f In [**15**]: ma = re.match(r'[abc]','f')#我们会发现ma没有group()方法 In [**16**]: ma.group() --------------------------------------------------------------------------- AttributeErrorTraceback (most recent call last) in () ----> 1 ma.group()AttributeError: 'NoneType' object has no attribute 'group'#这时ma为空,表示没有匹配到结果,很简单,因为[abc]中并不包含f In [**17**]: **print** (type(ma))

2.在[]内开头添加^表示不等于
如 [^123]
几个特殊的字符集匹配,与上面一样,依旧是单个字符匹配 \d 匹配数字字符,这里与C语言的%d一样 使用decimal的缩写来表示数字
#使用\d匹配数字 In [**20**]: ma = re.match(r'\d','2')In [**21**]: ma.group() Out[**21**]: '2'

\D 匹配非数字字符
#使用\D匹配数字字符 In [**22**]: ma = re.match(r'\D','2')In [**23**]: ma #结果匹配不到结果 In [**24**]: **print**(type(ma)) #使用\D匹配非数字字符 In [**25**]: ma = re.match(r'\D','+') #结果正确 In [**26**]: ma.group() Out[**26**]: '+'

\s 匹配空白字符
#使用\s匹配空字符 In [**27**]: ma = re.match(r'\s',' ') #结果正确 In [**28**]: ma.group() Out[**28**]: ' ' #使用\s匹配非空字符 In [**29**]: ma = re.match(r'\s','w') #没有正确匹配 In [**30**]: ma.group() --------------------------------------------------------------------------- AttributeErrorTraceback (most recent call last) in () ----> 1 ma.group()AttributeError: 'NoneType' object has no attribute 'group'

\S 匹配非空白字符
#用\S匹配空字符 In [**33**]: ma = re.match(r'\S','') #结果匹配失败 In [**34**]: **print**(type(ma)) #\S匹配非空字符 In [**35**]: ma = re.match(r'\S','c') #成功匹配 In [**36**]: ma.group() Out[**36**]: 'c'

\w 匹配单词字符 也就是 [a-zA-Z0-9]这个字符集
#用\w匹配单词字符 In [**37**]: ma = re.match(r'\w','c')In [**38**]: ma.group() Out[**38**]: 'c'

\W 匹配非单词字符 也就是除了数字英文数字之外的符号等字符
#用\W匹配非单词字符 In [**39**]: ma = re.match(r'\W','[')In [**40**]: ma.group() Out[**40**]: '['

2.匹配多个字符的正则表达式 *重复前一个字符0次或无限次 来个简单例子:
#当使用单个匹配时,只能匹配到第一个字符 In [**41**]: ma = re.match(r'a','aaa')In [**42**]: ma.group() Out[**42**]: 'a' #使用多个匹配时,可以匹配到多个 In [**43**]: ma = re.match(r'a*','aaa')In [**44**]: ma.group() Out[**44**]: 'aaa' #这里可以看出可以匹配0个a于是返回一个空,可以与+对比看一下 In [**45**]: ma = re.match(r'a*','')In [**46**]: ma.group() Out[**46**]: ''

+ 重复前一个字符至少1次或者无限次
In [**47**]: ma = re.match(r'a+','aaaaa') #跟*一样可以匹配到 aaaaa In [**48**]: ma.group() Out[**48**]: 'aaaaa' #与*不同 In [**49**]: ma = re.match(r'a+','') #+不支持重复0次 In [**50**]: **print**(type(ma))

? 重复前一个字符0次或1次
#不同于+/*?只会匹配返回1个字符 In [**51**]: ma = re.match(r'a?','aaa')In [**52**]: ma.group() Out[**52**]: 'a' #如果为空,a? 也会匹配到0个字符 In [**53**]: ma = re.match(r'a?',' ')In [**54**]: ma.group() Out[**54**]: ''

{m} 重复前一个字符m次
#匹配三个aaa 于是使用a{3},但是不会匹配到所有 In [**55**]: ma = re.match(r'a{3}','aaaaa')In [**56**]: ma.group() Out[**56**]: 'aaa'#因为要匹配的'aa'不满足三个 于是返回为NoneType In [**57**]: ma = re.match(r'a{3}','aa')In [**58**]: ma.group() --------------------------------------------------------------------------- AttributeErrorTraceback (most recent call last) in () ----> 1 ma.group()AttributeError: 'NoneType' object has no attribute 'group'

{m,n}重复前一个字符m-n次
#想要匹配3-5个a ,这里可以看到能匹配到3个 In [**59**]: ma = re.match(r'a{3,5}','aaa')In [**60**]: ma.group() Out[**60**]: 'aaa'#想要匹配3-5个a ,这里可以看到并不能匹配到少于3个的字符串 In [**61**]: ma = re.match(r'a{3,5}','aa')In [**62**]: ma.group() --------------------------------------------------------------------------- AttributeErrorTraceback (most recent call last) in () ----> 1 ma.group()AttributeError: 'NoneType' object has no attribute 'group'#这里很有意思,明明有6个已经超越了5个为什么还能匹配到 #因为这里只要包含3-5个6个肯定包含5个所以能够匹配到 In [**63**]: ma = re.match(r'a{3,5}','aaaaaa')In [**64**]: ma.group() Out[**64**]: 'aaaaa'

*?,+?,??这三个都是将匹配模式变为非贪婪模式 去查阅后才明白非贪婪模式,就是将最小匹配结果返回
举两个例子
#先让我们看一下贪婪模式下的结果 In [**3**]: ma = re.match(r'a*','aaaa') #你会发现匹配结果将会是满足要求的最大结果,如果我变成10个a就会返回10个a In [**4**]: ma.group() Out[**4**]: 'aaaa' #然后看下非贪婪模式下的匹配结果 In [**5**]: ma = re.match(r'a*?','aaaa') #None? 为什么是None ? 回忆一下*的作用,匹配0个或无限个前一个字符,所以在非贪婪模式下,将会按最小的0个来匹配,所以返回的是一个None. In [**6**]: ma.group() Out[**6**]: ''

再看一下+?
#同样先看一下贪婪模式 In [**13**]: ma = re.match(r'a+','aaaaaaaaaa') #会返回最大匹配结果 In [**14**]: ma.group() Out[**14**]: 'aaaaaaaaaa' #非贪婪模式下的结果 In [**15**]: ma = re.match(r'a+?','aaaa') #由于+是匹配1个或者无限个前一个字符,所以非贪婪模式下,最小结果也就是返回一个,没毛病. In [**16**]: ma.group() Out[**16**]: 'a'

续: 3.边界匹配 边界匹配可以说是什么实用的一个匹配符号,包括:
^ 文档中说:匹配字符串的开头,同时会匹配多行模式下的每一行的开头。
#先来创建一个多行的字符串 In [**42**]: str = '''aaafirst ...: aaasecond ...: aaathird ...: '''

#需注意,1.这里用的findall,没有使用match,2.通过re.M将匹配变为多行模式 In [**55**]: findList = re.findall(r'^aaa.*',str,re.M) #我们发现结果为三个 In [**56**]: findList Out[**56**]: ['aaafirst', 'aaasecond', 'aaathird']

千万不要漏掉re.M,否则就会这样:
#不加re.M In [**62**]: findList = re.findall(r'^aaa.*',str) #结果只返回了一个 In [**63**]: findList Out[**63**]: ['aaafirst']

$ 与^类似,只是$会匹配字符串末尾
#首先来创建一个字符串 In [**64**]: str = ''' firstaaa ...: secondaaa ...: thirdaaa ...: '''

#不要忘记re.M哦 In [**65**]: ma = re.findall(r'.*aaa$',str,re.M)In [**66**]: ma Out[**66**]: ['firstaaa', 'secondaaa', 'thirdaaa']

\A 文档中:仅仅是匹配字符串开头 我们用同样的代码看一下\A 与 ^的区别
#先来创建一个多行的字符串 In [**42**]: str = '''aaafirst ...: aaasecond ...: aaathird ...: '''

#来试一下re.M情况下的\A,匹配结果 In [**74**]: ma = re.findall(r'\Aaaa.*',str,re.M) #我们可以看到只返回了一个,与^不加re.M的结果一样 In [**75**]: ma Out[**75**]: ['aaafirst'] #再看一下不带re.M的\A匹配结果 In [**76**]: ma = re.findall(r'\Aaaa.*',str) #不出意外只返回第一个结果 In [**77**]: ma Out[**77**]: ['aaafirst']

\Z 与\A类似,只是\Z仅仅是匹配字符串的末尾 同样的方式,举个例子
#首先来创建一个字符串 #这里请注意不要将'''放到下一行,否则thirdaaa后面会有一个\n将会无法匹配到结果In [**64**]: str = ''' firstaaa ...: secondaaa ...: thirdaaa '''

#结果与预想一样,只返回了末尾的这一个 In [**82**]: ma = re.findall(r'.*aaa\Z',str,re.M)In [**83**]: ma Out[**83**]: ['thirdaaa'] #不带re.M的也是这样 In [**84**]: ma = re.findall(r'.*aaa\Z',str)In [**85**]: ma Out[**85**]: ['thirdaaa']

4.分组匹配 分组匹配可以让匹配更加灵活
| 匹配左右任意一个分组,相当于或者 先来假设一个场景:aaa或者bbb
#让我们来匹配一下aaa In [**86**]: ma = re.match(r'aaa|bbb','aaa') #成功 In [**88**]: ma.group() Out[**88**]: 'aaa'#匹配一下bbb In [**89**]: ma = re.match(r'aaa|bbb','bbb') #同样成功 In [**91**]: ma.group() Out[**91**]: 'bbb'

() ()内将会被看成一个分组 先来假设一个场景:aa_bb在_处要求可以同时匹配a也可以同时匹配b
如果没有()我们会怎么写,如果是我,我大概会这样: #通过 | 来区分 In [**92**]: ma = re.match(r'aaabb|aabbb','aaabb') #结果没有问题 In [**93**]: ma.group() Out[**93**]: 'aaabb'使用() 就会方便很多 #使用(a|b) 将a|b看成一个分组 In [**94**]: ma = re.match(r'aa(a|b)bb','aaabb') #结果没有问题 In [**95**]: ma.group() Out[**95**]: 'aaabb' #我们试一下aabbb有没有问题 In [**96**]: ma = re.match(r'aa(a|b)bb','aabbb')In [**97**]: ma.group() Out[**97**]: 'aabbb'#这里说一个我自己办的一个愚蠢的事情,当我看到这个问题的时候第一反应只想到了 | 然而结果... #本以为 | 只是识别 a|b 结果发现 它是将 aaa看成一组 bbb看成一组 In [**100**]: ma = re.match(r'aaa|bbb','aaabb')In [**102**]: ma.group() Out[**102**]: 'aaa' #虽然是个错误,但对我的理解有很大的帮助

分组所对应的编号 每添加一个分组都会对应的生成一个编号
通过一个经典的例子来看一下,html中的键值对的匹配...
#由于标签唯一的区别是一个有/一个没有 #所以这里的正则表达式使用一个分组(\w+>),后面很巧妙的使用\1来补充 In [**103**]: ma = re.match(r'<(\w+>)\w+balabala')In [**104**]: ma Out[**104**]: <_sre.SRE_Match at 0x103f0a8a0> #查询结果没有问题 In [**105**]: ma.group() Out[**105**]: 'balabala' #这时候可以查询一下ma的分组,我们会发现,返回的元组中有一个root>的分组 In [**106**]: ma.groups() Out[**106**]: ('root>',)

(?P) 给分组起名字 (?P=name) 通过分组名使用分组 由于两个一般是配合使用,这里就写在一起了.
假如一个表达式中含有多个分组,我们在去用\1\2\3\4去区分,将让代码阅读变得困难,所以我们可以给每个分组起一个名字,其实在我看来,可以将分组看成一个变量,(?P)则是给这个变量起了一个名字这个后面会说
使用上面的例子:
#(\w+>)起名为mark,注意:命名需要放在最前面 In [**110**]: ma = re.match(r'<(?P\w+>)\w+balabala' ...: ) #查询结果没有问题 In [**111**]: ma.group() Out[**111**]: 'balabala' #查询groups也能查找到分组 In [**112**]: ma.groups() Out[**112**]: ('root>',)

昨天脑洞大开写了一个这样的问题:
#想的是模拟一个邮箱验证,前面要求4-10位,后面是@163或者是126,后面.com之后在跟一个163或者126 In [**115**]: ma = re.match(r'^\w{4,10}@(?P163|126).com(?P=email)','hexiaojia ...: n@163.com163') #这里是没有问题 In [**116**]: ma.group() Out[**116**]: 'hexiaojian@163.com163'#前后同为126时也没有问题 In [**119**]: ma = re.match(r'^\w{4,10}@(?P163|126).com(?P=email)','hexiaojia ...: n@126.com126')In [**121**]: ma.group() Out[**121**]: 'hexiaojian@126.com126'#问题在这,当我想去匹配hexiaojian@163.com126时,匹配失败. #我使用的是(163|126)在后面使用时为什么不识别126 In [**117**]: ma = re.match(r'^\w{4,10}@(?P163|126).com(?P=email)','hexiaojia ...: n@163.com126')In [**118**]: print (type(ma))

想来想去只有一种可能,那就是,当你第一次使用email时就已经给email赋值,当再次使用时会进行检查.
查阅网址:
http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html 其实这个要比我的全很多,大家可以仔细研究一番.
https://docs.python.org/3/library/re.html 因为现在再用3.5了,就不推荐2.7的了
感谢大家收看,欢迎拍砖

    推荐阅读