目录
一、正则表达式
正则表达式的作用
爬虫:数据的提取
例如:从 xxxxxxxxx.www.baidu.comxxxxxxx中将www.baidu.com提取出来
前后端:数据的规范
例如:检验用户输入的手机号是不是十一位数字
正则表达式:不是独属于Python的,是跨语言的。
正则表达式的使用
# 导入re模块 (他只是一个python文件:re.py)
import re
导入re模块后,当我们输入 re. 后Pycharm会自动提示有哪些可用的函数和方法
下面我们介绍一些在爬虫中常用的函数和方法
1.match函数
def match(pattern, string, flags=0):
"""Try to apply the pattern at the start of the string, returning
a Match object, or None if no match was found."""
return _compile(pattern, flags).match(string)
我们从re.py中查看match函数,可以看到match函数有两个必须传值的形参(pattern,string)和一个默认参数(flags)
pattern:传入正则表达式(提取规则,即提取内容需要满足的条件)
string:传入字符串(从该字符串中提取内容)
其次,match函数有返回值,所以我们在调用时需要一个变量进行返回值的接收
import re
# 目的:判断s字符串是不是以hello开头
s = 'hello,python'
res_ = re.match(r'hello', s)
print(res_) # 返回值: <re.Match object; span=(0, 5), match='hello'>
match的返回值是一个对象,只要目标字符串出现,match函数就是匹配成功的
匹配不成功返回值是:None
匹配单个字符(规范字符类型)
需求:豆瓣电影中有很多的电影,但我们只想要匹配哈利波特1-7部的,那我们应该怎么做呢?
# 匹配七次
import re
re.match(r'哈利波特1','哈利波特1')
re.match(r'哈利波特2','哈利波特2')
re.match(r'哈利波特3','哈利波特3')
re.match(r'哈利波特4','哈利波特4')
re.match(r'哈利波特5','哈利波特5')
re.match(r'哈利波特6','哈利波特6')
re.match(r'哈利波特7','哈利波特7')
以上代码缺点很明显,七部电影需要匹配七次,非常麻烦,那我们有什么通用一点的办法吗?
所以我们引入单个字符的匹配
# 通用办法
import re
res_ = re.match(r'哈利波特\d','哈利波特6') # \d可以代表一位数字
print(res_)
在这种情况下,第二个参数无论是哈利波特1,还是哈利波特2,还是哈利波特3......都可以匹配成功,可是这会出现另一个问题,我们到底匹配成功的是哪一部呢?是1还是2,3,4?所以匹配到的数据我们可以通过group()方法接收
data = res_.group()
print(data)
问题:当网站数据出现问题,比如多出来部哈利波特8,而实际上没有这部电影,我们通过上面的办法也会将哈利波特8匹配成功,所以当需求不一样时,我们的匹配规则也需要变化
import re
res_ = re.match(r'哈利波特[1234567]', '哈利波特8') # []:表示匹配[]中列举的一个字符
# res_ = re.match(r'哈利波特[1-7]', '哈利波特8') # 简单写法
print(res_) # 匹配不成功
[ ]中的内容在一些情况下有简单写法:1. 数字:0-9
2. 小写字母:a-z
3. 大写字母:A-Z
在这里,我们给出单个字符匹配的规则表
. | 匹配任意1个字符(除了\n之外) |
[ ] | 匹配[ ]中列举的字符 |
\d | 匹配单个数字:0,1,2,3,4,5,6,7,8,9 |
\D | 匹配非数字 |
\s | 匹配空白,即空格和Tab(缩进) |
\S | 匹配非空白 |
\w | 匹配单词字符,即a-z,A-Z,0-9,_ |
\W | 匹配非单词字符 |
\w 不推荐使用,匹配内容非官网文档所介绍的:a-z,A-Z,0-9,_
import re
res_ = re.match(r'哈利波特\w', '哈利波特一')
print(res_)
按照官方文档中介绍的规则,是应该匹配失败的,但是实际上是匹配成功的
匹配多个字符(规范字符数量)
问题:如果哈利波特拍的太好了,出到了第11部,那我们该如何匹配呢?
import re
res_ = re.match(r'哈利波特\d','哈利波特11') # \d可以代表一位数字
print(res_)
如果使用匹配单个字符的方法,上述代码匹配到的结果是:哈利波特1
所以要想实现我们的目的,需要引入匹配多个字符,下面是一些常用的匹配规则:
* | 匹配前一个字符出现0次或者无限次,即可有可无 |
+ | 匹配前一个字符出啊先1次或者无限次,即至少有1次 |
? | 匹配前一个字符出现1次或者0次,即要么有1次要么没有 |
{m} | 匹配前一个字符出现m次 |
{m,n} | 匹配前一个字符出现m到n次 |
重点:匹配前一个字符,即依赖前一个字符
import re
# 解决电影名称的问题
res_ = re.match(r'哈利波特\d{1,2}','哈利波特11') # \d可以代表一位数字,{1,2}表示1到2位
print(res_)
# 匹配电话号
res_1 = re.match(r'\d{11}', '15193039859') # 匹配11位数字
print(res_1)
phone_ = res_1.group()
print(phone_)
# 匹配qq号
res_2 = re.match(r'\d{6,12}', '925290303') # 匹配6-12位数字
print(res_2)
qq_number = res_2.group()
print(qq_number)
匹配开头和结尾
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
2.search函数
import re
# match和search的区别
# 匹配中间的数字
s = 'abc123456def'
res_1 = re.match(r'\d{6}', s)
print(res_1) # None
res_2 = re.search(r'\d{6}', s)
print(res_2) # <re.Match object; span=(3, 9), match='123456'>
match函数默认匹配开头
search函数从开头开始搜索,匹配到符合规范的返回结果
小案例
匹配QQ邮箱:qq号+@qq.com
分析:1. 6-12位的qq号
2. @后面的qq可以大写可以小写
3. 以com结尾
import re
email_ = input('请输入QQ邮箱:')
res_ = re.match(r'\d{6,12}@[Qq]{2}.com', email)
print(res_)
上述代码看似正确,但是存在一些小bug:输入925290303@qq#com时匹配成功
输入925290303@qq.comm时匹配成功
分析:1. 在正则表达式中,“ . ”表示匹配任意一个字符(\n)除外,所以会出现第一种匹配成功
2. match默认匹配开头,当满足“ \d{6,12}@[Qq]{2}.com ” 时无论后面再出现什么字符都会匹配成功
解决:1. [ ] , . , * , ? 只是当普通字符出现,而不是作为正则表达式的规则出现时,我们需要对其进行转义操作:在这些字符前面加上 \ 即可
2. 在match匹配时规定匹配结尾
import re
email_ = input('请输入QQ邮箱:')
res_ = re.match(r'\d{6,12}@[Qq]{2}\.com$', email)
print(res_)
分组
import re
email_ = input('请输入QQ邮箱:')
res_ = re.match(r'\d{6,12}@(qq|QQ)\.com$', email) # qq,QQ其中任意一个都行
print(res_)
()可以进行分组,括号里的是单独的逻辑作用不到外面
3.findall函数
引入:我们得到的3个人的qq号信息:小明:123456789,小王:23456789,小李:3215647 我们需要将三个人的qq号都提取出来,此时search和match都不能满足我们的需求,就需要使用一个新的函数:findall来实现
import re
info_ = '小明:123456789,小王:23456789,小李:3215647'
res_1 = re.search(r'\d+', info_)
print(res_1)
print(res_1.group()) # 123456789
res_2 = re.findall(r'\d+', info_)
print(res_2) # ['123456789', '23456789', '3215647']
从中我们可以发现区别:
search函数匹配到一个符合规范的就返回结果,不继续搜索,返回值为对象
findall函数匹配所有符合规范的结果,返回值为由符合规范结果构成的列表,没有匹配到返回一个空列表
无论需要匹配几个一般都采用findall函数,search函数在没有匹配到符合规则的内容时调用group方法会报错(影响程序运行),findall没有匹配到符合规则的内容时返回空列表
我们可以使用findall函数和()分组获取长字符串中符合规则的内容
import re
s = '<link data-vue-meta="true" rel="canonical" href="https://www.bilibili.com/video/BV1Zs41137Tr/">'
# 从标签中提取视频链接
url_ = re.findall(r'<link data-vue-meta="true" rel="canonical" href="(.*)">', s)
print(url_)
4.贪婪与非贪婪
贪婪:正则表达式默认是贪婪的,取得越多越好
非贪婪:加上一个 ?,只匹配第一个满足的
import re
s = '<link data-vue-meta="true" rel="canonical" href="https://www.bilibili.com/video/BV1Zs41137Tr/">'
# 从标签中提取视频链接(非贪婪)
url_ = re.findall(r'<link data-vue-meta="true" rel="canonical" href="(.*?)">', s)
print(url_)
二、爬虫
爬虫的概念
爬虫就是模拟客户端发送网络请求,接受请求对应的响应,按照一定的规则自动抓取互联网信息的程序。
客户端(浏览器),一般情况下操作者是正常用户,当我们用爬虫模拟客户端时,服务器时不欢迎我们的,所以我们应该尽可能的去模拟正常用户去发送请求。
爬虫:模拟客户端访问,抓取数据 ---> 我们做的事情
反爬:保护重要数据,阻止恶意网络攻击 ---> 网站的后端服务器,识别爬虫
反反爬:针对反爬措施 ---> 我们做的事情,尽量模拟正常用户:1.属性(静态)
2.行为(动态)
网络通信
1.电脑(浏览器)输入url:www.baidu.com(域名)
2.DNS服务器:IP地址标注的服务器 由www.baidu.com找到对应的IP地址1.1.1.1,DNS服务器将IP地址返回给浏览器
3.浏览器拿到IP地址去访问服务器,返回响应
一个请求(www.baidu.com)只能对应一个数据包(文件),就好比www.baidu.com这个请求,实际上只能获取到一个数据包:html数据,他是不包含图片、css、js的,但是我们在www.baidu.com的Networks里面看到了n个数据包,这些数据包共同组成了这个页面(n个数据包 -> n个请求 - > n个不同的url)。而产生这种现象都是浏览器的功劳:浏览器会帮助我们把url(www.baidu.com -> https://www.baidu.com/)补全,得到这个数据包(www.baidu.com)的响应,浏览器发现,这个数据包(www.baidu.com)的响应里面缺少了很多东西,对于这些缺少的部分浏览器会自动帮我们发送请求,获取响应,渲染完整,最终组成完整的页面。
爬虫的分类
通用爬虫 | 通常值搜索引擎和大型Web服务提供商的爬虫 | |
聚焦爬虫 (针对特定网站的爬虫,定向的获取某方面数据的爬虫) | 累计式爬虫 | 从开始到结束,不断进行爬取,过程中进行去重操作 |
增量式爬虫 | 已下载网页采取增量式更新和只爬取新产生的或者已经发生变化网页的爬虫 | |
Deep Web爬虫 | 不断通过静态链接获取的,隐藏在搜索表单后的,只有用户提交一些关键词才能获得的web页面 |
爬虫的基本流程
1.确认目标:确认目标的url:www.baidu.com
2.发送请求:发送网络请求,获取特定的服务端给你的响应
3.提取数据:从响应中提取到特定的数据,jsonpath,xpath为主,re为辅
4.保存:本地(html,json,txt)或者数据库
获取到的响应当中,有可能提取到了还需要发送请求的url,我们就拿着解析到的url继续发送请求