了解了正则表达式,想必一般情况下的匹配都不会出现什么问题,但是如果一些特殊情况,可能需要用到一些更高级的正则表达式匹配操作,本节我们来说明一下正则表达式的一个较常用又比较重要的知识点——零宽断言。
实例引入
首先我们来看一个例子,这里有一段问答对话:
问:我用的是Windows XP+Service Pack 2,为什么无法安装输入卡号和密码的控件? 答:在Windows XP+Service Pack 2、Windows 2003等操作系统中,用户可以自己选择是否安装控件。 问:为什么我看到的卡号输入框显示为*符号? 答:您的浏览器禁止下载执行ActiveX控件 , 对于这种情况 , 您必须打开浏览器的ActiveX的相关权限。 操作方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择”安全” |”Internet”|”自定义级别”,在弹出的对话框中选择”重置为 安全级-中” , 点”重置”按钮,确定。 问:看了以上几个问题,还是不能登录,怎么办? 答:您的浏览器由于其他原因不能安装招商银行登录控件, 请下载并安装招商银行登录控件下载版。 问:无法出现个人网上银行大众版登录界面。 答:这种情况是由于您的机器无法和我行服务器建立安全连接,通常是因为代理服务器设置错误引起。如果您是拨号上网,请不要使用代理服务器;如果您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;如果您是经过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项–>连接–>局域网设置–>使用代理服务器–>高级。 问:我在输入账号和卡号时,总出错,该怎样输? 答:存折账号为10位,按存折本上的账号输入, 密码为6位。如果一卡通是12位卡号的,只需输入地区码后面的8位卡号,不需要输入前面4位的地区码,密码为6位。如果一卡通是16位卡号的,请将16位卡号全部输入,密码为6位。 问:我的存折没有设密码,怎样在个人网上银行大众版中查询余额? 答:存折必须设有密码方可在 个人网上银行大众版 中查询,因此请您到存折开户行给您的存折设置密码。 注:网上个人银行是招商银行为个人客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。
我们需要将这段对话中的问题和答案对提取出来,即提取出如下内容:
Q:我用的是Windows XP+Service Pack 2,为什么无法安装输入卡号和密码的控件?
A:在Windows XP+Service Pack 2、Windows 2003等操作系统中,用户可以自己选择是否安装控件。
Q:为什么我看到的卡号输入框显示为*符号?
A:您的浏览器禁止下载执行ActiveX控件 , 对于这种情况 , 您必须打开浏览器的ActiveX的相关权限。 操作方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择”安全” |”Internet”|”自定义级别”,在弹出的对话框中选择”重置为 安全级-中” , 点”重置”按钮,确定。
…
如果要用 Python 实现的话,那么我们很可能自然而然想到 split() 或 findall() 方法,如果用 split() 方法,我们可能会这么写:
1
2
3
4
|
import re
results = re.split('问:| 答:', text)
for index, result in enumerate(results[1:]):
print(('Q' if index%2 == 0 else 'A') + ': ' + result)
|
这里 split() 方法的第一个参数传入了
问:| 答:
这个正则表达式,意思是将这段话用
问:
或者
答:
分开,这个功能是正则表达式对字符串进行分割的方法,相比直接字符串的 split() 方法功能更为强大。这里其实得到的结果是一个列表,长度是一个奇数,如果我们把 results 打印出来,结果是这样的:
1
|
['', '我用的是Windows XP+Service Pack 2,为什么无法安装输入卡号和密码的控件?', '在Windows XP+Service Pack 2、Windows 2003等操作系统中,用户可以自己选择是否安装控件。 ', '为什么我看到的卡号输入框显示为*符号?', '您的浏览器禁止下载执行ActiveX控件 , 对于这种情况 , 您必须打开浏览器的ActiveX的相关权限。 操作方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,确定。 ', '看了以上几个问题,还是不能登录,怎么办?', '您的浏览器由于其他原因不能安装招商银行登录控件, 请下载并安装招商银行登录控件下载版。 ', '无法出现个人网上银行大众版登录界面。', '这种情况是由于您的机器无法和我行服务器建立安全连接,通常是因为代理服务器设置错误引起。如果您是拨号上网,请不要使用代理服务器;如果您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;如果您是经过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项-->连接-->局域网设置-->使用代理服务器-->高级。 ', '我在输入账号和卡号时,总出错,该怎样输?', '存折账号为10位,按存折本上的账号输入, 密码为6位。如果一卡通是12位卡号的,只需输入地区码后面的8位卡号,不需要输入前面4位的地区码,密码为6位。如果一卡通是16位卡号的,请将16位卡号全部输入,密码为6位。 ', '我的存折没有设密码,怎样在个人网上银行大众版中查询余额?', '存折必须设有密码方可在 个人网上银行大众版 中查询,因此请您到存折开户行给您的存折设置密码。 注:网上个人银行是招商银行为个人客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。 ']
|
这是因为我们分割使用的字符本身就处于整个文本的字符,所以一上来就找到了分割的标志
问:
,所以它左侧的结果就是空字符串了,所以最终得到的结果第一个内容就是空字符串,后续的内容便是正常的一问一答的短句。所以这里我们还需要对结果进行切片操作,去除第一个元素,然后将其遍历打印输出,最终结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
Q: 我用的是Windows XP+Service Pack 2,为什么无法安装输入卡号和密码的控件?
A: 在Windows XP+Service Pack 2、Windows 2003等操作系统中,用户可以自己选择是否安装控件。
Q: 为什么我看到的卡号输入框显示为*符号?
A: 您的浏览器禁止下载执行ActiveX控件 , 对于这种情况 , 您必须打开浏览器的ActiveX的相关权限。 操作方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,确定。
Q: 看了以上几个问题,还是不能登录,怎么办?
A: 您的浏览器由于其他原因不能安装招商银行登录控件, 请下载并安装招商银行登录控件下载版。
Q: 无法出现个人网上银行大众版登录界面。
A: 这种情况是由于您的机器无法和我行服务器建立安全连接,通常是因为代理服务器设置错误引起。如果您是拨号上网,请不要使用代理服务器;如果您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;如果您是经过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项-->连接-->局域网设置-->使用代理服务器-->高级。
Q: 我在输入账号和卡号时,总出错,该怎样输?
A: 存折账号为10位,按存折本上的账号输入, 密码为6位。如果一卡通是12位卡号的,只需输入地区码后面的8位卡号,不需要输入前面4位的地区码,密码为6位。如果一卡通是16位卡号的,请将16位卡号全部输入,密码为6位。
Q: 我的存折没有设密码,怎样在个人网上银行大众版中查询余额?
A: 存折必须设有密码方可在 个人网上银行大众版 中查询,因此请您到存折开户行给您的存折设置密码。 注:网上个人银行是招商银行为个人客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。
|
这样确实没问题,我们可以顺利地提取出来,但是总感觉这个解法并不那么优雅,因为我们这里是将问题和答案的内容都单独切出来了,并没有将问答对一块提取,而且 split() 方法返回的结果的第一个元素还不是我们想要的结果,所以还需要进行一些切片操作来去除,所以整个写法感觉实现起来并不完美。
所以我们又想到了 findall() 方法,这时我们会这么写:
1234import reresults = re.findall('问:(.*?) 答:(.*?)', text, re.S)for result in results: print('Q: ' + result[0], 'A: ' + result[1], sep='\n')
表面上看似乎是把问题答案对用正则表示出来了,而且使用了非贪婪匹配,但是很明显,在末尾我们并没有指定匹配的终点,所以整个的结果就会导致回答是完全匹配不到的,运行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
Q: 我用的是Windows XP+Service Pack 2,为什么无法安装输入卡号和密码的控件?
A:
Q: 为什么我看到的卡号输入框显示为*符号?
A:
Q: 看了以上几个问题,还是不能登录,怎么办?
A:
Q: 无法出现个人网上银行大众版登录界面。
A:
Q: 我在输入账号和卡号时,总出错,该怎样输?
A:
Q: 我的存折没有设密码,怎样在个人网上银行大众版中查询余额?
A:
|
好,那么我们加上匹配的终点吧,以下一个的
问:
作为我们正则表达式匹配的终点总可以了吧?所以我们可能会改写成这样子:
1234import reresults = re.findall('问:(.*?) 答:(.*?)问:', text, re.S)for result in results: print('Q: ' + result[0], 'A: ' + result[1], sep='\n')
这样写似乎看起来是可以了,但结果却是这样的:
1
2
3
4
5
6
|
Q: 我用的是Windows XP+Service Pack 2,为什么无法安装输入卡号和密码的控件?
A: 在Windows XP+Service Pack 2、Windows 2003等操作系统中,用户可以自己选择是否安装控件。
Q: 看了以上几个问题,还是不能登录,怎么办?
A: 您的浏览器由于其他原因不能安装招商银行登录控件, 请下载并安装招商银行登录控件下载版。
Q: 我在输入账号和卡号时,总出错,该怎样输?
A: 存折账号为10位,按存折本上的账号输入, 密码为6位。如果一卡通是12位卡号的,只需输入地区码后面的8位卡号,不需要输入前面4位的地区码,密码为6位。如果一卡通是16位卡号的,请将16位卡号全部输入,密码为6位。
|
结果只剩三个问题答案对了,有三个问答对被“吃”掉了,其实这是因为我们的正则表达式最后加了
问:
的缘故,findall() 方法它会查找所有符合正则表达式的结果,但其中匹配的时候它内部也是有一个查找索引在扫描的。在查找第一个符合要求的结果时,由于我们是根据正则表达式结尾的
问:
来作为结束标志,所以在找到第一个符合要求的结果时,我们的查找索引就已经移动到了第二个问答对开头的
问:
上面,即查找索引就已经进入到了第二个问答对的位置了,而在下一次查找符合要求的结果时,索引会继续往后移动进行扫描,所以它是从第二个问答对的
问:
后面继续扫描的,所以对于第二个问答对,实际上已经被割裂了,所以它只能查找到第三个问答对的时候才可以发现符合正则表达式的内容。因此,我们可以观察到,返回的结果只是第一、三、五三个问答对。
所以,如果我们想要用该方法找到完整的留个问答对,就需要用到零宽断言了。
解法如下:
1234import reresults = re.findall('问:(.*?) 答:(.*?)(?=问:|\Z)', text, re.S)for result in results: print('Q: ' + result[0], 'A: ' + result[1], sep='\n')
运行结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
Q: 我用的是Windows XP+Service Pack 2,为什么无法安装输入卡号和密码的控件?
A: 在Windows XP+Service Pack 2、Windows 2003等操作系统中,用户可以自己选择是否安装控件。
Q: 为什么我看到的卡号输入框显示为*符号?
A: 您的浏览器禁止下载执行ActiveX控件 , 对于这种情况 , 您必须打开浏览器的ActiveX的相关权限。 操作方法:在浏览器菜单中选择“工具”|“Internet选项”,在弹出的对话框中选择"安全" |"Internet"|"自定义级别",在弹出的对话框中选择"重置为 安全级-中" , 点"重置"按钮,确定。
Q: 看了以上几个问题,还是不能登录,怎么办?
A: 您的浏览器由于其他原因不能安装招商银行登录控件, 请下载并安装招商银行登录控件下载版。
Q: 无法出现个人网上银行大众版登录界面。
A: 这种情况是由于您的机器无法和我行服务器建立安全连接,通常是因为代理服务器设置错误引起。如果您是拨号上网,请不要使用代理服务器;如果您过去安装过我行SSL安全代理,请调用“添加-删除程序”删除SSL安全代理;如果您是经过代理访问Internet,请联系您所在网的网络管理员设置代理服务器。IE5.0浏览器设置代理服务器的步骤: Internet选项-->连接-->局域网设置-->使用代理服务器-->高级。
Q: 我在输入账号和卡号时,总出错,该怎样输?
A: 存折账号为10位,按存折本上的账号输入, 密码为6位。如果一卡通是12位卡号的,只需输入地区码后面的8位卡号,不需要输入前面4位的地区码,密码为6位。如果一卡通是16位卡号的,请将16位卡号全部输入,密码为6位。
Q: 我的存折没有设密码,怎样在个人网上银行大众版中查询余额?
A: 存折必须设有密码方可在 个人网上银行大众版 中查询,因此请您到存折开户行给您的存折设置密码。 注:网上个人银行是招商银行为个人客户提供的网上银行。 本页面内容仅供参考,部分业务以当地网点的公告与具体规定为准。
|
这里我们实际上是使用了
(?=)
这样的形式来构建了整个表达式,等号后面的内容是
问:
或者结束符
\Z
,这样其实就保证了在匹配的时候,查找索引不会继续向后移,但这也同时标志了结束标志,因此它就可以查找到完整的内容了。
零宽断言
零宽断言,顾名思义,是一种零宽度的匹配,它匹配的内容不会保存到匹配结果中,表达式的匹配内容只是代表了一个位置而已,如标明某个字符的右边界是怎样的构造。
在前面我们使用了
?=
来进行了实例讲解,这是其中一个用法,另外还有
?<=
、
?!
、
?<!
,下面我们来依次进行讲解说明。
-
?=
代表零宽度正预测先行断言,它断言自身出现的位置的后面可以匹配后面跟的表达式。 -
?<=
代表零宽度正回顾后发断言,它断言自身出现的位置的前面可以匹配后面跟的表达式。 -
?!
代表零宽度负预测先行断言,它断言自身出现的位置的后面不可以匹配后面跟的表达式。 -
?<!
代表零宽度负回顾后发断言,它断言自身出现的位置的后面不可以匹配后面跟的表达式。
?=
首先我们来看下
?=
的用法,它断言自身出现的位置的后面可以匹配后面跟的表达式。
比如我们这里有这样的一个字符串:
1str = '我的个人邮箱是[email protected],个人博客是cuiqingcai.com,个人公众号是进击的Coder'
在这里我们想把我的个人邮箱这句话和个人邮箱单独摘出来,假如我们不使用零宽断言的话,我们需要给个人邮箱后面这一句加一个结束标识符或者单独匹配邮箱作为标识符,我们可能会这么写:
1234import restr = '我的个人邮箱是[email protected],个人博客是cuiqingcai.com,个人公众号是进击的Coder'result = re.search('我的个人邮箱是(.*?),个人博客', str)print('整句结果:' + result.group(), '第一个匹配结果:' + result.group(1), sep='\n')
在正则表达式的最后我们加了
,个人博客
作为匹配的结束符,然后邮箱部分用非贪婪匹配的模式进行匹配,我们看下运行结果:
我们可以看到第一个匹配结果成功得到了邮箱信息,但是我们看整句结果缺并不理想,它多匹配了我们加入的结尾标识,并没有得到正常的一句话。
这时候如果我们改用
?=
来匹配,结果就不会带有此标识符了,改写如下:
1
2
3
4
|
import re
str = '我的个人邮箱是[email protected],个人博客是cuiqingcai.com,个人公众号是进击的Coder'
result = re.search('我的个人邮箱是(.*?)(?=,个人博客)', str)
print('整句结果:' + result.group(), '第一个匹配结果:' + result.group(1), sep='\n')
|
在这里我们将结尾标识符改成了
(?=,个人博客)
,这样就将此部分内容作为零宽度匹配,它代表后面需要跟
,个人博客
,但是它不会出现在匹配结果中。
运行结果如下: