爬虫简介
什么是爬虫:
通过编写程序,模拟浏览器上网,然后让其去互联网上抓取数据的过程。
爬虫的价值:
实际应用
就业
爬虫究竟是合法还是违法的?
- 在法律中是不被禁止
- 具有违法风险
- 善意爬虫 恶意爬虫
爬虫带来的风险可以体现在如下2方面:
- 爬虫干扰了被访问网站的正常运营
- 爬虫抓取了收到法律保护的特定类型的数据或信息
如何在使用编写爬虫的过程中避免进入局子的厄运呢?
- 时常的优化自己的程序,避免干扰被访问网站的正常运行
- 在使用,传播爬取到的数据时,审查抓取到的内容,如果发现了涉及到用户隐私,商业机密等敏感内容需要及时停止爬取或传播
爬虫在使用场景中的分类
- 通用爬虫:抓取系统重要组成部分。抓取的是一整张页面数据。
- 聚焦爬虫:是建立在通用爬虫的基础之上。抓取的是页面中特定的局部内容。
- 增量式爬虫:检测网站中数据更新的情况。只会抓取网站中最新更新出来的数据。
反爬机制
- 门户网站,可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。
反反爬策略
- 爬虫程序可以通过制定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据。
robots.txt协议:
- 君子协议。规定了网站中哪些数据可以被爬虫爬取哪些数据不可以被爬取。
- www.taobao.com/robots.txt可以查看淘宝的robtos.txt协议
http协议
- 概念:就是服务器和客户端进行数据交互的一种形式。
- 官方概念:HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
- 工作原理:
- HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。
常用请求头信息
accept:浏览器通过这个头告诉服务器,它所支持的数据类型
Accept-Charset: 浏览器通过这个头告诉服务器,它支持哪种字符集
Accept-Encoding:浏览器通过这个头告诉服务器,支持的压缩格式
Accept-Language:浏览器通过这个头告诉服务器,它的语言环境
Host:浏览器通过这个头告诉服务器,想访问哪台主机
If-Modified-Since: 浏览器通过这个头告诉服务器,缓存数据的时间
Referer:浏览器通过这个头告诉服务器,客户机是哪个页面来的 防盗链
Connection:浏览器通过这个头告诉服务器,请求完后是断开链接还是何持链接
X-Requested-With: XMLHttpRequest 代表通过ajax方式进行访问
User-Agent:请求载体的身份标识
常用响应头信息
Location: 服务器通过这个头,来告诉浏览器跳到哪里
Server:服务器通过这个头,告诉浏览器服务器的型号
Content-Encoding:服务器通过这个头,告诉浏览器,数据的压缩格式
Content-Length: 服务器通过这个头,告诉浏览器回送数据的长度
Content-Language: 服务器通过这个头,告诉浏览器语言环境
Content-Type:服务器通过这个头,告诉浏览器回送数据的类型
Refresh:服务器通过这个头,告诉浏览器定时刷新
Content-Disposition: 服务器通过这个头,告诉浏览器以下载方式打数据
Transfer-Encoding:服务器通过这个头,告诉浏览器数据是以分块方式回送的
Expires: -1 控制浏览器不要缓存
Cache-Control: no-cache
Pragma: no-cache
https协议
HTTPS (Secure Hypertext Transfer Protocol)安全超文本传输协议,HTTPS是在HTTP上建立SSL加密层,并对传输数据进行加密,是HTTP协议的安全版。
https加密算法
**对称秘钥加密:**客户端向服务器发送一条信息,首先客户端会采用已知的算法对信息进行加密,比如MD5或者Base64加密,接收端对加密的信息进行解密的时候需要用到密钥,中间会传递密钥,(加密和解密的密钥是同一个),密钥在传输中间是被加密的。这种方式看起来安全,但是仍有潜在的危险,一旦被窃听,或者信息被挟持,就有可能破解密钥,而破解其中的信息。因此“共享密钥加密”这种方式存在安全隐患。
非对称秘钥加密:“非对称加密”使用的时候有两把锁,一把叫做“私有密钥”,一把是“公开密钥”,使用非对象加密的加密方式的时候,服务器首先告诉客户端按照自己给定的公开密钥进行加密处理,客户端按照公开密钥加密以后,服务器接受到信息再通过自己的私有密钥进行解密,这样做的好处就是解密的钥匙根本就不会进行传输,因此也就避免了被挟持的风险。就算公开密钥被窃听者拿到了,它也很难进行解密,因为解密过程是对离散对数求值,这可不是轻而易举就能做到的事。以下是非对称加密原理图:
但是非对称秘钥加密技术也存在如下缺点:
- 第一个是:如何保证接收端向发送端发出公开秘钥的时候,发送端确保收到的是预先要发送的,而不会被挟持。只要是发送密钥,就有可能有被挟持的风险。
- 第二个是:非对称加密的方式效率比较低,它处理起来更为复杂,通信过程中使用就有一定的效率问题而影响通信速度
**证书秘钥加密:**在上面我们讲了非对称加密的缺点,其中第一个就是公钥很可能存在被挟持的情况,无法保证客户端收到的公开密钥就是服务器发行的公开密钥。此时就引出了公开密钥证书机制。数字证书认证机构是客户端与服务器都可信赖的第三方机构。证书的具体传播过程如下:
- 服务器的开发者携带公开密钥,向数字证书认证机构提出公开密钥的申请,数字证书认证机构在认清申请者的身份,审核通过以后,会对开发者申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将密钥放在证书里面,绑定在一起
- 服务器将这份数字证书发送给客户端,因为客户端也认可证书机构,客户端可以通过数字证书中的数字签名来验证公钥的真伪,来确保服务器传过来的公开密钥是真实的。一般情况下,证书的数字签名是很难被伪造的,这取决于认证机构的公信力。一旦确认信息无误之后,客户端就会通过公钥对报文进行加密发送,服务器接收到以后用自己的私钥进行解密。
requests模块基础
-
在python实现的网络爬虫中,用于网络请求发送的模块有两种,第一种为urllib模块,第二种为requests模块。urllib模块是一种比较古老的模块,在使用的过程中较为繁琐和不便。当requests模块出现后,就快速的代替了urllib模块,因此,在我们课程中,推荐大家使用requests模块。
-
Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用。
-
requests模块是python中原生的基于网络请求的模块,其主要作用是用来模拟浏览器发起请求。功能强大,用法简洁高效。在爬虫领域中占据着半壁江山的地位。
-
环境安装
- pip install requests
-
使用流程/编码流程
- 指定url
- 基于requests模块发起请求
- 获取响应对象中的数据值
- 持久化存储
爬取搜狗首页的页面数据
import requests
if __name__ == "__main__":
#step_1:指定url
url = 'https://www.sogou.com/'
#step_2:发起请求
#get方法会返回一个响应对象
response = requests.get(url=url)
#step_3:获取响应数据.text返回的是字符串形式的响应数据
page_text = response.text
print(page_text)
#step_4:持久化存储
with open('./sogou.html','w',encoding='utf-8') as fp:
fp.write(page_text)
print('爬取数据结束!!!')
网页采集器
UA:User-Agent(请求载体的身份标识)
UA检测:门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求的载体身份标识为某一款浏览器,
说明该请求是一个正常的请求。但是,如果检测到请求的载体身份标识不是基于某一款浏览器的,则表示该请求
为不正常的请求(爬虫),则服务器端就很有可能拒绝该次请求。
UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器
在sogou上搜索Mustang:
https://www.sogou.com/web?query=Mustang&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=347184&sst0=1607063708245&lkt=7%2C1607063706415%2C1607063707871&sugsuv=1593752542573356&sugtime=1607063708245
定义url: https://www.sogou.com/web
import requests
if __name__ == "__main__":
#UA伪装:将对应的User-Agent封装到一个字典中
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
url = 'https://www.sogou.com/web'
#处理url携带的参数:封装到字典中
kw = input('enter a word:')
param = {
'query':kw
}
#对指定的url发起的请求对应的url是携带参数的,并且请求过程中处理了参数
response = requests.get(url=url,params=param,headers=headers)
page_text = response.text
fileName = kw+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_text)
print(fileName,'Crawler is ok!!!')
破解百度翻译
在百度翻译输入框中输入一个字母就会实时的出现该字的翻译及类似相关的翻译,在Network中的Preview可以看见json格式化后的响应数据。在python中输入一个单词就可以获取它的翻译并保存下来
python代码:
import requests
import json
if __name__ == "__main__":
#1.指定url
post_url = 'https://fanyi.baidu.com/sug'
#2.进行UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
#3.post请求参数处理(同get请求一致)
word = input('enter a word:')
data = {
'kw':word
}
#4.请求发送
response = requests.post(url=post_url,data=data,headers=headers)
#5.获取响应数据:json()方法返回的是obj(如果确认响应数据是json类型的,才可以使用json())
dic_obj = response.json()
#持久化存储
fileName = word+'.json'
with open(fileName,'w',encoding='utf-8') as fp:
json.dump(dic_obj,fp=fp,ensure_ascii=False)
print('over!!!')
豆瓣电影爬取
import requests
import json
if __name__ == '__main__':
url = 'https://movie.douban.com/j/search_subjects'
param = {
#查询的参数
'type': 'movie', #查询的电影
'tag': '恐怖', #电影的类型是恐怖
'sort': 'recommend', #推荐的
'page_limit': '20', #一次取多少部电影
'page_start': '0' #从第几部位置开始取
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
response = requests.get(url=url, params=param, headers=headers)
list_data = response.json()
print(list_data)
with open('./douban.json', 'w', encoding='utf-8') as fp:
#ensure_ascii,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。
json.dump(list_data, fp=fp, ensure_ascii=False)
print('Crawler is ok!')
药监总局相关数据爬取
需求:爬取国家药品监督管理总局中基于中华人民共和国化妆品生产许可证相关数据,http://scxk.nmpa.gov.cn:81/xk/
- 动态加载数据
- 首页中对应的企业信息数据是通过ajax动态请求到的。
http://125.35.6.84:81/xk/itownet/portal/dzpz.jsp?id=e6c1aa332b274282b04659a6ea30430a
http://125.35.6.84:81/xk/itownet/portal/dzpz.jsp?id=f63f61fe04684c46a016a45eac8754fe
- 通过对详情页url的观察发现:
- url的域名都是一样的,只有携带的参数(id)不一样
- id值可以从首页对应的ajax请求到的json串中获取
- 域名和id值拼接处一个完整的企业对应的详情页的url
- 详情页的企业详情数据也是动态加载出来的
- http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
- http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
- 观察后发现:
- 所有的post请求的url都是一样的,只有参数id值是不同。
- 如果我们可以批量获取多家企业的id后,就可以将id和url形成一个完整的详情页对应详情数据的ajax请求的url
import requests
import json
if __name__ == '__main__':
#获取id的url
post_url01 = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsList'
#获取企业详细信息的url
post_url02 = 'http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do?method=getXkzsById'
#UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
# 存储企业的id
id_list = []
# 存储所有的企业详情数据
data_list = []
# 爬取1-5页
for i in range(1,6):
#参数的封装
param01 = {
'on': 'true',
'page': str(i),
'pageSize': '15',
'productName': '',
'conditionType': '1',
'applyname': '',
'applysn': ''
}
#返回json格式的响应数据
page_text01 = requests.post(url=post_url01, data=param01, headers=headers).json()
# 批量获取ID
for id_a in page_text01["list"]:
ID = id_a["ID"]
id_list.append(ID)
# 批量获取详细信息
for id_b in id_list:
param02 = {
'id': id_b
}
page_text02 = requests.post(url=post_url02, data=param02, headers=headers).json()
data_list.append(page_text02)
#持久化存储
with open('medical.json','w',encoding='utf-8') as fp:
json.dump(data_list,fp=fp,ensure_ascii=False)
数据解析
聚焦爬虫:
爬取页面中指定的页面内容。
编码流程:
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 持久化存储
数据解析分类:
- 正则
- bs4
- xpath(***)
数据解析原理概述:
解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
- 1.进行指定标签的定位
- 2.标签或者标签对应的属性中存储的数据值进行提取(解析)
正则
爬取单张图片
import requests
if __name__ == '__main__':
#爬取图片数据
url = 'https://pic.qiushibaike.com/system/pictures/12384/123840689/medium/YNPMVERXPFQZEAMA.jpg'
#content返回的是二进制形式的图片数据
# text(字符串) content(二进制)json() (对象)
img_data = requests.get(url=url).content
with open('qiutu01.jpg','wb') as fp:
fp.write(img_data)
正则解析
<div class="thumb">
<a href="/article/123845932" target="_blank">
<img src="//pic.qiushibaike.com/system/pictures/12384/123845932/medium/0OVJD6UPJDMYLJ6O.jpg" alt="糗事#123845932" class="illustration" width="100%" height="auto">
</a>
</div>
python代码:
需求:爬取糗事百科中糗图板块一页下的所有的糗图图片
import requests
import re
import os
if __name__ == '__main__':
#创建一个文件夹,保存所有的图片
if not os.path.exists('./qiutuLibs'):
os.mkdir('./qiutuLibs')
url = 'https://www.qiushibaike.com/imgrank/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
#使用通用爬虫对url对应的一整张页面进行爬取
page_text = requests.get(url=url,headers=headers).text
#使用聚焦爬虫将页面中所有的糗图进行解析/提取
ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>'
#使用re.S参数以后,正则表达式会将这个字符串作为一个整体,将“\n”当做一个普通的字符加入到这个字符串中,在整体中进行匹配。不然的话,这里会出现空列表
img_src_list = re.findall(ex,page_text,re.S)
# print(img_src_list)
for src in img_src_list:
#拼接出一个完整的图片url
src = 'https:'+src
#请求到了图片的二进制数据
img_data =requests.get(url=src,headers=headers).content
#生成图片名称
img_name = src.split('/')[-1]
#图片存储的路径
imgPath = './qiutuLibs/'+img_name
with open(imgPath,'wb') as fp:
fp.write(img_data)
print(img_name,'download success!!')
正则解析分页爬取
需求:爬取糗事百科中糗图板块1-3页下的所有的糗图图片
import requests
import re
import os
if __name__ == '__main__':
if not os.path.exists('./qiutuLibs/all'):
os.mkdir('./qiutuLibs/all')
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36'
}
for pageNum in range(1, 4):
#对应页码的url
url = f'https://www.qiushibaike.com/imgrank/page/{pageNum}/