05 request高级操作
模拟登陆
引入
sometimes,相关的需求会让我们去爬取基于某些用户的相关用户信息,例如爬取张三人人网账户中的个人身份信息、好友账号信息等。那么这个时候,我们就需要对当前用户进行登录操作,登录成功后爬取其用户的相关用户信息。在浏览器中我们可以很便捷的进行用户登录操作,但是使用requests模块如何进行模拟浏览器行为的登录操作呢?
分析
- 模拟浏览器请求行为
- 在浏览器中进行登录时,录入完用户名、密码和验证码后,需要点击登录按钮。只有在点击登录按钮后,当前页面才会发起一次网络请求。该次网络请求可以通过抓包工具捕获。
-
经过抓包分析后,我们就可以模拟浏览器点击登陆按钮后发起的请求操作了。该请求为post请求,对应的url在上图中可以看到,切请求携带的参数也可以在抓到的数据包中获取。
- 参数分析:
- email:用户名
- password:密码
- icode:验证码(通过云打码识别)
- 剩下的参数不需要分析
- 参数分析:
-
模拟登录代码:
import requests
from lxml import etree
from YunCode import YDMHttp,get_code_image
if __name__ == '__main__':
url = 'http://www.renren.com/'
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'
}
page_text = requests.get(url=url, headers=headers).text
print(page_text)
print(page_text)
# 解析验证码图片地址
tree = etree.HTML(page_text)
code_img_src = tree.xpath('//*[@id="verifyPic_login"]/@src')[0]
# 请求验证码图片,且保存至本地
img_data = requests.get(url=code_img_src, headers=headers).content
with open('./code.jpg', 'wb') as fp:
fp.write(img_data)
#验证码图片显示的内容数据
code_text=get_code_image('./code.jpg', 1004)
#参数处理
formdata = {
'email': 'xxx',
'icode': code_text,
'origURL': 'http://www.renren.com/home',
'domain': 'renren.com',
'key_id': '1',
'captcha_type': 'web_login',
'password': '7b456e6c3eb6615b2e122a2942ef3845da1f91e3de075179079a3b84952508e4',
'rkey': '44fd96c219c593f3c9612360c80310a3',
'f': 'https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3Dm7m_NSUp5Ri_ZrK5eNIpn_dMs48UAcvT-N_kmysWgYW%26wd%3D%26eqid%3Dba95daf5000065ce000000035b120219',
}
#发送post请求模拟游览器登陆
post_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2020201440719'
response = requests.post(url=post_url, data=formdata, headers=headers).text
# 将响应内容写入文件
with open('./renren.html','w') as fp:
fp.write(response)
requests模块的cookie处理
会话和Cookies
在浏览网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问,而且登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录。还有一些网站,在打开浏览器时就自动登录了,而且很长时间都不会失效,这种情况又是为什么?其实这里面就涉及到了会话和cookie的相关知识,本节就来揭开他们的神秘面纱。
无状态HTTP
HTTP的无状态指的是http协议对事物处理是没有记忆能力的,也就是说服务器不知道客户端是什么状态。当我们向服务器发送请求后,服务器解析此请求,然后返回对应的响应,服务器负责完成这个过程,而且这个过程是完全独立的,服务器不会记录前后状态的变化,也就是缺少状态记录。这就意味着如果后续需要处理前面的信息,则必须重传,这导致需要额外传递一些前面的重复请求,才能获取后续响应,然而这种效果显然不是我们想要的。为了保持前后状态,我们肯定不能将前面的请求全部重传一次,这太浪费资源了,对于这种需要用户登录页面来说,更是棘手。
这时两个保持http连接状态的技术出现了,分别是会话和cookie。会话在服务端,用来保存用户的会话信息。cookie在客户端,有了cookie,浏览器在下次访问网页时会自动附带上cookie发送给服务器,服务器识别cookie并鉴定出是哪个用户,然后在判断出用户的相关状态,然后返回对应的响应。
- 会话:会话(对象)是用来存储特定用户进行会话所需的属性及配置信息的。
- cookie:指的是某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据。
- 会话维持:当客户端第一次请求服务器时,服务器会返回一个响应对象,响应头中带有Set-Cookie字段,cookie会被客户端进行存储,该字段表明服务器已经为该客户端用户创建了一个会话对象,用来存储该用户的相关属性机器配置信息。当浏览器下一次再请求该网站时,浏览器会把cookie放到请求头中一起提交给服务器,cookie中携带了对应会话的ID信息,服务器会检查该cookie即可找到对应的会话是什么,然后再判断会话来以此辨别用户状态。
- 形象案例:当iphone用户第一次向iphone的售后客服打电话咨询相关问题时,售后客服会针对当前用户创建一个唯一的“问题描述”,用来记录当前用户的iphone产品出现的相关问题,然后当用户阐述清楚问题后,售后客服就会将问题记录在所谓的“问题描述”中,并将“问题描述”的唯一编号通过短信告诉该用户。这样的好处就是,下次该用户的产品再次出现问题向售后电话咨询时,提供了“问题描述”的唯一编码后,就不需要在将该产品之前的问题再次进行描述了。此案例中,“问题描述”就相当于是会话,“问题描述”的唯一编码就是会话ID,短信就是cookie。
案例分析
需求:使用requests模块实现github的模拟登录
-
浏览器登录流程:
- 录入用户名和密码,点击登录
- 页面跳转,显示登录成功后的主页面:
-
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.检查分析抓取的登陆时发起的post请求对应的数据包:
该数据包是携带了cookie进行的请求发送,那么该cookie是什么时候存储到客户端的呢?一定是该请求的前次请求时产生的cookie。
在浏览器进行登陆请求发送之前,我们已经是第二次访问的github服务器端了,因为第一次是在浏览器中访问登陆页面时。
抓取该次请求的数据包,查看响应头信息中是否存在set-cookie,如果有,则证实该次请求时,服务器端给客户端创建了会话对象,且创建了cookie返回给了客户端进行存储。
果然存在set-cookie,因此,我们在使用requests模块进行模拟登陆时,发起的请求也是需要携带cookie的。那么cookie如何被携带到requests的请求中呢?
- requests模块处理cookie的两种方式:
1.将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'
}
2.创建会话对象,使用会话对象进行请求发送。因为会话中会自动携带且处理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:登录成功
requests模块的代理IP
引入
我们在做爬虫的过程中经常会遇到这样的情况,最初爬虫正常运行,正常抓取数据,一切看起来都是那么美好,然而一杯茶的功夫可能就会出现错误,比如403,这时打开网页一看,可能会看到“您的IP访问频率太高”这样的提示。出现这种现象的原因是网站采取了一些反爬措施。比如,服务器会检测某个IP在单位时间内请求的次数,如果超过了某个阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封IP。
既然服务器检测的是某个IP单位时间的请求次数,那么借助某种方式来伪装我们的IP,让服务器识别不出是由我们本机发起的请求,不就可以成功防止封IP了吗?一种有效的方式就是使用代理。
什么是代理
代理实际上指的就是代理服务器,它的功能就是代理网络用户去取得网络信息。形象的说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给Web服务器,Web服务器把响应传回我们。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥梁,此时本机不是直接向Web服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后代理服务器再发送给Web服务器,接着由代理服务器再把Web服务器返回的响应转发给本机。这样我们同样可以正常访问网页,但这个过程中Web服务器识别出的真实IP就不再是我们本机的IP了,就成功实现了IP伪装,这就是代理的基本原理。
代理的作用
- 突破自身IP访问的限制,访问一些平时不能访问的站点。
- 隐藏真实IP,免受攻击,防止自身IP被封锁
相关代理网站
- 快代理
- 西祠代理
- www.goubanjia.com
案例展示
当我们在百度中搜索“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)