文本处理被认为是编程中最常用的功能之一,例如,从一大堆HTML代码中找到href属性值为某个Url的a标签就是网络爬虫经常要做的工作。
11.1 在python语言中使用正则表达式
python语言通过标准库中的re模块支持正则表达式。
11.1.1 使用match方法匹配字符串
匹配字符串是正则表达式中最常用的一类应用。也就是设定一个文本模式,然后判断另外一个字符串是否符合这个文本模式。
match方法用于指定文本模式和待匹配的字符串。该方法的前两个参数必须指定,第1个参数表示文本模式,第2个参数表示待匹配的字符串。如果匹配成功,则match方法返回 SRE_Match 对象,然后可以调用该对象中的group方法获取匹配成功的字符串,如果文本模式就是一个普通的字符串,那么group方法返回的就是文本模式字符串本身。
m = re.match('brid','bird')
print(m.group())
示例:
import re
m = re.match('hello', 'hello')
if m is not None:
print(m.group())
print(m.__class__.__name__)
m = re.match('hello', 'world')
if m is not None:
print(m.group())
print(m)
# 注意:只要模式从字符串起始位置开始,就可以匹配成功
m = re.match('hello', 'hello world')
if m is not None:
print(m.group())
print(m)
hello
SRE_Match
None
hello
<_sre.SRE_Match object; span=(0, 5), match='hello'>
11.1.2 使用 search 方法在一个字符串中查找模式
搜索是正则表达式的另一类常用的应用场景。也就是从一段文本中找到一个或多个与文本模式相匹配的字符串。
search方法的传参与match方法类似,示例如下:
import re
# 匹配失败,返回 none
m = re.match('python','I love python.')
if m is not None:
print(m.group())
print(m)
# 搜索成功,返回 python
m = re.search('python','I love python.')
if m is not None:
print(m.group())
print(m)
从SRE_Math 对象(m 变量)的输出信息可以看出,span的值是(7,13),表明满足文本模式的字符串的起始位置的索引是7,结束位置的下一个字符的索引是13。
11.1.3 匹配多个字符串
如果想要搜索多个字符串,最简单的方法就是在文本模式字符串中使用匹配符号 (|),只要满足任何一个就算匹配成功。
s = 'Bill|Mike|John'
m = re.match(s, 'Bill')
if m is not None:
print(m.group())
m = re.match(s, 'Mike')
if m is not None:
print(m.group())
# 以上均匹配成功
示例:
import re
s = 'Bill|Mike|John'
m = re.match(s, 'Bill')
if m is not None:
print(m.group())
m = re.match(s, "Bill:my friend")
if m is not None:
print(m.group())
m = re.search(s,'Where is Mike?')
if m is not None:
print(m.group())
print(m)
# 以上均匹配成功
11.1.4 匹配任何单个字符
前边给出的文本模式字符串都是精确匹配。在正则表达式中,最常用的是匹配一类字符串。所以就需要使用一些特殊符号表示一类字符串。
点(.),这个符号可以匹配任意一个单个字符。
m = re.match('.ind','bind') # 匹配成功
如果要匹配真正的点(.),应该如何处理?要解决这个问题,需要使用转义符(\)。
m = re.match('\.ind','bind') # 匹配失败
示例:
import re
s = '.ind'
m = re.match(s, 'bind')
if m is not None:
print(m.group())
# 匹配成功,但是得到的m对象需要使用group方法才能获取内容
m = re.match(s,'binding')
# 字符串拼接
print("<" + str(m))
# 匹配失败
m = re.match(s,'bin')
print(m)
# 搜索成功
m = re.search(s,'<bind>')
print(m.group())
print(m)
s1 = '3.14'
s2 = '3\.14'
# 匹配成功
m = re.match(s1, '3.14')
print(m)
# 匹配成功
m = re.match(s1, '3314')
print(m)
# 匹配成功
m = re.match(s2, '3.14')
print(m)
# 匹配失败
m = re.match(s2, '3314')
print(m)
11.1.5 使用字符集
如果匹配的字符串中,某些字符可以有多种选择,就需要使用字符集([ ])。例如,[abc] 相当于 “a|b|c”。对于长度大于1的字符串使用或关系时,字符集就无能为力了。
import re
# 成功
m = re.match('[ab][cd][ef][gh]', 'adfh')
print(m.group())
# 成功
m = re.match('[ab][cd][ef][gh]', 'bceg')
print(m.group())
# 不匹配
m = re.match('[ab][cd][ef][gh]', 'abceh')
print(m)
# 成功
m = re.match('ab[cd][ef][gh]', 'abceh')
print(m.group())
print(m)
# 成功
m = re.match('abcd|efgh', 'efgh')
print(m.group())
print(m)
11.1.6 重复、可选和特殊字符
正则表达式中最常见的就是匹配一些重复的字符串。
- (*)表示出现 0 到 n 次
- (+)表示出现 1 到 n 次
- (?)表示 0 次 或 1 次
- (\w)表示任意一个字母或数字
- (\d)表示任意一个数字
示例:
import re
# abc aabc abbbccc
s = 'a+b+c+'
strList = ['abc','aabc','bbabc','aabbbcccxyz']
for value in strList:
m = re.match(s, value)
if m is not None:
print(m.group())
else:
print('{}不匹配{}'.format(value,s))
print('--------------')
# 任意3个数字-任意3个小写字母
# 123-abc 433-xyz
#s = '\d\d\d-[a-z][a-z][a-z]'
s = '\d{3}-[a-z]{3}'
strList = ['123-abc','432-xyz','1234-xyz','1-xyzabc','543-xyz^%ab']
for value in strList:
m = re.match(s, value)
if m is not None:
print(m.group())
else:
print('{}不匹配{}'.format(value,s))
print('-------------')
s = '[a-z]?\d+'
strList = ['1234','a123','ab432','b234abc']
for value in strList:
m = re.match(s, value)
if m is not None:
print(m.group())
else:
print('{}不匹配{}'.format(value,s))
print('-------------')
# email
email = '\w+@(\w+\.)*\w+\.com'
emailList = ['abc@126.com','test@mail.geekori.com','test-abc@geekori.com','abc@geekori.com.cn']
for value in emailList:
m = re.match(email,value)
if m is not None:
print(m.group())
else:
print('{}不匹配{}'.format(value,email))
strValue = '我的email是lining@geekori.com,请发邮件到这个邮箱'
m = re.search(email, strValue)
print(m)
email = '[a-zA-Z0-9]+@(\w+\.)*\w+\.com'
m = re.search(email, strValue)
print(m)
abc
aabc
bbabc不匹配a+b+c+
aabbbccc
--------------
123-abc
432-xyz
1234-xyz不匹配\d{3}-[a-z]{3}
1-xyzabc不匹配\d{3}-[a-z]{3}
543-xyz
-------------
1234
a123
ab432不匹配[a-z]?\d+
b234
-------------
abc@126.com
test@mail.geekori.com
test-abc@geekori.com不匹配\w+@(\w+\.)*\w+\.com
abc@geekori.com
<_sre.SRE_Match object; span=(0, 26), match='我的email是lining@geekori.com'>
<_sre.SRE_Match object; span=(8, 26), match='lining@geekori.com'>
11.1.7 分组
如果一个模式字符串中有用一对圆括号括起来的部分,那么这部分就会作为一组,可以通过group方法的参数获取指定组匹配的字符串。
import re
m = re.match('(\d{3})-(\d{4})-([a-z]{2})', '123-4567-xy')
if m is not None:
print(m.group())
print(m.group(1))
print(m.group(2))
print(m.group(3))
# 得到一个元祖
print(m.groups())
print('-----------------')
m = re.match('(\d{3}-\d{4})-([a-z]{2})', '123-4567-xy')
if m is not None:
print(m.group())
print(m.group(1))
print(m.group(2))
# 获取每组的值,组成一个元祖
print(m.groups())
print('-----------------')
m = re.match('\d{3}-\d{4}-([a-z]{2})', '123-4567-xy')
if m is not None:
print(m.group())
print(m.group(1))
# 获取每组的值,组成一个元祖
print(m.groups())
print('-----------------')
m = re.match('\d{3}-\d{4}-[a-z]{2}', '123-4567-xy')
if m is not None:
print(m.group())
# 获取每组的值,组成一个元祖
print(m.groups())
123-4567-xy
123
4567
xy
('123', '4567', 'xy')
-----------------
123-4567-xy
123-4567
xy
('123-4567', 'xy')
-----------------
123-4567-xy
xy
('xy',)
-----------------
123-4567-xy
()
11.1.8 匹配字符串的起始和结尾以及单词边界
- (^)表示匹配字符串的开始
- ($)表示匹配字符串的结尾
- (\b)表示匹配单词的边界(两边是空格或标点符号)
示例:
import re
# 匹配
m = re.search('^The', 'The end.')
print(m)
if m is not None:
print(m.group())
# 匹配
m = re.search('^The', 'end. The')
print(m)
if m is not None:
print(m.group())
# 匹配
m = re.search('The$', 'end. The')
print(m)
if m is not None:
print(m.group())
# 不匹配
m = re.search('The$', 'The end.')
print(m)
if m is not None:
print(m.group())
# 匹配
m = re.search(r'\bthis', "What's this?")
print(m)
if m is not None:
print(m.group())
# 不匹配
m = re.search(r'\bthis', "What'sthis?")
print(m)
if m is not None:
print(m.group())
# 匹配
m = re.search(r'\bthis\b', "What's this?")
print(m)
if m is not None:
print(m.group())
# 不匹配
m = re.search(r'\bthis\b', "What's thisa")
print(m)
if m is not None:
print(m.group())
11.1.9 使用 findall 和 finditer 查找每一次出现的位置
findall 函数总是返回一个包含搜索结果的列表。如果findall函数没有找到匹配的部分,就会返回一个空列表。finditer 函数与 findall 函数类似,只是更节省内存。区别在于 findall 会一次性返回到列表,而 finditer 会返回一个迭代器,只有对返回结果进行迭代,才会执行匹配。findall和finditer函数相当于读取XML文档的两种技术:DOM和SAX。前者更灵活,但也更耗内存资源;后者顺序读取,不能随机读取,但更节省内存资源。
示例:
import re
s = '12-a-abc54-a-xyz---78-A-ytr'
result = re.findall(r'\d\d-a-[a-z]{3}',s)
print(result)
result = re.findall(r'(\d\d)-a-([a-z]{3})',s)
print(result)
result = re.findall(r'\d\d-a-[a-z]{3}',s,re.I)
print(result)
result = re.findall(r'(\d\d)-a-([a-z]{3})',s,re.I)
print(result)
it = re.finditer(r'(\d\d)-a-([a-z]{3})',s,re.I)
for result in it:
print(result.group(),end=' < ')
groups = result.groups()
for i in groups:
print(i,end = ' ')
print('>')
['12-a-abc', '54-a-xyz']
[('12', 'abc'), ('54', 'xyz')]
['12-a-abc', '54-a-xyz', '78-A-ytr']
[('12', 'abc'), ('54', 'xyz'), ('78', 'ytr')]
12-a-abc < 12 abc >
54-a-xyz < 54 xyz >
78-A-ytr < 78 ytr >
11.1.10 用 sub 和 subn 搜索与替换
sub函数与subn函数用于实现搜索和替换功能。这两个函数的功能几乎完全一样。都是将某个字符串中所有匹配正则表达式的部分替换成其他字符串。sub 函数返回替换后的结果,subn函数返回一个元组,元组的第1个元素是替换后的结果,第2个元素是替换的总数。
替换的字符串可以是普通字符串,也可以通过 “\N” 形式取出替换字符串中的分组信息,其中N是分组编号,从1开始。
import re
# 参数:匹配模式、要替换字符串、被替换的整个字符串
result = re.sub('Bill', 'Mike', 'Bill is my son')
print(result)
# 以元组形式输出
result = re.subn('Bill', 'Mike', 'Bill is my son,I like Bill')
print(result)
print(result[0])
print('替换总数','=',result[1])
# 取元组匹配模式,进行替换
result = re.sub('([0-9])([a-z]+)', r'产品编码(\1-\2)','01-1abc,02-2xyz,03-9hgf')
print(result)
def fun():
return r'产品编码(\1-\2)'
result = re.subn('([0-9])([a-z]+)', fun(),'01-1abc,02-2xyz,03-9hgf')
print(result)
print(result[0])
print('替换总数','=',result[1])
11.1.11 使用 split 分隔字符串
split 函数用于根据正则表达式分隔字符串。第1个参数是模式字符串,第2个参数待分隔的字符串。如果待分隔的字符串非常大,那么可以使用关键字maxsplit指定最大分隔次数。
import re
result = re.split(';','Bill;Mike;John')
print(result)
result = re.split('[,;.\s]+','a,b,,d,d;x c;d. e')
print(result)
result = re.split('[a-z]{3}-[0-9]{2}','testabc-4312productxyz-43abill')
print(result)
result = re.split('[a-z]{3}-[0-9]{2}','testabc-4312productxyz-43abill',maxsplit=1)
print(result)
['Bill', 'Mike', 'John']
['a', 'b', 'd', 'd', 'x', 'c', 'd', 'e']
['test', '12product', 'abill']
['test', '12productxyz-43abill']
11.2 一些常用的正则表达式
具体要求不同,相应的正则表达式也可能不相同。
import re
# Email
email = '[0-9a-zA-Z]+@[0-9a-zA-Z]+\.[a-zA-Z]{2,3}'
result = re.findall(email, 'lining@geekori.com')
print(result)
result = re.findall(email, 'abcdefg@aa')
print(result)
result = re.findall(email, '我的email是lining@geekori.com,不是bill@geekori.cn,请确认输入的Email是否正确')
print(result)
# 匹配IPv4的正则表达式
ipv4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
result = re.findall(ipv4, '这是我的IP地址:33.12.54.34,你的IP地址是100.32.53.13吗')
print(result)
# 匹配url的正则表达式
url = 'https?:/{2}\w.+'
url1 = 'https://geekori.com'
url2 = 'ftp://geekori.com'
print(re.match(url,url1))
print(re.match(url,url2))