概述:
正则表达式,又称规则表达式 , (Regular Expression,在代码中常简写为regex、regexp或RE),是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符"),是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个语法规则的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。
本质:
正则表达式本质就是一个字符串,是一个用于对字符串进行处理的字符串。
小结:
- 正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。
- 正则表达式使用单个字符串来描述、匹配一系列匹配某个语法规则的字符串。
- 正则表达式是繁琐的,但它是强大的,能够提高效率。
- 许多程序设计语言都支持利用正则表达式进行字符串操作。
使用场景:
- 表单验证(例如 : 手机号、邮箱、身份证.... )
- 爬虫
正则对python的支持:
普通字符:
字母、数字、汉字、下划线、以及没有特殊定义的字符。
正则表达式中的普通字符,在匹配的时候,只匹配与自身相同的一个字符。
eg:表达式c,在匹配字符串abcdefg时,匹配结果为成功;匹配到的内容是c,匹配到的位置开始于2,结束于3。(注:下标从0开始还是从1开始,取决于当前使用的编程语言。)
match()函数:
语法:
match(pattern, string, flags=0)
参数解释:
pattern: 正则表达式(定义一套验证规则)
string: 需要被验证的字符串数据
flags: 标志位 用于控制正则表达式的匹配方法 比如说大小写 多行匹配等等,默认情况下不定义(不开启任何模式)
特点:
match:从头开始尝试匹配字符串数据(如果开头就不匹配则直接返回None)
如果匹配成功,则返回一个 match 对象;
如果匹配失败,则返回 None 值;
match对象常用的5个方法/函数:
group():返回匹配成功的数据(原串中的某子串数据)
start():返回匹配成功的数据的起始索引
end():返回匹配成功的数据的结束索引
span():返回一个元祖对象,有两个元素组成。
第一个元素记录了匹配成功的起始索引;
第二个元素记录了匹配成功的结束索引;
groups():返回所有子组的信息,以元祖的形式返回;如果没有分组,返回空元祖对象
注意事项:
1.正则表达式返回的索引值需要满足含头不含尾的特点;
2.正则表达式验证的数据内容严格区分大小写;
3.我们之后在定义正则规则的时候,在引号前面+一个r,无脑操作;
代码实例:
pattern = 'java' # 正则表达式的模版
s = 'java and python' # 要匹配的数据
# s = 'python and java' # 如果是这个字符串,则匹配不到(开头没找到)
result = re.match(pattern, s)
if result:
print(pattern)
else:
print(result, '没有匹配到')
元字符:
正则表达中使用了很多元字符,用来表示一些特殊的含义或功能。
一些无法书写或者具有特殊功能的字符,采用在前面加斜杠""进行转义的方法。 例如下表所示:
代码举例:
import re
""" 小数点,可以匹配除了换行符\n以外的任意一个字符 """
print(re.match('a.c', 'abc').group())
"""
从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功,则返回 None
"""
""" 逻辑或|"""
print(re.match('a|c', 'c').group()) # c
print(re.match('a|c', 'a').group()) # a
print(re.match('a|c', 'ac').group()) # a
print(re.match('a|c', 'ca').group()) # c
# print(re.match('a|c', 'ba').group()) # 报错 AttributeError: 'NoneType' object has no attribute 'group'
""" 转义符\,将特殊字符变成普通字符 """
print(re.match('消失的她.', '消失的她.').group()) # OK
print(re.match('消失的她.', '消失的她们').group()) # 小数点可以匹配除了换行符的其他任何,因此也OK
print(re.match('消失的她.', '消失的她.').group()) # 加了转义符,变成普通字符.,也OK
# print(re.match('消失的她.', '消失的她们').group()) # 普通字符.不能匹配们,因此报错
"""- 定义[]中的一个字符区间"""
print(re.match('[a-z]', 'ab').group()) # a
print(re.match('[0-9]', '123456').group()) # 1
""" ^,对字符集求反(尖号必须在括号的最前面)"""
print(re.match('[^a-z]', '245')) # 返回匹配的位置,和匹配到的数据 <re.Match object; span=(0, 1), match='2'>
print(re.match('[^a-z]', '1')) # <re.Match object; span=(0, 1), match='1'>
""" ()对表达式进行分组,将圆括号内的内容当作一个整体 """
print(re.match('(abc)', 'abc').group()) # abc
print(re.match('(ab.)', 'abc').group()) # abc,小数点可以匹配除了换行符的其他任何,因此也OK
print(re.match('(abc)', 'ab').group()) # 报错,AttributeError: 'NoneType' object has no attribute 'group'
预定义匹配字符集:
正则表达式中的一些表示方法,可以同时匹配某个预定义字符集中的任意一个字符。比如,表达式\d可以匹配任意一个数字。虽然可以匹配其中任意字符,但是只能是一个,不是多个。
代码举例:
import re
"""\d,0-9中的任意一个数字"""
print(re.match('a\db', 'a3b').group()) # a3b
# print(re.match('a\db', 'abb').group()) # 报错
"""\w,任意一个字母或数字或下划线,eg: A-Z、a-z、0-9、_,中的任意一个"""
print(re.match('a\wfg', 'a_fg').group()) # a_fg
print(re.match('a\wfg', 'a_fg')) # a_fg # <re.Match object; span=(0, 4), match='a_fg'>
"""\s,空格、制表符、换页符等空白字符的其中任意一个"""
print(re.match('a\svf', 'a vf').group()) # a vf
print(re.match('a\svf', 'a\tvf').group()) # a vf
print(re.match('a\svf', 'a\nvf').group())
重复匹配:
前面的表达式,无论是只能匹配一种字符的表达式,还是可以匹配多种字符其中任意一个的表达式,都只能匹配一次。但是有时候我们需要对某个字段进行重复匹配,例如手机号码13666666666,一般的新手可能会写成\d\d\d\d\d\d\d\d\d\d\d(注意,这不是一个恰当的表达式),不但写着费劲,看着也累,还不⼀定准确恰当。 这种情况可以使用表达式再加上修饰匹配次数的特殊符号{},不但重复书写表达式就可以重复匹配。例如abcd可以写成[abcd]{2}。
代码举例:
import re
"""{n},表达式重复n次,eg: \d{2}相当于\d\d,a{3}相当于aaa"""
print(re.match('\d', '123').group()) # 1
print(re.match('\d\d\d', '123').group()) # 123
print(re.match('\d{3}', '123').group()) # 123
"""{m,n},表达式至少重复m次,最多重复n次,eg: ab{1,3},匹配ab或abb或abbb"""
print(re.match('\d{3,4}-\d{5,6}', '0571-100861').group()) # 0571-100861
print(re.match('\d{3,4}-\d{5,6}', '0571-10086').group()) # 0571-10086
# print(re.match('\d{3,4}-\d{5,6}', '0571-187').group()) # 报错 AttributeError: 'NoneType' object has no attribute 'group'
"""{m,},表达式至少重复m次,eg: \w\d{2,}可匹配a12、_1111、M123等等"""
print(re.match('\d{3,}', '1213456789').group())
"""*,表达式出现0次到任意次,相当于{0,},eg: ^*b可以匹配b、^^^b等等
正则表达式是作为一个整体去进行匹配的
"""
print(re.match('w[a-z]*', 'waaa').group()) # waaa
print(re.match('w[a-z]*', 'waaafghjkllkkm').group()) # waaafghjkllkkm
print(re.match('w[a-z]*', 'w').group()) # w
"""+,表达式至少出现一次,相当于{1,},eg: a+b可以匹配ab、aab、aaab"""
# print(re.match('w[a-z]+', 'w').group()) # 报错
print(re.match('w[a-z]+', 'www').group()) # www
print(re.match('w[a-z]+', 'wwwbaiducom').group()) # wwwbaiducom
位置匹配和非贪婪匹配:
位置匹配:
有时候,我们对匹配出现的位置有要求,比如开头、结尾、单词之间等等。
贪婪与非贪婪模式:
贪婪模式:在重复匹配时,正则表达式默认总是尽可能多的匹配。
例如,针对文本dxxxdxxxd,表达式(d)(\w+)(d)中的\w+将匹配第一个d和最后一个d之间的所有字符xxxdxxx。可见,\w+在匹配的时候,总是尽可能多的匹配符合它规则的字符。同理,带有?、*和{m,n}的重复匹配表达式都是尽可能地多匹配。
校验数字的相关表达式:
特殊场景的表达式:
代码举例:
import re
"""匹配开头 ^"""
print(re.match('^a\d{3,}', 'a1232434').group()) # a1232434
"""匹配结束 $"""
print(re.match('^a\d{3,}b$', 'a123b').group()) # a123b
"""需求:拿到 div>abc</div>"""
"""贪婪模式"""
s = '<div>abc</div><div>bcd</div>'
ptn = '<div>.*</div>' # .匹配任意一个数据 * 重复任意次
print(re.match(ptn, s).group()) # <div>abc</div><div>bcd</div>
"""非贪婪模式"""
ptn = '<div>.*?</div>' # .匹配任意一个数据 * 重复任意次 ? 0次到1次
print(re.match(ptn, s).group()) # <div>abc</div>
re模块及常用方法:
内置库, 不用通过第三方模块安装,导入即可。
compile(pattern,flags=0):
这个方法是re模块的工厂法,⽤于将字符串形式的正则表达式编译为 Pattern 模式对象,可以实现更加效率的匹配。
第二个参数flag是匹配模式 使用compile()完成一次转换后,再次使用该匹配模式的时候就不能进行转换了。
经过 compile()转换的正则表达式对象也能使用普通的re方法。
flag匹配模式:
search(pattern,string,flags=0):
在文本内查找,返回第一个匹配到的字符串。
它的返回值类型和使用方法与 match()是一样的,唯一的区别是查找的位置不用固定在文本的开头。
findall(pattern,string,flags=0):
作为 re 模块的三大搜索函数之一,findall()和match()、search()的不同之处在于,前两者都是单值匹配,找到一个就忽略后面,直接返回就不再查找。而 findall 是全文查找,返回值是一个匹配到的字符串的列表。这个列表没有 group() 方法,没有 start、end、span,更不是一个匹配对象,仅仅是个列表。如果一项都没有匹配到,则返回一个空列表。
split(pattern,string,maxsplit=0,flags=0):
re模块的 split() 方法和字符串的 split() 方法很相似,都是利用特定的字符去分割字符串。但是 re 模块的 split() 可以使用正则表达式,因此更灵活,更强大。参数 maxsplit,用于指定分割的次数。
sub(pattern,repl,string,count=0,flag=0):
sub() 方法类似于字符串的 replace() 方法,用指定的内容替换匹配到的字符,可以指定替换次数。
代码举例:
import re
# compile,将字符串形式的正则表达式编译为 Pattern 模式对象,可以实现更加效率的匹配
pat = re.compile('abc') # re对象,将规则抽取出来了。
print(pat.match('abc').group())
print(re.match('abc', 'abc').group())
# print(re.match('abc', 'ab234253465c').group()) # 报错 AttributeError: 'NoneType' object has no attribute 'group'
# split()
s = '8*5+9-3/2'
print(re.findall('\d', s))
print(re.split('[*+-/]', s))
# sub,用指定的内容替换匹配到的字符,可以指定替换次数。
s1 = '上海,今天蓝色暴雨预警么?'
print(re.sub('[/:*?"<>]', '', s1)) # 上海,今天蓝色暴雨预警么
# flags模式
print(re.findall('a', 'aAA', re.I)) # re.I 不区分大小写
分组功能:
re模块有一个分组功能,相当于二次过滤,将已经匹配到的内容再次筛选到需要的内容。通过 () 实现,而想要获取分组的内容,靠的则是 group() 和 groups()。
正则实战案例:
案例1、获取某地区的近7日天气情况:
import requests
import re
import csv
"""
需求: 获取长沙7天的天气情况(日期,天气,温度,风力)并保存在csv文件中
解题分析:
1、获取网页源代码
2、解析网页源代码并获取目标数据
3、从目标数据中一层一层,直至拿到精准数据
4、对数据进行过滤,并存入csv文件
5、将数据保存至csv文件
网站:http://www.weather.com.cn/weather/101250101.shtml
"""
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
"114.0.0.0 Safari/537.36",
}
url = 'http://www.weather.com.cn/weather/101250101.shtml'
response = requests.get(url, headers=headers)
response.encoding = 'utf-8'
html = response.text
# print(html) # 获取到网页源代码
"""使用正则,对数据进行解析"""
# re.S,使.这个通配符能够匹配到包括换行在内的所有字符
ulResult = re.match('.*?(<ul class="t clearfix".*?</ul>).*?', html, re.S)
# print(ulResult) # 返回一个macth对象
# group(num),num为正整数,获取正则表达式匹配结果中相应的第n个括号内的元组
ulData = ulResult.group(1)
# print(ulData) # 拿到ul中的所有数据
# 从ul中取出所有li标签中的数据
liData = re.findall('<li.*?>.*?</li>', ulData, re.S)
# print(liData) # 拿到li标签中的所有数据,返回的是列表
for li in liData:
print(li)
# 获取目标数据的正则表达式
# pattern = re.compile('<li.*?<h1>(.*?)</h1>.*?<p.*?>(.*?)</p>.*?<i>(.*?)</i>.*?<i>(.*?)</i>', re.S)
pattern = re.compile('<li.*?<h1>(.*?)</h1>.*?<p.*?>(.*?)</p>.*?<i>(.*?)</i>.*?<i>(.*?)</i>', re.S)
# print(pattern) # re.compile('<li.*?<h1>(.*?)</h1>.*?<p.*?>(.*?)</p>.*?<i>(.*?)</i>.*?<i>(.*?)</i>', re.DOTALL)
allLiData = []
for i in liData: # 通过循环,获取li标签中的每条数据
res = pattern.match(i) # 交给match去解析
# print(res.groups()) # 获取到所有数据,返回元组类型的数据
# print(res.group(1))
# print(res.group(2))
# print(res.group(3))
# print(res.group(4))
itemData = [res.group(1), res.group(2), res.group(3), res.group(4)]
allLiData.append(itemData)
# print(allLiData)
# 将数据保存至csv文件
with open('weather.csv', mode='w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
writer.writerow(['日期', '天气', '温度', '风力'])
writer.writerows(allLiData)
案例2、表情包的抓取:
import re
import requests
from lxml import etree
from urllib.request import urlretrieve # 保存图片的
# import os
# os.mkdir('images') # 可代替手动创建文件夹
"""
将 http://www.godoutu.com/face/hot/page/1.html 下10页数据的表情包全部抓取
print(45*1801) 8w多条数据
图片数据 二进制
保存
with open wb模式
urllib
找到图片的路径 图片名字
题目分析:
一、观察 url 变化的规律:
1:http://www.godoutu.com/face/hot/page/1.html
2:http://www.godoutu.com/face/hot/page/2.html
3:http://www.godoutu.com/face/hot/page/3.html
二、对获取的网页源代码进行数据解析,拿到存放数据的大盒子
三、对大盒子中的数据进行一层层的过滤,获取精准数据
四、保存数据至csv文件中。
其中,表情包中存在静态jpg、png,和动态图 gif。
爬取的图片、音频、视频都是二进制格式,如果需要保存的话,我们需要以wb的模式写入。
保存图片的方式:
urllib
with open
第一步:F12,刷新接口,没有动态数据
"""
# div class='ui segment imghover'
for i in range(1, 11):
url = f'http://www.godoutu.com/face/hot/page/{i}.html'
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
"114.0.0.0 Safari/537.36",
}
response = requests.get(url, headers=headers)
response.encoding = 'utf-8'
html = response.text
# print(response) # 获取网页源代码
# xpath对象
element = etree.HTML(html)
bigDiv = element.xpath('//div[@class="ui segment imghover"]/div[@class="tagbqppdiv"]')
# print(bigDiv)
for item in bigDiv:
everyHref = item.xpath("./a/img/@data-original")[0] # [0]:获取第一条数据,并返回字符串
# print(result) # http://img.godoutu.com/large/006APoFYjw1fbn44pgm23j305i05wglt.jpg
# title = item.xpath('./a/@title') # 不加[0],返回的是列表 ['饺子动图表情包 - 萌娃饺子动态表情包表情']
title = item.xpath('./a/@title')[0] # 加上[0],返回的是字符串 饺子动图表情包 - 萌娃饺子动态表情包表情
# print(title)
# sub() 去掉非法字符
newtitle = re.sub('[/:*?<>|]', '', title)
# print(newtitle)
# 判断图片是以什么结尾的
if str(everyHref).endswith('jpg'):
urlretrieve(everyHref, f'images/{newtitle}.jpg')
print(f'{newtitle}.jog下载成功')
else:
urlretrieve(everyHref, f'images/{newtitle}.gif')
print(f'{newtitle}.gif下载成功')