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'
[...] 匹配一个字符集,这里是匹配的字符集,但是只能匹配一个字符
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()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-16-7c62fc675aee> in <module>()
----> 1 ma.group()
AttributeError: 'NoneType' object has no attribute 'group'
#这时ma为空,表示没有匹配到结果,很简单,因为[abc]中并不包含f
In [**17**]: **print** (type(ma))
<type 'NoneType'>
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))
<type 'NoneType'>
#使用\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()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-30-7c62fc675aee> in <module>()
----> 1 ma.group()
AttributeError: 'NoneType' object has no attribute 'group'
\S 匹配非空白字符
#用\S匹配空字符
In [**33**]: ma = re.match(r'\S',' ')
#结果匹配失败
In [**34**]: **print**(type(ma))
<type 'NoneType'>
#\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))
<type 'NoneType'>
? 重复前一个字符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()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-58-7c62fc675aee> in <module>()
----> 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()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-62-7c62fc675aee> in <module>()
----> 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'
#虽然是个错误,但对我的理解有很大的帮助
<number> 分组所对应的编号
每添加一个分组都会对应的生成一个编号
通过一个经典的例子来看一下,html中的键值对的匹配<root>...</root>
#由于标签唯一的区别是一个有/一个没有
#所以这里的正则表达式使用一个分组(\w+>),后面很巧妙的使用\1来补充
In [**103**]: ma = re.match(r'<(\w+>)\w+</\1','<root>balabala</root>')
In [**104**]: ma
Out[**104**]: <_sre.SRE_Match at 0x103f0a8a0>
#查询结果没有问题
In [**105**]: ma.group()
Out[**105**]: '<root>balabala</root>'
#这时候可以查询一下ma的分组,我们会发现,返回的元组中有一个root>的分组
In [**106**]: ma.groups()
Out[**106**]: ('root>',)
(?P<name>) 给分组起名字
(?P=name) 通过分组名使用分组
由于两个一般是配合使用,这里就写在一起了.
假如一个表达式中含有多个分组,我们在去用\1\2\3\4去区分,将让代码阅读变得困难,所以我们可以给每个分组起一个名字,其实在我看来,可以将分组看成一个变量,(?P<name>)则是给这个变量起了一个名字这个后面会说
使用上面的例子:
#(\w+>)起名为mark,注意:命名需要放在最前面
In [**110**]: ma = re.match(r'<(?P<mark>\w+>)\w+</(?P=mark)','<root>balabala</root>'
...: )
#查询结果没有问题
In [**111**]: ma.group()
Out[**111**]: '<root>balabala</root>'
#查询groups也能查找到分组
In [**112**]: ma.groups()
Out[**112**]: ('root>',)
昨天脑洞大开写了一个这样的问题:
#想的是模拟一个邮箱验证,前面要求4-10位,后面是@163或者是126,后面.com之后在跟一个163或者126
In [**115**]: ma = re.match(r'^\w{4,10}@(?P<email>163|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}@(?P<email>163|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}@(?P<email>163|126).com(?P=email)','hexiaojia
...: n@163.com126')
In [**118**]: print (type(ma))
<type 'NoneType'>
想来想去只有一种可能,那就是,当你第一次使用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的了