正则表达式
编写爬虫的第一步就是抓取资源,抓取web资源后,通常需要对抓取的Web资源进行分析,这就是编写爬虫的第二步。这里的Web资源主要指的是HTML代码,python语言内置的正则表达式可以对任意字符串进行搜索、分组等复杂操作。
一。什么是正则表达式:python语言通过标准库的re模块支持正则表达式
二.用match方法匹配字符串:
match()函数有两个参数,第一个参数是表示文本模式(正则表达式或普通文本),第二个参数是待匹配的字符串
匹配成功则返回一个SER_Match对象,然后调用该对象中的group方法获取匹配成功的字符串;匹配失败不报错
match()函数从字符串起始位置开始匹配
import re # 导入re模块
# match()函数有两个参数,第一个参数是表示文本模式(正则表达式或普通文本),第二个参数是待匹配的字符串
# 匹配成功则返回一个SER_Match对象,然后调用该对象中的group方法获取匹配成功的字符串;匹配失败不报错
# match()函数从字符串起始位置开始匹配
m = re.match('hello', 'hello') # 进行文本模式匹配,匹配成功
if m is not None:
print(m.group()) # 运行结果:hello
print(m.__class__.__name__) # 输出m的类名,运行结果:SRE_Match
print(type(m)) # .__class__.__name__直接输出类对象名称
m = re.match('hello', 'world') # 进行文本模式匹配,匹配失败,m为None
if m is not None:
print(m.group())
print(m) # 运行结果:None
m = re.match('he', 'hello world') # 只要模式从字符串起始位置开始,也可以匹配成功
if m is not None:
print(m.group()) # 运行结果:hello
# 运行结果:<_sre.SRE_Match object; span=(0, 5), match='hello'>
print(m)
二.用search方法搜索满足条件的字符串
进行文本模式匹配,匹配失败,match方法返回None
搜索从一段文本中找到一个或多个与文本模式相匹配的字符串,这就是search,用法与match匹配相似
import re
# 进行文本模式匹配,匹配失败,match方法返回None
# 搜索从一段文本中找到一个或多个与文本模式相匹配的字符串,这就是search,用法与match匹配相似
m = re.match('python','I love python.')
if m is not None:
print(m.group())
# 运行结果:None
print(m)
# 进行文本模式搜索,搜索成功
m = re.search('python','I love python.')
if m is not None:
# 运行结果:python
print(m.group())
# 运行结果:<_sre.SRE_Match object; span=(7, 13), match='python'>
print(m)
print(m.span) # <built-in method span of _sre.SRE_Match object at 0x0000019E3A22CC60>
三.匹配多个字符串
择一匹配符(|)
import re
# 匹配符号的文本
s = 'Bill|Mike|John' # 指定使用择一匹配符号的文本模式字符串
m = re.match(s, 'Bill') # 匹配成功
if m is not None:
print(m.group()) # 运行结果:Bill
m = re.match(s, "Bill:my friend") # 匹配成功
if m is not None:
print(m.group()) # 运行结果:Bill
m = re.search(s,'Where is Mike?') # 搜索成功
if m is not None:
print(m.group()) # 运行结果:Mike
# 运行结果:<_sre.SRE_Match object; span=(9, 13), match='Mike'>
print(m)
四.匹配任何单个字符串
匹配一类字符串的特殊符号(.)
import re
# 匹配任意单个字符串.
s = '.ind' # 使用了点(.)符号的文本模式字符串
m = re.match(s, 'bind') # 匹配成功
if m is not None:
print(m.group()) # 运行结果:bind
m = re.match(s,'binding')
# 运行结果:<<_sre.SRE_Match object; span=(0, 4), match='bind'>
print("<" + str(m))
m = re.match(s,'bin') # 匹配失败
print(m) # 运行结果:None
m = re.search(s,'<bind>') # 搜索成功
print(m.group()) # 运行结果:bind
# 运行结果:<_sre.SRE_Match object; span=(1, 5), match='bind'>
print(m)
s1 = '3.14' # 使用了点(.)符号的文本模式字符串
s2 = '3\.14' # 使用了转义符将点(.)变成真正的点字符
m = re.match(s1, '3.14') # 匹配成功,因为点字符同样也是一个字符
# 运行结果:<_sre.SRE_Match object; span=(0, 4), match='3.14'>
print(m)
m = re.match(s1, '3314') # 匹配成功,3和14之间可以是任意字符
# 运行结果:<_sre.SRE_Match object; span=(0, 4), match='3314'>
print(m)
m = re.match(s2, '3.14') # 匹配成功
# 运行结果:<_sre.SRE_Match object; span=(0, 4), match='3.14'>
print(m)
m = re.match(s2, '3314') # 匹配失败,因为中间的3并不是点(.)字符
print(m) # 运行结果:None
五。使用字符集
字符集也就是一对中括号[],[abc]相当于a,b,c三个字符可以取其中任何一个,类似于a|b|c
所以对单个字符串使用关系时,字符集和择一匹配符的效果是一样的
import re
# 使用字符集[],匹配成功
m = re.match('[ab][cd][ef][gh]', 'adfh') # 多个字符集连接在一起相当于连接的功能,就是匹配的结果有2*4=16种
# 运行结果:adfh
print(m.group())
# 使用字符集,匹配成功
m = re.match('[ab][cd][ef][gh]', 'bceg')
# 运行结果:bceg
print(m.group())
# 使用字符集,匹配不成功,因为a和b是或的关系
m = re.match('[ab][cd][ef][gh]', 'abceh')
# 运行结果:None
print(m)
# 字符集和普通文本模式字符串混合使用,匹配成功,ab相当于前缀
m = re.match('ab[cd][ef][gh]', 'abceh') # 匹配
# 运行结果:abceh
print(m.group())
# 运行结果:<_sre.SRE_Match object; span=(0, 5), match='abceh'>
print(m)
# 使用择一匹配符,匹配成功,abcd和efgh是或的关系,只要满足一个即可
m = re.match('abcd|efgh', 'efgh') # 匹配
# 运行结果:efgh
print(m.group())
# 运行结果:<_sre.SRE_Match object; span=(0, 4), match='efgh'>
print(m)
六.重复、可选和特殊字符
匹配重复的字符串:要匹配连续出现的3个a或匹配至少出现一个0的字符串;就需要使用两个符号:"*"
(*
在md中不显示)和"+"。
其中"*"
表示字符串出现0到n次,"+"表示字符串出现1到n次;还有一个"?"表示字符串出现0到1次
# 匹配'a'、'b'、'c'三个字母按顺序从左到右排列,而且这3个字母都必须至少有1个。
# abc aabc abbbccc都可以匹配成功
# /w 匹配的是字母或数字,/W 匹配的不是字母或数字;/d匹配的是数字
# + 代表前一个字符出现的1-n次, * 表示前一个字符出现的 0-n次(注意:a*也可以匹配baa,缘由是a*可以匹配空串,任何字符串都可以认为是以空串作为前缀的)
# (abc)+匹配一组字符串重复
# -匹配任意几个字母或数字
# [a-z]表示的是26个小写子母中的一个;[a-z]{3}小写字母中的3个
# [a-zA-Z0-9]1个字母(大写或小写)或1个数字
# “\w”对中文也匹配
本例通过在模式中使用"*"和"+"和"?"符号以及特殊字符""和"",演示他们的不同用法
import re
s = 'a+b+c+'
strList = ['abc','aabc','bbabc','aabbbcccxyz']
# 只有'bbabc'无法匹配成功,因为开头没有'a'
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都可以成功
# 下面采用了两种设置模式字符串的方式
# [a-z]是设置字母之间或关系的简化形式,表示a到z的26个字母可以选择任意一个,相当于“a|b|c|…|z”
#s = '\d\d\d-[a-z][a-z][a-z]'
# {3}表示让前面修饰的特殊字符“\d”重复3次,相当于“\d\d\d”
s = '\d{3}-[a-z]{3}'
strList = ['123-abc','432-xyz','1234-xyz','1-xyzabc','543-xyz^%awb']
# '1234-xyz'和'1-xyzabc'匹配失败
for value in strList:
m = re.match(s, value)
if m is not None:
print(m.group())
else:
print('{}不匹配{}'.format(value,s))
print('-------------')
# 匹配以a到z的26个字母中的任意一个作为前缀(也可以没有这个前缀),后面是至少1个数字
s = '[a-z]?\d+'
strList = ['1234','a123','ab432','b234abc']
# 'ab432'匹配失败,因为前缀是两个字母
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']
# 'test-abc@geekori.com'匹配失败,因为“test”和“abc”之间有连字符(-)
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,请发邮件到这个邮箱'
# 搜索文本中的email,由于“\w”对中文也匹配,所以下面对email模式字符串进行改进
m = re.search(email, strValue)
print(m)
# 规定“@”前面的部分必须是至少1个字母(大写或小写)和数字,不能是其他字符
email = '[a-zA-Z0-9]+@(\w+\.)*\w+\.com'
m = re.search(email, strValue)
print(m)
七.分组
如果一个模式字符串中有用一对圆括号括起来的部分,那么这部分就会作为一组,可以通过group方法的参数获取指定的组匹配的字符串
如果模式字符串中并没有用圆括号括起来的部分,那么就不会对待匹配的字符串进行分组
分成3组:(\d{3})、(\d{4})和([a-z]{2})
import re
m = re.match('(\d{3})-(\d{4})-([a-z]{2})', '123-4567-xy')
if m is not None:
print(m.group()) # 运行结果:123-4567-xy
print(m.group(1)) # 获取第1组的值,运行结果:123
print(m.group(2)) # 获取第2组的值,运行结果:4567
print(m.group(3)) # 获取第3组的值,运行结果:xy
print(m.groups()) # 获取每组的值组成的元组,运行结果:('123', '4567', 'xy')
print('-----------------')
# 分成2组:(\d{3}-\d{4})和([a-z]{2})
m = re.match('(\d{3}-\d{4})-([a-z]{2})', '123-4567-xy')
if m is not None:
print(m.group()) # 运行结果:123-4567-xy
print(m.group(1)) # 获取第1组的值,运行结果:123-4567
print(m.group(2)) # 获取第2组的值,运行结果:xy
print(m.groups()) # 获取每组的值组成的元组,运行结果:('123-4567', 'xy')
print('-----------------')
# 分了1组:([a-z]{2})
m = re.match('\d{3}-\d{4}-([a-z]{2})', '123-4567-xy')
if m is not None:
print(m.group()) # 运行结果:123-4567-xy
print(m.group(1)) # 获取第1组的值,运行结果:xy
print(m.groups()) # 获取每组的值组成的元组,运行结果:('xy',)
print('-----------------')
# 未分组,因为模式字符串中没有圆括号括起来的部分
m = re.match('\d{3}-\d{4}-[a-z]{2}', '123-4567-xy')
if m is not None:
print(m.group()) # 运行结果:123-4567-xy
print(m.groups()) # 获取每组的值组成的元组,运行结果:()
八。匹配字符串的起始和结尾以及单词边界
^用于匹配字符串的开始,$用于匹配字符串的结束; \b用于表示单词的边界(边界是指单词两侧是空格或标点符号)
import re
# 匹配字符串的开始与结尾
# ^用于匹配字符串的开始,$用于匹配字符串的结束
# \b用于表示单词的边界(边界是指单词两侧是空格或标点符号)
# 匹配成功
m = re.search('^The', 'The end.')
print(m)
if m is not None:
print(m.group()) # 运行结果:The
# The在匹配字符串的最后,不匹配
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()) # 运行结果:The
m = re.search('The$', 'The end.')
print(m)
if m is not None:
print(m.group())
# this的左侧必须有边界,成功匹配,this左侧是空格
m = re.search(r'\bthis', "What's this?")
print(m)
if m is not None:
print(m.group()) # 运行结果:this
# 不匹配,因为this左侧是“s”,没有边界
# 字符串前面的r表示该字符串中的特殊字符(如“\b”)不进行转义
m = re.search(r'\bthis', "What'sthis?")
print(m)
if m is not None:
print(m.group())
# this的左右要求都有边界,成功匹配,因为this左侧是空格,右侧是问号(?)
m = re.search(r'\bthis\b', "What's this?")
print(m)
if m is not None:
print(m.group()) # 运行结果:this
# 不匹配,因为this右侧是a,a也是单词,不是边界
m = re.search(r'\bthis\b', "What's thisa")
print(m)
if m is not None:
print(m.group())
九.findall和finditer查找每一次出现的位置
findall函数用于查询字符串中某个正则表单时模式全部的非重复出现情况(也就是说,匹配到重复的字符串也会按照顺序返回)
返回一个包含搜索结果的列表;
如果没有找到匹配的部分,就会返回一个空列表;如果匹配成功,列表将包含所有成功的匹配部分(从左向右按匹配顺序排列)
'''
finditer函数在功能上与findall函数类似,只是更节省内存。
finditer函数返回的是一个迭代器,只有对finditer函数返回结果进行迭代
才会对字符串中某个正则表达式模式进行匹配
findall相当于采用的是读取XML文档的DOM技术,更加灵活,但更耗内存
finditer相当于采用的是读取XML文档的SAX技术,按顺序读取XML文档的内容,不能随机读取XML文档的内容,但更节省内存(书上的描述)
'''
import re
# re.I将匹配模式设置为大小写不敏感
# 待匹配的字符串
s = '12-a-abc54-a-xyz---78-A-ytr'
# 匹配以2个数字开头,结尾是3个小写字母,中间用“-a”分隔的字符串,对大小写敏感
# 下面的代码都使用了同样的模式字符串
result = re.findall(r'\d\d-a-[a-z]{3}',s)
# 运行结果:['12-a-abc', '54-a-xyz']
print(result)
# 将模式字符串加了两个分组(用圆括号括起来的部分),findall方法也会以分组形式返回
result = re.findall(r'(\d\d)-a-([a-z]{3})',s)
# 运行结果:[('12', 'abc'), ('54', 'xyz')]
print(result)
# 忽略大小写(最后一个参数值:re.I)
result = re.findall(r'\d\d-a-[a-z]{3}',s,re.I)
# 运行结果:['12-a-abc', '54-a-xyz', '78-A-ytr']
print(result)
# 忽略大小写,并且为模式字符串加了2个分组
result = re.findall(r'(\d\d)-a-([a-z]{3})',s,re.I)
# 运行结果:[('12', 'abc'), ('54', 'xyz'), ('78', 'ytr')]
print(result)
# 使用finditer函数匹配模式字符串,并返回匹配迭代器
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('>')
十.用sub和subn搜索与替换
sub函数与subn函数用于实现搜索和替换功能
将某个字符串中所有匹配正则表达式的部分替换成其他字符串;用来替换的部分可能是有一个字符串,也可以是一个函数
sub函数返回替换的结果;
subn函数返回一个元组,元组的第一个元素是替换的结果,第二个元素是替换的总数
sub函数第1个参数是模式字符串,第2个参数是要替换的字符串,第3个参数是被替换的字符串
import re
# sub函数第1个参数是模式字符串,第2个参数是要替换的字符串,第3个参数是被替换的字符串
# 匹配'Bill is my son'中的'Bill',并用'Mike'替换'Bill'
result = re.sub('Bill', 'Mike', 'Bill is my son')
# 运行结果:Mike is my son
print(result)
# 返回替换结果和替换总数
result = re.subn('Bill', 'Mike', 'Bill is my son,I like Bill')
# 运行结果:('Mike is my son,I like Mike', 2)
print(result)
# 运行结果:Mike is my son,I like Mike
print(result[0])
# 运行结果:替换总数 = 2
print('替换总数','=',result[1])
# 使用“\N“形式引用匹配字符串中的分组(取出替换字符串中的分组信息)
result = re.sub('([0-9])([a-z]+)', r'产品编码(\1-\2)','01-1abc,02-2xyz,03-9hgf')
# 运行结果:01-产品编码(1-abc),02-产品编码(2-xyz),03-产品编码(9-hgf)
print(result)
# 该函数返回要替换的字符串
def fun():
return r'产品编码(\1-\2)'
result = re.subn('([0-9])([a-z]+)', fun(),'01-1abc,02-2xyz,03-9hgf')
# 运行结果:('01-产品编码(1-abc),02-产品编码(2-xyz),03-产品编码(9-hgf)', 3)
print(result)
# 运行结果:01-产品编码(1-abc),02-产品编码(2-xyz),03-产品编码(9-hgf)
print(result[0])
# 运行结果:替换总数 = 3
print('替换总数','=',result[1])
十一.使用split分割字符串
split()函数3个参数,模式字符串,待匹配的字符串,maxsplit最大分割次数(不指定则全部分割按照模式字符串)
返回一个列表,列表中的子元素都是被分割的字符串
import re
result = re.split(';','Bill;Mike;John')
# 运行结果:['Bill', 'Mike', 'John']
print(result)
# 用至少1个逗号(,),分号(;),点(.)和空白符(\s)分隔字符串
result = re.split('[,;.\s]+','a,b,,d,d;x c;d. e') # []括号内表示或的意义|
# 运行结果:['a', 'b', 'd', 'd', 'x', 'c', 'd', 'e']
print(result)
# 用以3个小写字母开头,紧接着一个连字符(-),并以2个数字结尾的字符串作为分隔符对字符串进行分隔
result = re.split('[a-z]{3}-[0-9]{2}','testabc-4312productxyz-43abill')
# 运行结果:['test', '12product', 'abill']
print(result)
# 使用maxsplit参数限定分隔的次数,这里限定为1,也就是只分隔一次
result = re.split('[a-z]{3}-[0-9]{2}','testabc-4312productxyz-43abill',maxsplit=1)
# 运行结果:['test', '12productxyz-43abill']
print(result)
十二.常用的一些正则表达式
1.Email:'[0-9a-zA-Z]+@[0-9a-zA-Z]+.[a-zA-Z]{2,3}'
2.IP地址(IPV4):'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}'
3.Web地址:'https?😕{2}\w.+'
import re
# 匹配Email的正在表达式
email = '[0-9a-zA-Z]+@[0-9a-zA-Z]+\.[a-zA-Z]{2,3}'
result = re.findall(email, 'lining@geekori.com')
# 运行结果:['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是否正确')
# 运行结果:['lining@geekori.com', 'bill@geekori.cn']
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吗')
# 运行结果:['33.12.54.34', '100.32.53.13']
print(result)
# 匹配Url的正则表达式
url = 'https?:/{2}\w.+'
url1 = 'https://geekori.com'
url2 = 'ftp://geekori.com'
# 运行结果:<_sre.SRE_Match object; span=(0, 19), match='https://geekori.com'>
print(re.match(url,url1))
# 运行结果:None
print(re.match(url,url2))