# ****正则表达式***** # 没有中括号只有多个转义符可以看做是这些转义符组成了一个字符串,转义符之间的关系是 且 的关系 # 包含中括号时,中括号内的转义符之间的关系是 或 的关系,多个中括号之间的关系是 且 的关系 # 使用 r'字符串' 后,正则表达式是不需要加转义符的如 @ . < > 以及字母和数字,加上转义符 \ 也可以 # \d可以匹配一个数字 \w可以匹配一个字母或者数字或下划线 # . 可以匹配任意字符 ,* 表示任意个字符 , + 表示至少一个字符 ,? 表示0个或者1个字符, # {n}表示n个字符 {n,m}表示n-m个字符 # \d{3}\s+\d{3,8} \d{3}表示匹配3个数字 \s表示匹配一个空格(包括Tab等空白符),所以\s+ 表示至少有一个空格 \d{3.8}表示3-8个数字 # 综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。 # 如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中,要用'\'转义,所以,上面的正则是\d{3}\-\d{3,8} # 要做更精确的匹配,可以使用[]表示范围 # [0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线,中括号里面是或的关系 # [0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100','0_Z','Py3000'等等 # [a-zA-Z\_][0-9a-zA-Z\_]* 可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量,两个中括号表示的是且的关系 # [a-zA-Z\_][0-9a-zA-Z\_]{0, 19} 更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符) # A|B可以匹配 A 或者 B,所以(P|p)ython可以匹配'Python'或'python' # ^ 表示行的开头 ,^\d 表示必须以数字开头 # $ 表示行的结尾, \d$ 表示必须以数字结束 # py 也可以匹配python,但是加上了 ^py$ 就变成了整行匹配,就只能匹配 'py'了 # 注意当字符串中包含转移符"\"时,如s = 'ABC\\-001'对应的正则表达式的字符串变为 'ABC\-001',因此建议使用python的 r 前缀,\ # 这样就不用考虑转义的问题了(和repr原形毕露类似) s = r'ABC\\-001' 对应的正则表达式字符串不变 'ABC\\-001' import re ret = re.match(r'^\d{3}\-\d{3,8}$','010-12345') print(ret) # <_sre.SRE_Match object; span=(0, 9), match='010-12345'> # match() 方法判断是非匹配,如果匹配成功,返回一个Match对象,否则返回None,常见的判断方法是 test = '010-12345' if re.match(r'^\d{3}\-\d{3,8}$',test): print('ok') else: print('failed') # ok # ***切分字符串*** # 用正则表达式切分字符串比用固定的字符更灵活 ret = 'a b c'.split(' ') print(ret) # ['a', 'b', '', 'c'] 无法识别连续的空格 ret = re.split(r'\s+','a b c') print(ret) # ['a', 'b', 'c'] 这样通过 \s+ 匹配多个空格就可以切割空格了 ret = re.split(r'[\s\,]+','a,b c d') print(ret) # ['a', 'b', 'c', 'd'] 可以按照 正则表达式匹配的字符串,包含一个或多个由逗号和空格组成的字符串进行切割 ret = re.split(r'[\s\,\;]+','a,b;; c d') print(ret) # ['a', 'b', 'c', 'd'] # ***分组,提取字符串*** # 除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group) m = re.match(r'(\d{3})-(\d{3,8})$','010-12345') # ^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码 print(m) # <_sre.SRE_Match object; span=(0, 9), match='010-12345'> print(m.group(0)) # 010-12345 print(m.group(1)) # 010 print(m.group(2)) # 12345 # group(0) 表示原始字符串 ,group(1) group(2)....表示第1,2...个子串 import re t = '19:05:30' m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t) print(m.groups()) # ('19', '05', '30') (0[0-9]|1[0-9]|2[0-3]|[0-9]) 表示 0[0-9] 或者 1[0-9] 或者 2[0-3] 或者 [0-9] ,0[0-9]表示第一个数字是0第二个数字是0-9中的一个数字,\ # 同样的1[0-9]表示第一个数字是1第二个数字是0-9; 2[0-3]表示第一个数字是2第二个数字是0-3 这样这就表示一个24进制的小时了 # 这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期 '^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$' # 对于'2-30','4-31'这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了 # **贪婪匹配** # 正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0 import re ret = re.match(r'^(\d+)(0*)$','1023000').groups() print(ret) # ('1023000', '') 由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了 # 必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配 ret = re.match(r'^(\d+?)(0*)$','1023000').groups() print(ret) # ('1023', '000') # **编译** # 当我们在Python中使用正则表达式时,re模块内部会干两件事情: # 编译正则表达式,如果正则表达式的字符串本身不合法,会报错; # 用编译后的正则表达式去匹配字符串。 # 如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,\ # 接下来重复使用时就不需要编译这个步骤了,直接匹配 import re re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$') ret1 = re_telephone.match("010-12345").groups() # 编译后生成Regular Expression对象,由于该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串 print(ret1) # ('010', '12345') ret2 = re_telephone.match("010-8086").groups() # **练习题** # 提取邮箱的姓名,如下 # <Tom Paris> tom@voyager.org => Tom Paris # bob@example.com => bob import re def name_of_email(addr): m = re.match(r'^([0-9a-zA-Z\.\s<>]+)@[a-zA-Z]+.[a-zA-Z]+$',addr) if re.match(r'^<([0-9a-zA-Z\.\s]+)>',m.group(1)): return re.match(r'^<([0-9a-zA-Z\.\s]+)>',m.group(1)).group(1) else: return m.group(1) ret = name_of_email('tom@voyager.org') print(ret)
''' 正则表达式 字符组, [] , 一个字符组只能匹配一个字符 ,字符组中的特殊字符一般是不用转义的,如果因为字符组中其他匹配符如\d使用了转义符,则其他特殊字符可能就需要使用转义符了,可以这样做先不加转义符,显示颜色或者语法有问题再加上,在[]外面的特殊字符是一定要加转义符的 [0-9] [a-z] [A-Z] [0-9a-fA-F] . 匹配除换行符外的任意字符(当在re函数如re.findall中设置了flags=s时,也可匹配换行符了) \w 数字字母下划线 \s空白符 \d数字 \W 非数字字母下划线 \S非空白符 \D非数字 \n 换行符 \t 制表符 \b匹配一个单词的结尾 ^ 字符串的开始 $ 字符串的结尾 a|b 数字a或者b ()匹配括号内的表达式,也表示一个组 [...] 匹配字符组中的字符 [^...] 匹配除了字符组中字符的所有字符
[\u4e00-\u9fa5] 匹配一个汉字 量词 * 0次或更多次 + 1次或更多次 ? 0次或1次 {n} 重复n次 {n,} 重复n次或更多次 {n,m} 重复n次到m次 注意:量词默认都是贪婪匹配,在量词后面加 ? 使其变成惰性匹配,有一个比较常用的是 .*?X 表示取前面任意长度的字符,直到出现一个X(X可以使一个字符,也可以是多个字符) 转义符 \ \ 是转义符,即可以通过 \ 把其他字符变成具有特殊意义的代表,如在正则表达式中\d把字母d转义成了可匹配任意字符的代表 如果要取消\的转义功能,只让\作为字符的话,需要在\前面再加一个\ 比如要匹配 '\d' ,因为 \ 在字符串中有特殊含义所以 要用两个\表示\,即字符串中要写成'\\d',因为在正则表达式中\也有特殊含义, 所以'\\d'中的每个'\'都需要两个'\'来表示,即如果要匹配字符串'\d',我们的正则表达式应为'\\\\d',这样太麻烦了,为了简化在 字符串和正则表达式前都加上 r ,r 代表原生字符串,省去了 \ 在字符串中的特殊含义,如 print(r'\n') 输出 '\n',print('\n') 什么也不输出,只是换行 所以匹配 r'\d' 我们只需要写 r'\\d'就行了 正则 待匹配的字符 匹配结果 '\d ' '\d' False '\\d' '\d' True 这个在测试工具中是成功的,但在python解释器中不行 '\\\\d' '\\d' True 同上,python解释器中不成功 '\\\\d' '\d' True 这样是可以的,就是太麻烦了 r'\d' r'\d' False r'\\d' r'\d' True 这个在解释器中是成功的,所以我们都使用这种办法 re 模块的常用方法 findall() 返回所有满足条件的结果,放在一个列表中 如果正则表达式中有括号的话,表示一个组,findall会只把匹配结果中组的内容返回,如果想要把匹配字符串全部返回, 取消权限即可,在正则表达式的括号里的开始位置加上 ?: 就行了 search() 只匹配第一个并返回一个包含匹配信息的对象,该对象可以调用group()方法得到匹配的字符串 search中分组 根据分组的索引使用分组 \1,1是分组的索引值 ret = re.search(r'<(\w+?)>(\w+)</\1>',"<b>hello</b>") # \1表示和group(1)中的内容一样 print(ret.group()) # <b>hello</b> 默认是group(0) 返回匹配的字符 print(ret.group(0)) # <b>hello</b> print(ret.group(1)) # b 返回匹配字符串中第一组内容 print(ret.group(2)) # hello 返回匹配字符串中第二组内容 给分组起名字 (?P<tag>正则表达式),使用分组的名字(?P=tag) ret = re.search('<(?P<tag>\w+?)>\w+</(?P=tag)>',"<b>hello</b>") print(ret.group()) # <b>hello</b> print(ret.group('tag')) # b match() 和findall基本一样,不同的是match的正则表达式只从字符串的开始位置匹配,即默认在正则表达式的开头加入^ split() 使用正则表达式匹配字符串,然后用匹配的字符串切割原字符串,返回一个列表,如果正则表达式中有括号即组, 把括号中匹配的字符串保留到列表中返回 sub(正则,新内容,字符串,个数) 返回替换后的字符串 subn() 返回一个元组,元组中是替换后的字符串和替换的个数 finditer() 返回一个存放匹配结果的迭代器,节省内存 compile(正则表达式) 将正则表达式编译成为一个 正则表达式对象,并返回该对象, 通过这个对象可以调用re的方法用来匹配或者切割字符串,多次使用该正则表达式时节省编译时间 re模块的方法都有一个flags=0的默认参数,可以通过传入这个默认参数如re.S,改变方法的功能 re.I(IGNORECASE)忽略大小写,括号内是完整的写法 re.M(MULTILINE)多行模式,改变^和$的行为 re.S(DOTALL)点可以匹配任意字符,包括换行符 re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用 re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释 '''
# 只匹配整数 ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))") print(ret) #['1', '-2', '60', '', '5', '-4', '3'] 因为 ()表示取匹配字符串中组的内容,小数也被匹配到了,只是没取(没有组中的内容),所以会出现空字符串的情况 ret.remove("") print(ret) #['1', '-2', '60', '5', '-4', '3']
爬虫中用到的正则匹配
常用正则表达式回顾:
单字符:
. : 除换行以外所有字符
[] :[aoe] [a-w] 匹配集合中任意一个字符
\d :数字 [0-9]
\D : 非数字
\w :数字、字母、下划线、中文
\W : 非\w
\s :所有的空白字符包,括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S : 非空白
数量修饰:
* : 任意多次 >=0
+ : 至少1次 >=1
? : 可有可无 0次或者1次
{m} :固定m次 hello{3,}
{m,} :至少m次
{m,n} :m-n次
边界:
$ : 以某某结尾
^ : 以某某开头
分组:
(ab)
贪婪模式: .*
非贪婪(惰性)模式: .*?
re.I : 忽略大小写
re.M :多行匹配
re.S :单行匹配
re.sub(正则表达式, 替换内容, 字符串)
回顾练习:
import re
#提取出python
key="javapythonc++php"
re.findall('python',key)[0]
#####################################################################
#提取出hello world
key="<html><h1>hello world<h1></html>"
re.findall('<h1>(.*)<h1>',key)[0]
#####################################################################
#提取170
string = '我喜欢身高为170的女孩'
re.findall('\d+',string)
#####################################################################
#提取出http://和https://
key='http://www.baidu.com and https://boob.com'
re.findall('https?://',key)
#####################################################################
#提取出hello
key='lalala<hTml>hello</HtMl>hahah' #输出<hTml>hello</HtMl>
re.findall('<[Hh][Tt][mM][lL]>(.*)</[Hh][Tt][mM][lL]>',key)
#####################################################################
#提取出hit.
key='bobo@hit.edu.com'#想要匹配到hit.
re.findall('h.*?\.',key)
#####################################################################
#匹配sas和saas
key='saas and sas and saaas'
re.findall('sa{1,2}s',key)
#####################################################################
#匹配出i开头的行
string = '''fall in love with you
i love you very much
i love she
i love her'''
re.findall('^.*',string,re.M)
#####################################################################
#匹配全部行
string1 = """<div>静夜思
窗前明月光
疑是地上霜
举头望明月
低头思故乡
</div>"""
re.findall('.*',string1,re.S)
项目需求:爬取糗事百科指定页面的糗图,并将其保存到指定文件夹中
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
import re
import os
if __name__ == "__main__":
url = 'https://www.qiushibaike.com/pic/%s/'
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
}
#指定起始也结束页码
page_start = int(input('enter start page:'))
page_end = int(input('enter end page:'))
#创建文件夹
if not os.path.exists('images'):
os.mkdir('images')
#循环解析且下载指定页码中的图片数据
for page in range(page_start,page_end+1):
print('正在下载第%d页图片'%page)
new_url = format(url % page)
response = requests.get(url=new_url,headers=headers)
#解析response中的图片链接
e = '<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>'
pa = re.compile(e,re.S)
image_urls = pa.findall(response.text)
#循环下载该页码下所有的图片数据
for image_url in image_urls:
image_url = 'https:' + image_url
image_name = image_url.split('/')[-1]
image_path = 'images/'+image_name
image_data = requests.get(url=image_url,headers=headers).content
with open(image_path,'wb') as fp:
fp.write(image_data)
import re
# 使用sub 或subn 正则匹配字符串然后替换
# 注意:除了赋值操作,其他都不会改变原字符串,只会返回一个新的字符串.列表则不同.函数操作是直接在原列表上进行的
content = "<b>hello2</b><b>hello3</b>" # 这里的需求是将hello2和hello3替换成 '你好'
# 直接使用sub或sub
content = re.sub(r'>(\w+?)<','你好',content) # 可以进行替换,这是这里分组是无效的,并没有按照我希望的按照分组中的正则字符串进行匹配,而是使用整个正则进行匹配了,所以要在正则匹配上写的更精确
print(content)
# 如果使用上面的方法不容易实现时,使用下面的方法,先分组再替换
s_list = re.findall(r'>(\w+?)<',content)
for s in s_list:
content = re.sub(s,'你好',content) # 使用sub替换
# content = content.replace(s,'你好') # 使用字符串替换
print(content)