Linux集群之美
上QQ阅读APP看书,第一时间看更新

2.5.1 正则表达式的应用

正则表达式主要用于搜索、替换和解析字符串,它遵循一定的语法规则,使用非常灵活,功能强大。使用正则表达式编写一些逻辑验证非常方便,例如进行电子邮件格式或IP地址的验证。正则表达式在Python爬虫中也是必不可少的利器。re模块使Python语言拥有全部的正则表达式功能,在处理复杂的字符串需求时它也是Python开发工作必不可少的模块之一。

1.正则表达式简介

正则表达式是由字母、数字和特殊字符(括号、星号和问号等)组成的。正则表达式中有许多特殊的字符(也称为元字符),这些特殊字符是构成正则表达式的要素。表2-3说明了正则表达式中特殊字符的含义。

表2-3 Python中各元字符明细表

注意

^与[^m]的定义完全不同,后者中的“^”表示“除了......”的意思;另外,表中的(?P<name>)与(?P=name)是Python中独有的写法,其他的符号在各种编程语言中都是通用的。

(1)原子

原子是正则表达式中最基本的组成单位,每个正则表达式中至少要包含一个原子,常见的原子由普通字符或通用字符和原子表构成。

原子表是由一组地位平等的原子组成的,匹配的时候会取该原子表中的任意一个原子来进行匹配。在Python中,原子表用[]表示,[xyz]就是一个原子表,这个原子表中定义了3个原子,这3个原子的地位平等。

如果我们要对正则表达式进行嵌套,就需要使用分组“()”。即我们可以使用“()”将一些原子组合成一个大原子,小括号括起来的部分会被当作一个整体来使用。

(2)贪婪模式与懒惰模式

其实从字面上就能很好地理解,贪婪模式就是尽可能多地匹配,而懒惰模式就是尽可能少地匹配。下面通过一个实例来理解,代码如下:


#-*-encoding:utf-8-*-
import re

string = 'helolomypythonhistorypythonourpythonend'
p1 = "p.*y"  #贪婪模式
p2 = "p.*?y" #懒惰模式

r1 = re.search(p1,string)
r2 = re.search(p2,string)
print r1.group()
print r2.group()

代码输出结果如下:


pythonhistorypythonourpy
py

通过对比可发现,懒惰模式下采用的是就近匹配原则,可以让匹配更为精确;而在贪婪模式下,就算已经找到一个最近的结尾y字符,仍然不会停止搜索,直到找不到结尾字符y为止,此时结尾的y字符即为源字符串中最右边的y字符。

例如,要将3位数字重复两次,可以使用下面的正则表达式:


(\d\d\d){2}

请将其与下面的正则表达式进行区分:


\d\d\d{2}

该表达式相当于“\d\d\d\d”,匹配的结果为“1234”和“5678”。

如果要匹配电话号码,例如“010-12345678”这样的电话号码,我们一般会采用“\d\d\d-\d\d\d\d\d\d\d\d”这样的正则表达式。这其中出现了11次“\d”,表达方式极为烦琐,而且有些地区的区号也有可能是3位数字或4位数字,因此这种正则表达式就不能满足需求了。另外,电话号码还有很多写法,例如01012345678,或者(010)12345668等,所以我们需要设计一个通用的正则表达式,如下:


[\(]?\d{3}[\)-]?\d{8}|[\(]?\d{4}[\)-]?\d{7}

有兴趣的读者可以关注与电话号码相关的正则代码,示例如下:


import re
#coding:utf-8

te1 = "027-86912233"
print re.findall(r'\d{3}-\d{8}|\d{4}-\d{7}',te1)

te2 = "0755-1234567"
print re.findall(r'\d{3}-\d{8}|\d{4}-\d{7}',te2)

te3= "(010)12345678"
print re.findall(r'[\(]?\d{3}[\)-]?\d{8}',te3)

te4 = "010-12345678"
print re.findall(r'[\(]?\d{3}[\)-]?\d{8}',te4)

结果是可以按照正则匹配打印出相应的电话号码。

2.使用re模块处理正则表达式

Python的re模块具有正则表达式的功能。re模块提供了一些根据正则表达式查找、替换、分隔字符串的函数,这些函数使用正则表达式作为第一个参数。re模块常用的函数见表2-4。

表2-4 re模块各函数的作用明细表

re模块的很多函数中都有一个flags标志位,该参数用于设置匹配的附加选项。例如,是否忽略大小写、是否支持多行匹配等,具体见表2-5。

表2-5 re模块标志位的作用描述

正则表达式的解析非常费时,对此我们可以使用compile()进行预编译,compile()函数返回1个pattern对象。该对象提供一系列方法来查找、替换或扩展字符串,从而提高字符串的匹配速度。此函数通常与match()和search()一起用于对含有分组的正则表达式进行解析。正则表达式的分组从左往右开始计数,第1个出现的为第1组,以此类推。此外还有0号组,0号组用于存储匹配整个正则表达式的结果。

(1)常见函数说明

1)re.match()函数。其使用格式为:


math(pattern,string,flags=0)

第一个参数代表对应的正则表达式,第二个参数代表对应的源字符,第三个参数是可选的flag标志位。

2)re.search()函数。其使用格式为:


search(pattern,string,flags=0)

第一个参数代表对应的正则表达式,第二个参数代表对应的源字符,第三个参数是可选的flag标志位。

re.match()和re.search()的基本语法是一模一样的,那么,它们的区别在哪里呢?re.match只匹配字符串的开始,如果字符串的开始不符合正则表达式,则匹配失败,函数返回None;而re.search则匹配整个字符串(全文搜索),直到找到一个匹配为止。这里举个例子说明:


