requests模块高级
版权声明:本博客来自路飞学城Python全栈开发培训课件,仅用于学习之用,严禁用于商业用途。
欢迎访问路飞学城官网:https://www.luffycity.com/
本节重点
-
requests模块的Cookies处理
-
requests模块的代理IP操作
1. requests模块的Cookies处理
1.1 会话和Cookies
在浏览网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问,而且登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录。还有一些网站,在打开浏览器时就自动登录了,而且很长时间都不会失效,这种情况又是为什么?其实这里面就涉及到了会话和cookie的相关知识,本节就来揭开他们的神秘面纱。
1.2 无状态HTTP
HTTP的无状态指的是http协议对事物处理是没有记忆能力的,也就是说服务器不知道客户端是什么状态。当我们向服务器发送请求后,服务器解析此请求,然后返回对应的响应,服务器负责完成这个过程,而且这个过程是完全独立的,服务器不会记录前后状态的变化,也就是缺少状态记录。这就意味着如果后续需要处理前面的信息,则必须重传,这导致需要额外传递一些前面的重复请求,才能获取后续响应,然而这种效果显然不是我们想要的。为了保持前后状态,我们肯定不能将前面的请求全部重传一次,这太浪费资源了,对于这种需要用户登录页面来说,更是棘手。
这时两个保持http连接状态的技术出现了,分别是会话和cookie。会话在服务端,用来保存用户的会话信息。cookie在客户端,有了cookie,浏览器在下次访问网页时会自动附带上cookie发送给服务器,服务器识别cookie并鉴定出是哪个用户,然后在判断出用户的相关状态,然后返回对应的响应。
- 会话:会话(对象)是用来存储特定用户进行会话所需的属性及配置信息的。
- cookie:指的是某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据。
- 会话维持:当客户端第一次请求服务器时,服务器会返回一个响应对象,响应头中带有Set-Cookie字段,cookie会被客户端进行存储,该字段表明服务器已经为该客户端用户创建了一个会话对象,用来存储该用户的相关属性机器配置信息。当浏览器下一次再请求该网站时,浏览器会把cookie放到请求头中一起提交给服务器,cookie中携带了对应会话的ID信息,服务器会检查该cookie即可找到对应的会话是什么,然后再判断会话来以此辨别用户状态。
- 形象案例:当iphone用户第一次向iphone的售后客服打电话咨询相关问题时,售后客服会针对当前用户创建一个唯一的“问题描述”,用来记录当前用户的iphone产品出现的相关问题,然后当用户阐述清楚问题后,售后客服就会将问题记录在所谓的“问题描述”中,并将“问题描述”的唯一编号通过短信告诉该用户。这样的好处就是,下次该用户的产品再次出现问题向售后电话咨询时,提供了“问题描述”的唯一编码后,就不需要在将该产品之前的问题再次进行描述了。此案例中,“问题描述”就相当于是会话,“问题描述”的唯一编码就是会话ID,短信就是cookie。
1.3 案例之github的模拟登录
1.3.1 项目需求
使用requests模块实现github的模拟登录
1.3.2 浏览器登录流程
-
录入用户名和密码,点击登录
-
页面跳转,显示登录成功后的主页面:
1.3.3 requests模拟登陆初试
-
通过抓包工具捕获点击登陆按钮后对应的post请求数据包
-
获取该数据包的url和请求参数
-
发起请求,进行持久化存储
import requests from lxml import etree headers = { ‘User-Agent’: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36’ } url = ‘https://github.com/session‘ data = { ‘commit’: ‘Sign in’, ‘utf8’: ‘✓’, ‘authenticity_token’:’lGnVdPaEf/jfBd6dzkQFpd4idQZxO96HSvRzWIPo5099dvmZTqEx+Q13zLjQzYpE08YmZdJStHd+Vk6Yhz7t/Q==’ , ‘login’: ‘bobo328410948@sina.com’, ‘password’: ‘bobo@15027900535’, ‘webauthn-support’: ‘supported’, } page_text = requests.post(url=url,headers=headers,data=data).text with open(‘./git.html’,’w’,encoding=’utf-8’) as fp: fp.write(page_text)
-
查看爬取下来的页面是否为登陆成功后的页面:
不是登陆成功后的页面,则表示模拟登陆失败!
1.3.4 模拟登陆失败原因分析
检查分析抓取的登陆时发起的post请求对应的数据包:
该数据包是携带了cookie进行的请求发送,那么该cookie是什么时候存储到客户端的呢?一定是该请求的前次请求时产生的cookie。
在浏览器进行登陆请求发送之前,我们已经是第二次访问的github服务器端了,因为第一次是在浏览器中访问登陆页面时。
抓取该次请求的数据包,查看响应头信息中是否存在set-cookie,如果有,则证实该次请求时,服务器端给客户端创建了会话对象,且创建了cookie返回给了客户端进行存储。
果然存在set-cookie,因此,我们在使用requests模块进行模拟登陆时,发起的请求也是需要携带cookie的。那么cookie如何被携带到requests的请求中呢?
1.3.5 requests模块cookie处理
requests模块处理cookie的两种方式:
-
将cookie手动从抓包工具中获取,然后封装到requests请求的headers中,将headers作用到请求方法中。(不建议)
headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 'Cookie':'xxxxxxxxx' }
-
创建会话对象,使用会话对象进行请求发送。因为会话中会自动携带且处理cookie。(推荐)
特别说明: 在使用会话对象发起登陆请求时,需要额外注意该请求参数:
’authenticity_token':'lGnVdPaEf/jfBd6dzkQFpd4idQZxO96HSvRzWIPo5099dvmZTqEx+Q13zLjQzYpE08YmZdJStHd+Vk6Yhz7t/Q==' ,
该参数的value值看起来像是一组密文数据,则该数据很有可能是动态变化的,因此需要动态捕获,动态赋值。如果该参数不处理的话,也是会登陆失败的。那么该值应该从那哪里动态捕获呢?这种值我们可以统一称为动态taken参数,该值一般都会动态存在与该请求对应的前台页面中,因此我们在登陆页面的源码中进行搜索:
因此我们可以通过数据解析,动态解析捕获到该请求参数的值。详细代码如下:
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
}
#创建会话对象,该会话对象可以调用get和post发起请求
session = requests.Session()
#使用会话对面对登录页面发起请求
page_text = session.get(url='https://github.com/login',headers=headers).text
#解析出动态的taken值
tree = etree.HTML(page_text)
t = tree.xpath('//*[@id="login"]/form/input[2]/@value')[0]
#指定模拟登录请求的url
url = 'https://github.com/session'
#参数封装(处理动态taken值)
data = {
'commit': 'Sign in',
'utf8': '✓',
'authenticity_token': t,
'login': 'bobo328410948@sina.com',
'password': 'bobo@15027900535',
'webauthn-support': 'supported',
}
#使用会话对象进行模拟登录请求发送(携带cookie)
page_text = session.post(url=url,headers=headers,data=data).text
#持久化存储
with open('./git.html','w',encoding='utf-8') as fp:
fp.write(page_text)
查看git.html:登录成功
1.4 案例之古诗文网模拟登陆
1.4.1 项目需求
使用requests模块实现古诗文网https://so.gushiwen.org/user/login.aspx的模拟登录
1.4.2 模拟登陆流程
首先我们分析登录页面,发现除了用户名、密码外,增加了验证码和两个隐藏的字段:
通过抓包分析,登录请求为post请求,请求参数除了用户名、密码、验证码外,隐藏字段的值同时需要上传
同时请求头里还有cookie信息
通过分析,我们请求流程如下:
- 通过session发起登录页面get请求
- 对登录页面进行数据解析,获取隐藏字段的值和验证码图片
- 对验证码图片使用超级鹰进行解析
- 通过sessionf发起登录页面post请求
- 通过页面数据验证是否登录成功
1.4.3 代码示例
超级鹰接口:
# chaojiying.py
import requests
from hashlib import md5
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: 图片字节
codetype: 题目类型 参考 http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id:报错题目的图片ID
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
爬虫代码:
import requests
from lxml import etree
from chaojiying import Chaojiying_Client
# 封装识别验证码图片的函数
def getCodeText(imgPath, codeType):
# 普通用户用户名
username = '超级鹰的用户名'
# 普通用户密码
password = '超级鹰的密码'
# 软件ID,开发者分成必要参数。登录开发者后台【我的软件】获得!
soft_id = 超级鹰的软件id
# 图片文件:即将被识别的验证码图片的路径
filename = imgPath
# 验证码类型,# 例:1004表示4位字母数字,不同类型收费不同。请准确填写,否则影响识别率。
# 在此查询所有类型 http://www.yundama.com/price.html
codetype = codeType
result = None
# 检查
if (username == 'username'):
print('请设置好相关参数再测试')
else:
# 初始化
chaojiying = Chaojiying_Client(username, password, soft_id)
im = open(filename, 'rb').read()
# 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
result = chaojiying.PostPic(im, codetype)
print(result)
pic_str = result['pic_str']
return pic_str
def get_code(url, s, headers):
# 将验证码图片下载到本地
page_text = s.get(url=url, headers=headers).text
# 解析验证码图片img中src属性值
tree = etree.HTML(page_text)
code_img_src = 'https://so.gushiwen.org' + tree.xpath('//*[@id="imgCode"]/@src')[0]
print(code_img_src)
img_data = s.get(url=code_img_src, headers=headers).content
# 将验证码图片保存到了本地
with open('code.jpg', 'wb') as fp:
fp.write(img_data)
# 解析动态参数
__VIEWSTATE = tree.xpath('//input[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//input[@id="__VIEWSTATEGENERATOR"]/@value')[0]
# 调用超级鹰验证码平台的示例程序进行验证码图片数据识别
code_text = getCodeText('code.jpg', 1004)
print('识别结果为:', code_text)
info = {
"code_text": code_text,
"__VIEWSTATE": __VIEWSTATE,
"__VIEWSTATEGENERATOR": __VIEWSTATEGENERATOR,
}
return info
def login(url, s, headers, info):
# 模拟登录
data = {
# 前两个参数是动态参数
'__VIEWSTATE': info["__VIEWSTATE"],
'__VIEWSTATEGENERATOR': info["__VIEWSTATEGENERATOR"],
'from': '',
'email': '古诗文的用户名',
'pwd': '古诗文网的密码',
'code': info["code_text"],
'denglu': '登录',
}
# s表示的session中已经存储了相关的cookie
response = s.post(url=url, data=data, headers=headers)
page_text = response.text
print('响应状态:', response.status_code)
with open('gushiwenwang.html', 'w', encoding='utf-8') as fp:
fp.write(page_text)
print('登录成功!')
if __name__ == '__main__':
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
}
url = 'https://so.gushiwen.org/user/login.aspx'
s = requests.Session()
info = get_code(url=url, s=s, headers=headers)
login(url=url,s=s,headers=headers,info=info)
2.requests模块的代理IP操作
2.1 引子
我们在做爬虫的过程中经常会遇到这样的情况,最初爬虫正常运行,正常抓取数据,一切看起来都是那么美好,然而一杯茶的功夫可能就会出现错误,比如403,这时打开网页一看,可能会看到“您的IP访问频率太高”这样的提示。出现这种现象的原因是网站采取了一些反爬措施。比如,服务器会检测某个IP在单位时间内请求的次数,如果超过了某个阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封IP。
既然服务器检测的是某个IP单位时间的请求次数,那么借助某种方式来伪装我们的IP,让服务器识别不出是由我们本机发起的请求,不就可以成功防止封IP了吗?一种有效的方式就是使用代理。
2.2 什么是代理
代理实际上指的就是代理服务器,它的功能就是代理网络用户去取得网络信息。形象的说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给Web服务器,Web服务器把响应传回我们。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥梁,此时本机不是直接向Web服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后代理服务器再发送给Web服务器,接着由代理服务器再把Web服务器返回的响应转发给本机。这样我们同样可以正常访问网页,但这个过程中Web服务器识别出的真实IP就不再是我们本机的IP了,就成功实现了IP伪装,这就是代理的基本原理。
2.3 代理的作用
- 突破自身IP访问的限制,访问一些平时不能访问的站点。
- 隐藏真实IP,免受攻击,防止自身IP被封锁
2.4 相关代理网站
- 快代理
- 西祠代理
- www.goubanjia.com
2.4 案例
当我们在百度中搜索“ip”关键词后,对应搜索结果的页面显示中会有本次浏览器发起请求对应的IP信息:
如果我们使用requests模块相关操作应用了代理,则请求到该页面中显示的ip信息就是代理IP相关信息了。我们可以使用requests模块请求方法的proxies参数处理代理IP:
import requests
import random
if __name__ == "__main__":
#不同浏览器的UA
header_list = [
# 遨游
{"user-agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)"},
# 火狐
{"user-agent": "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"},
# 谷歌
{
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"}
]
#不同的代理IP
proxy_list = [
{"http": "112.115.57.20:3128"},
{'http': '121.41.171.223:3128'}
]
#随机获取UA和代理IP
header = random.choice(header_list)
proxy = random.choice(proxy_list)
url = 'http://www.baidu.com/s?ie=UTF-8&wd=ip'
#参数3:设置代理
response = requests.get(url=url,headers=header,proxies=proxy)
response.encoding = 'utf-8'
with open('daili.html', 'wb') as fp:
fp.write(response.content)