文章目录
-
1.正则表达式
-
1.1 元字符
-
使用点
11`.
11`匹配任意字符
* \d匹配数字
* + * 匹配多个字符
*11`[]
11`(字符集)
* 使用.*匹配任意多个字符1
2
31 * 1.2 数量词
2 * 1.3 精确匹配与泛匹配
3 -
泛匹配
* 精确匹配1
21 * 1.4 贪婪匹配与非贪婪匹配
2 -
- re模块
-
2.1 re.match
* 2.2 re.search
* 2.3 re.findall 划重点!
* 2.4 re.split
* 2.5 re.sub
* 2.6 re.compile
* 2.7 原生字符串
* 2.8 匹配开头结尾 -
末尾匹配
1
21 * 2.9 案例:抓取电影天堂数据
2
1.正则表达式
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。——百度百科
下面是正则表达式常见的使用场景:
- 检查字符串的合法性
-
验证用户名 (a-z,0-9,不能全是数字,不能全是字母)
- 验证邮箱格式 (xxx@qq.com)
- 验证电话号码 (11位数字)
- 验证身份证 (18位 )
- 验证QQ号码格式(5-12纯数字,第一位不能为0);
-
提取字符串中信息
-
提取一条短信中数字;
- 提取文件名的后缀;
- 采集器(网络爬虫)
-
替换字符串
-
替换字符串中的非法字符;
- 对电话号码进行屏蔽;(1852****0102)
- 替换占位符 “hello {{name}} ” hello 王老二 (模板框架)
-
分割字符串
-
将一个字符串按照指定的规则进行分割;
通俗地说,正则的功能是检索特定形式的字符串,对象是字符串。
1.1 元字符
使用元字符匹配单个字符
.
匹配任意1个字符(除了\n)
[ ]
匹配[ ]中列举的字符
\d
匹配数字,即0-9
\D
匹配非数字,即不是数字
\s
匹配空白,即 空格,tab键
\S
匹配非空白
\w
匹配单词字符,即a-z、A-Z、0-9、_
\W
匹配非单词字符
*
匹配前一个字符出现0次或者无限次,即可有可无
+
匹配前一个字符出现1次或者无限次,即至少有1次
1
2
3
4
5
6
7
8
9 1import re
2
3text = '''
4这是用来匹配的字符串
5from:1427319758@qq.com
6tel:88888888
7'''
8
9
针对上述字符串进行元字符的正则匹配演示
使用点.匹配任意字符
1
2
3
4 1res = re.findall('.',text)
2print(res)
3
4
运行结果(注意返回的是列表):
1
2
3 1['这', '是', '用', '来', '匹', '配', '的', '字', '符', '串', 'f', 'r', 'o', 'm', ':', '1', '4', '2', '7', '3', '1', '9', '7', '5', '8', '@', 'q', 'q', '.', 'c', 'o', 'm', 't', 'e', 'l', ':', '8', '8', '8', '8', '8', '8', '8', '8']
2
3
\d匹配数字
1
2
3
4 1res = re.findall('\d',text)
2print(res)
3
4
运行结果:
1
2
3 1['1', '4', '2', '7', '3', '1', '9', '7', '5', '8', '8', '8', '8', '8', '8', '8', '8', '8']
2
3
+ * 匹配多个字符
1
2
3
4
5 1res = re.findall('\d+',text)
2res_1 = re.findall('\d*',text)
3print(res,res_1)
4
5
运行结果:
1
2
3
4 1['1427319758', '88888888']
2['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '1427319758', '', '', '', '', '', '', '', '', '', '', '', '', '88888888', '', '']
3
4
*匹配前一字符可以是0次,因此\d*会匹配每个不是数字的字符,即为空。\d+等同\d\d*
人为规定只能匹配出现在字符集中的字符,如要找寻字符串中的qq号,qq号不能以0开头,且位数是5~12
可用[1-9]\d{4,11}来限制
邮箱可能出现为字符串,可用[1-9a-zA-Z]\w+[@]\w+[.][a-zA-z]+可以匹配出任意常规格式的邮箱
1
2
3
4
5
6
7
8
9 1res = re.findall('[1-9a-zA-Z]\w+[@]\w+[.][a-zA-z]+','''
21427319758@163.com开会就
31427319758@edu.cn是否
41427319758@xx.mail 阿萨德
5asdfbglsafhlf上单发顺丰打打分
6''')
7print(res)
8
9
运行结果:
1
2
3 1['1427319758@163.com', '1427319758@edu.cn', '1427319758@xx.mail']
2
3
使用.*匹配任意多个字符
1
2
3
4 1res = re.findall('.*',text)
2print(res)
3
4
运行结果:
1
2
3 1['', '这是用来匹配的字符串', '', 'from:1427319758@qq.com', '', 'tel:88888888', '', '']
2
3
由于re.findall()函数默认遇到换行符 '\n’会终止当前的匹配,即不匹配换行符,每一行单独匹配,因此会出现空元素。
1.2 数量词
使用数量词匹配多个字符
{m}
匹配前一个字符出现m次
{m,n}
匹配前一个字符出现从m到n次
限制匹配字符出现的次数
1
2
3
4
5 1res = re.findall('[1-9]\d{4,11}',text)
2res_1 = re.findall('([1-9]\d{4,11})@',text)
3print(res,res_1)
4
5
运行结果:
1
2
3 1['1427319758', '88888888'] ['1427319758']
2
3
第一种方式匹配了同样满足规则的手机号,第二种方式是考虑到qq号隐藏在邮箱地址里,所以在后面加了@来限制匹配的区域,即只匹配@前的第1位非0的5-12位纯数字。
1.3 精确匹配与泛匹配
泛匹配
泛匹配是匹配包括特征字符在内的所有的东西
1
2
3
4 1res = re.findall('Hello.*like','Hello world! I like python!')
2print(res)
3
4
运行结果:
1
2
3 1['Hello world! I like']
2
3
精确匹配
精确匹配是匹配括号里面的东西
1
2
3
4 1res_1 = re.findall('Hello(.*)like','Hello world! I like python!')
2print(res_1)
3
4
运行结果:
1
2
3 1[' world! I ']
2
3
我想要Hello和like之间的字符,泛匹配的结果会包含首尾的特征字符Hello和like;而精确匹配只会得到特征字符之间的字符串。
1.4 贪婪匹配与非贪婪匹配
Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;
非贪婪则相反,总是尝试匹配尽可能少的字符。
在"*","?","+","{m,n}"后面加上?,使贪婪变成非贪婪。
1
2
3
4
5 1res = re.findall('Hello(.*)like','Hello world! I like python! I like you!')
2res_1 = re.findall('Hello(.*?)like','Hello world! I like python! I like you!')
3print(res,res_1)
4
5
运行结果:
1
2
3
4 1[' world! I like python! I ']
2[' world! I ']
3
4
res返回的是贪婪匹配,它会在找到Hello后,匹配尽可能多的字符,然后在最后一个like停下;
res_1返回的是非贪婪匹配,它在找到Hello后,只匹配第一个like前的字符。
2. re模块
一直以来我们都是使用 re.search() 函数,其实在正则表达式模块中还有一些函数可以很方便的对字符串进行操作。re模块的使用可以分为两种:第一种是对象式的方式,第二种是函数式的方式。
2.1 re.match
match() 用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。它的一般使用形式如下:
1
2
3 1match(pattern, string[, flag])
2
3
其中,pattern是正则表达式规则字符串,string 是待匹配的字符串,flag 是可选参数。
当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
1
2
3
4
5
6
7
8
9 1import re
2
3pattern = 'Python'
4string = 'dsgfaPythonahsdgjasghPythonasdjajsk'
5result = re.match(pattern,string)
6result_1 = re.match(pattern,string[5:])
7print(result,result_1)
8
9
运行结果:
1
2
3
4 1None
2<_sre.SRE_Match object; span=(0, 6), match='Python'>
3
4
2.2 re.search
search() 用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果,它的一般使用形式如下:
1
2
3 1search(pattern, string[, flag])
2
3
当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
1
2
3
4 1ret = re.search('\d+', "python = 9999, c = 7890, c++ = 12345")
2print(ret.group())
3
4
运行结果:
1
2
3 19999
2
3
2.3 re.findall 划重点!
上面的 match 和 search 方法都是一次匹配,只要找到了一个匹配的结果就返回。然而,在大多数时候,我们需要搜索整个字符串,获得所有匹配的结果。findall() 的使用形式如下:
1
2
3 1findall(pattern, string[, flag])
2
3
findall() 以列表形式返回全部能匹配的子串,如果没有匹配,则返回一个空列表。
1
2
3
4 1ret = re.findall(r"\d+", "python = 9999, c = 7890, c++ = 12345")
2print(ret)
3
4
运行结果:
1
2
3 1['9999', '7890', '12345']
2
3
2.4 re.split
split()按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
1
2
3 1split(pattern, string[, maxsplit, flags])
2
3
其中,maxsplit 用于指定最大分割次数,不指定将全部分割。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1'''
2split():
3 分割字符串 去掉了匹配到的字符串
4 结果是列表形式
5 maxsplit: 默认是0 表示全部切割
6 1 代表切割一次
7 2 代表切割两次
8'''
9pattern = '\d+'
10string = 'Pythonasdkjasd464654adhuiaghsdk564654akjsdhkashdkja'
11result = re.split(pattern,string,2)
12print(result)
13
14
运行结果:
1
2
3 1['Pythonasdkjasd', 'adhuiaghsdk', 'akjsdhkashdkja']
2
3
实际上就是面向字符串操作的string.split的升级版
2.5 re.sub
sub()用于替换,使用形式如下:
1
2
3 1sub(pattern, repl, string[, count, flags])
2
3
第一个参数为对应的正则表达式,第二个参数为要替换成的字符串,第三个参数为源字符串,第四个参数为可选项,代表最多替换的次数,如果忽略不写,则会将符合模式的结果全部替换。
1
2
3
4
5
6
7 1pattern = 'Java'
2repl = '********'
3string = 'kjasdJavaadhuiaghsdkJavaakjsd'
4result = re.sub(pattern,repl,string,1)
5print(result)
6
7
运行结果:
1
2
3 1kjasd********adhuiaghsdkJavaakjsd
2
3
string.replace的升级版
- 参数flags
方法1:
1
2
3
4 1ret = re.sub("\d+", '18', "age = 12")
2print(ret)
3
4
运行结果:
1
2
3 1age = 18
2
3
方法2 用函数:
re.sub()的本质是在字符串中检索符合pattern格式的子串,然后把子串当做参数输入repl中,默认的repl功能可以看成是函数,即不论我输入的子串是什么样的,输出都用repl参数代替,此处repl无法跟子串产生关系
1
2
3
4 1def replace(string,repl):
2 return repl
3
4
如果我们要让输出的repl和子串产生关系,如将字符串中的电话号码15654862043输出成1565****043,仅仅通过设定一个repl字符串是不能实现的,就需要在repl处传入一个函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1import re
2
3text = '''
415654561654
513905641750
615646575635
718976534547
8'''
9
10def replace(string):
11 string = string.group()
12 repl = string[0:4] + '****' + string[-4:-1]
13 return repl
14
15ret = re.sub("\d+", repl = replace, string = text)
16print(ret)
17
18
运行结果:
1
2
3
4
5
6 11565****165
21390****175
31564****563
41897****454
5
6
2.6 re.compile
使用 compile() 函数将正则表达式的字符串形式编译为一个 Pattern 对象。通过该对象提供的一系列方法对文本进行匹配查找,获得匹配结果(Match对象)。编译可以实现更高效的匹配查找等。
- compile()函数
compile() 函数用于编译正则表达式,生成一个 Pattern 对象,它的一般使用形式如下:
1
2
3
4
5
6
7 1import re
2# 将正则表达式编译成 Pattern 对象
3pattern_1 = re.compile('\d+', re.S)
4pattern_2 = re.compile('\D+', re.l)
5pattern_3 = re.compile('\w+', re.S)
6
7
之前定义pattern都是不包括flags参数的,因此不用re.compile,仅用赋值语句让pattern = ‘\d+’也能实现,compile函数的优点在于:1.可以包含flags参数;2.形成模块,便于后续复用
1
2
3
4
5
6 1results1 = re.findall(pattern_1, '540775360@qq.com')
2results2 = re.findall(pattern_2, "python = 9999, c = 7890, c++ = 12345")
3results3 = re.findall(pattern_3, "python = 997")
4print(results1, results2, results3)
5
6
2.7 原生字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1>>> mm = "c:\\a\\b\\c"
2>>> mm
3'c:\\a\\b\\c'
4>>> print(mm)
5c:\a\b\c
6>>> re.match("c:\\\\",mm).group()
7'c:\\'
8>>> ret = re.match("c:\\\\",mm).group()
9>>> print(ret)
10c:\
11>>> ret = re.match("c:\\\\a",mm).group()
12>>> print(ret)
13c:\a
14>>> ret = re.match(r"c:\\a",mm).group()
15>>> print(ret)
16c:\a
17>>> ret = re.match(r"c:\a",mm).group()
18Traceback (most recent call last):
19 File "<stdin>", line 1, in <module>
20AttributeError: 'NoneType' object has no attribute 'group'
21>>>
22
23
24
Python中字符串前面加上 r 表示原生字符串,
与大多数编程语言相同,正则表达式里使用""作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符"\",那么使用编程语言表示的正则表达式里将需要4个反斜杠"":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
Python里的原生字符串很好地解决了这个问题,有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。
1
2
3
4
5
6 1>>> ret = re.match(r"c:\\a",mm).group()
2>>> print(ret)
3c:\a
4
5
6
2.8 匹配开头结尾
^
匹配字符串开头
$
匹配字符串结尾
末尾匹配
需求:匹配163.com的邮箱地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1#coding=utf-8
2
3import re
4
5email_list = ["xiaoWang@163.com", "xiaoWang@163.comheihei", ".com.xiaowang@qq.com"]
6
7for email in email_list:
8 ret = re.match("[\w]{4,20}@163\.com", email)
9 if ret:
10 print("%s 是符合规定的邮件地址,匹配后的结果是:%s" % (email, ret.group()))
11 else:
12 print("%s 不符合要求" % email)
13
14
15
运行结果:
1
2
3
4
5
6 1xiaoWang@163.com 是符合规定的邮件地址,匹配后的结果是:xiaoWang@163.com
2xiaoWang@163.comheihei 是符合规定的邮件地址,匹配后的结果是:xiaoWang@163.com
3.com.xiaowang@qq.com 不符合要求
4
5
6
完善后
1
2
3
4
5
6
7
8
9
10
11 1email_list = ["xiaoWang@163.com", "xiaoWang@163.comheihei", ".com.xiaowang@qq.com"]
2
3for email in email_list:
4 ret = re.match("[\w]{4,20}@163\.com$", email)
5 if ret:
6 print("%s 是符合规定的邮件地址,匹配后的结果是:%s" % (email, ret.group()))
7 else:
8 print("%s 不符合要求" % email)
9
10
11
运行结果:
1
2
3
4
5 1xiaoWang@163.com 是符合规定的邮件地址,匹配后的结果是:xiaoWang@163.com
2xiaoWang@163.comheihei 不符合要求
3.com.xiaowang@qq.com 不符合要求
4
5
这个例子只用于展示,用来匹配邮箱是没有意义的。因为它匹配不出末尾不是com但包含了邮箱信息的多行字符串,如
1
2
3
4
5
6
7 1'''
2xiaoWang@163.com
3xiaoKang@163.com
4以上就是邮箱
5'''
6
7
万能正则
(.*?) 匹配除了换行以外的任意字符串。无论长短,最多匹配一次,非贪婪匹配。
这个正则表达式可以解决你想要提取的大部分数据,在写正则表达式的时候可以首先尝试这个组合,也许能达到事半功倍的效果。并且常常结合re.findall()函数。
2.9 案例:抓取电影天堂数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 1'''
2电影天堂思路:
3 1. 进入最新的电影更多 --> 更多的第一页
4 2. 翻页 https://www.dytt8.net/html/gndy/dyzz/list_23_{}.html
5 1. > 提取每一页的数据电影的详情页网址
6 2. > 发送请求 得到响应
7 3. > 正则提取链接
8 4. > 保存数据(文件)
9
10 抓取之前尽量熟悉网页的布局和结构!! 熟悉网址的关系, 经常查找网页源码中的数据(Ctrl+F).
11'''
12import re
13import requests
14
15for page in range(1, 5):
16 url_list = f'https://www.dytt8.net/html/gndy/dyzz/list_23_{page}.html'
17 # 找到详情页的网址 先进入 列表页
18 r_list = requests.get(url_list)
19 # 指定编码
20 r_list.encoding = 'gb2312'
21 # 提取详情页的网址 返回列表
22 url_detail = re.findall('<a href="(.*?)" class="ulink">', r_list.text)
23 for u in url_detail:
24 url = 'https://www.dytt8.net' + u
25 # print(url)
26 # 再次发请求 得到详情页的响应
27 response = requests.get(url)
28 # 也会乱码
29 response.encoding = 'gb2312'
30 # 提取数据
31 result = re.findall('<a href="(.*?)">.*?</a></td>', response.text)[0:]
32 print(result)
33 try:
34 with open('dytt.txt', 'a', encoding='utf-8') as fp:
35 # write 只能字符串 和 二进制的 不能写字典 列表 等
36 fp.write(result[0]+'\n')
37 except:
38 print('没有提取到数据!!')
39
40
41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 1'''
2歌曲下载:
3 可见即可爬 电影(VIP) 歌曲
4 思路:
5 1. 抓包 找到翻页 http://www.htqyy.com/genre/musicList/3?pageIndex=6&pageSize=20&order=hot
6 2. 进入上面的网址 提取歌曲id
7 3. 下载歌曲 http://f2.htqyy.com/play7/{id}/mp3/1
8'''
9import re
10import requests
11
12for page in range(1, 3): # 1, 2
13 # 翻页的
14 url_song = f'http://www.htqyy.com/genre/musicList/3?pageIndex={page}&pageSize=20&order=hot'
15 # 发送请求得到响应 提取响应中的歌曲ID
16 response_song = requests.get(url_song)
17 # 提取ID 返回列表
18 id_songs = re.findall('value="(\d+)"><span', response_song.text)
19 # 遍历歌曲的id 然后下载
20 for ids in id_songs:
21 song_url = 'http://f2.htqyy.com/play7/{}/mp3/1'.format(ids)
22 try:
23 # 请求歌曲的网址 然后得到响应
24 response = requests.get(song_url, timeout=5)
25 # 保存歌曲
26 with open(f'{ids}.mp3', 'wb') as fp:
27 fp.write(response.content)
28 except:
29 print(f'这个歌曲{ids}出错')
30
31
32