#-*-encoding:utf-8-*-
import re

string = 'helolomypythonhistorypythonourpythonend'
patt = ".python."
r1 = re.match(patt,string)
r2 = re.search(patt,string)

print r1         #r1打印值为空
print r2         #<_sre.SRE_Match object at 0x10c56f2a0>
print r2.span()  #在起始位置匹配
print r2.group() #匹配整个表达式的字符串

运行结果如下:


None
<_sre.SRE_Match object at 0x10c56f2a0>
(7, 15)
ypythonh

3)全局匹配函数。其上面的例子中,即使源字符串中有多个结果符号模式,也只能提取一个结果。那么,我们如何将符合模式的内容全部匹配出来呢?

首先,使用re.compile()对正则表达式进行预编译,实现更加有效率的匹配。编译后,使用findall()根据正则表达式从源字符串中将匹配的结果全部找出。

代码如下:


#-*-encoding:utf-8-*-
import re

string = 'helolomypythonhistorypythonourpythonend'
pattern = re.compile('.python.') #预编译
result = pattern.findall(string)
print result

运行结果如下:


['ypythonh', 'ypythono', 'rpythone']

我们再看另外一个例子,如下:


#-*-encoding:utf-8-*-
import re

string = 'helolomypythonhistorypythonourpythonend'
pattern = re.compile(".python") #预编译
result = pattern.findall(string)
print result

运行结果如下:


['ypython', 'ypython', 'rpython']

4)re.sub()函数。其很多时候我们需要根据正则表达式来实现替换某些字符串的功能,这时可以使用re.sub()函数来实现,函数格式如下:


sub(pattern,repl,string,count)

其中第一个参数为正则表达式,第二个参数为要替换的字符串,第三个参数为源字符串,第四个参数为可选项,代表最多可替换的次数。如果忽略不写,那么会将符合模式的结果全部替换。

这里举个简单的例子说明:


#-*-coding:utf-8-*-
import re
string = 'helolomypythonhistorypythonourpythonend'
patt = "python."
r1 = re.sub(patt,'php',string)
r2 = re.sub(patt,'php',string,2)
print r1         
print r2

输出结果如下:


helolomyphpistoryphpurphpnd
helolomyphpistoryphpurpythonend

(2)Python正则表达式的常见应用

1)匹配电话号码。先用前面介绍的知识点来整理下数字,例如,对3位数字重复两次,可以使用下面的正则表达式:


(\d\d\d){2}

来看一个简单的例子:


import re

patt = '(\d\d\d){2}'
num='1245987967967867789'
result = re.search(patt,num)
print result.group()

前面已设计过一个通用的正则表达式,下面使用这个通用的适合电话号码的正则表达式进行测试:


#coding:utf-8
import re
telre = "[\(]?\d{3}[\)-]?\d{8}|[\(]?\d{4}[\)-]?\d{7}"
te1 = "027-86912233"
te2 = "02786912233"
te3 = "(027)86912233"
te4 = "(0278)6912233"

print re.search(telre,te1).group()
print re.search(telre,te2).group()
print re.search(telre,te3).group()
print re.search(telre,te4).group()

例子中te1-te4代表的电话号码全都能正常打印出来,结果如下:


027-86912233
02786912233
(027)86912233
(0278)6912233

2)匹配.com或.cn结尾的URL网址。用Python正则表达式来实现并不复杂,代码如下:


#-*- encoding:utf-8 -*-
import re
pattern = "[a-zA-Z]+://[^\s]*[.com|.cn]"
string = "<a 'http://www.163.com/'网易首页</a>"
result = re.search(pattern,string)
print result.group()

打印结果如下:


http://www.163.com

这里主要分析pattern的写法,首先写://是固定的,然后我们要以.com或.cn结尾,所以最后应该是[.com|.cn],在://跟[.com|.cn]之间是不能出现空格的,这里用[^\s],也是应该有字符串相关内容的,所以至少是重复一次,这里用的是[^\s]*而非[^\s]+;同理,前面出现的[a-zA-Z]代表的是任意的字母组合,也得有内容,所以后面得跟上+号,组合起来正则表达式就是:[a-zA-Z]+://[^\s]*[.com|.cn]。

3)匹配电子邮件。示例代码如下:


#-*- encoding:utf-8 -*-
import re
pattern = "^[0-9a-zA-Z_-]{0,19}@[0-9a-zA-Z._-]{1,13}\.[com,cn,net]{1,3}$"
string = "yuhongchun027@163.com"
result = re.search(pattern,string)
print result.group()

打印结果如下:


yuhongchun027@163.com

这个例子也很容易理解,大家要注意这里的{0,19}及{1,13}代表的是位数。

4)腾讯云的镜像触发器。腾讯云的私有仓库很多时候需要通过自定义正则规律来触发镜像自动Push,所以我们需要写一些较复杂的正则规律,在正式部署之前,需要进行如下验证:


import re
pattern = "1.([0-9].){1,2}[0-9]{4}.[0-9]{6}"
string = "1.1.0730.020332"
# string = "1.0.1.0730.020332"
result = re.search(pattern,string)
print result.group()

结果可以正常显示,如下:


1.1.0730.020332

再看另外一个例子:


import re
pattern = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{4}\.\d{6}|v\d{1,3}\.\d{1,3}-release-\d{1,3}'
string= '4.11.0.0801.102331'
result = re.search(pattern,string)

print result.group()

结果可以正常显示,如下:


4.11.0.0801.102331

Python正则表达式在工作中应用得非常多,我们还可以用它来写较复杂的爬虫需求,比如抓取某网站的待定格式图片或CSS等,平时要多注意总结归纳,这样可以加深理解,在开发工作中可以得心应手地写需求。