python爬虫四(cookie,代理池,模拟登录(打码))

requests高级操作:cookie处理,代理操作,验证码识别,模拟登录

cookie:
cookie是存储在客户端的一组键值对,是由服务器端创建。

cookie应用:
免密登录(服务器端将用户id和密码存在cookie中)

案例

爬取该网站中的新闻资讯

https://xueqiu.com/

分析:首页第一屏的数据不是动态加载,直接爬到就拿到实实在在的数据,但是滚轮往下划,会发起ajax请求动态加载,再划就会再次发ajax。这类网站没有页码,前端绑定事件为滚轮滑动,触发回调发起ajax请求动态加载数据到下面,但不是无限,后面划不动,需要手动点击加载更多触发回调)
在这里插入图片描述
上图找到了ajax的请求地址,发现每次动态加载请求的url和方式都是一样的,只是携带的查询参数max_id不一样,所以我马上去首页全局搜索这个max_id如20370667,果然,找到了这个装满max_id的json串。
在这里插入图片描述
现在我就可以按照headers中的url发起请求拿这个json串。
在这里插入图片描述
居然拿不到!(请求方式和请求地址以及请求参数全都正确)上图返回的参数告诉你是因为你没有登录。
在这里插入图片描述
根本原因就是请求头中没有带cookie。

手动加:
你肯定马上就会去在请求头加上一个key,value,但是这样不好,cookie写死了,因为cookie是会随时变化的,不是定死的,并且在headers里一大坨cookie数据不好看。

自动加:
实时cookie:调用requests中的Session类来创建Session对象:
session = requests.Session()

session对象作用:

可以像requests一样调用get/post进行请求的发送,在使用session进行请求发送的过程中,如果产生了cookie,则cookie会被自动存储到session对象中。那么在爬虫中使用session对象处理cookie时,session对象至少需要被用几次?

答案是至少两次才算用上了session(带上了cookie),一次是去拿cookie,一次是使用拿到的cookie

session = requests.Session()

# 该次请求只是为了拿cookie存储到session对象中
session.get('https://xueqiu.com/',headers=headers)

url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20370668&count=15&category=-1'
json_str = session.get(url,headers=headers).json()
print(json_str)

结果:
在这里插入图片描述
现在在上图就可以看到包含所有max_id的一堆json串,记住只要模仿的是ajax请求,直接用json()拿数据!!!!!!!因为现在ajax请求后端回来的Reponse中一定肯定只有json数据,非ajax请求Response才是个页面,才用text()或content.decode()来拿。

拿去格式化一下:
在这里插入图片描述
然后就根据上图数据结构把所有id塞进一个列表,然后遍历max_id_list ,动态化max_id参数requests发起请求拿数据即可。

# 不用这种用列表生成式也可以
max_id_list = []
for dict in json_str['list']:
	max_id_list.append(dict[id])

代理操作(代理服务器)

代理服务器就是用来转发请求和响应的,fiddler就是一个典型的代理服务器的抓包工具。
在这里插入图片描述

为什么要用代理?

防止服务器知道我们的真实ip,因为爬虫都是在跑循环,很容易被封ip。

代理匿名度:

透明(知道你是用了代理也知道你的真实ip),匿名(知道你使用了代理,但不知道你的真实ip),高匿(不知道你用了代理,更不知道你的真实ip,当然高匿不是为所欲为,追代理商一样能追到你)

代理的类型:

http:只能代理http协议的请求。https:只能代理https协议的请求。socket协议:(基本不用)

如何获取代理服务器?

免费:西祠代理,快代理,goubanjia(这些几乎是不能用的)
付费:芝麻HTTP

http://h.zhimaruanjian.com/

不仅便宜,而且新用户还能免费提取api

案例:高频的requests一个网站,让这个网站封死我们ip,然后使用代理能正常请求到数据。

就爬西祠的所有ip和端口,一是没有ajax动态加载,二是数据量足够(四千多页),三是好爬(每一页的url都是/nn/页码)
在这里插入图片描述

ip_list = []
# 我爬的前499页的ip及端口
for i in range(1,500):
    url = 'https://www.xicidaili.com/nn/{}'.format(i)
    response = requests.get(url,headers=headers).content.decode()
    tree = etree.HTML(response)
    # 排除第一,用[1:],因为第一个是列名称
    tr_list = tree.xpath('//table[@id="ip_list"]//tr')[1:]
    for tr in tr_list:
        # xpath永远返回列表
        ip = tr.xpath('./td[2]/text()')[0]
        ip_port = tr.xpath('./td[3]/text()')[0]
        ip_list.append('{}:{}'.format(ip,ip_port))
print(ip_list)

我们的ip被西祠的服务器封了!
在这里插入图片描述

使用代理来接触ip被封的情况:

1、构建一个代理池列表
import random

代理ip池

import random
# 代理ip池
all_ips = [
    {'https':'58.218.92.91:2538'},
    {'https':'58.218.92.91:5421'},
    {'https':'58.218.92.86:4614'}, 
    {'https':'58.218.92.87:4543'},
]
ip_list = []
# 我就只爬一二页了
for i in range(1,3):
    print('正在爬取第{}页的ip及端口:'.format(i))
    url = 'https://www.xicidaili.com/nn/{}'.format(i)
    # random.choice()实现了随机生成指定的数据
    response = requests.get(url,headers=headers,proxies=random.choice(all_ips)).content.decode()
    tree = etree.HTML(response)
    # 排除第一,用[1:],因为第一个是列名称
    tr_list = tree.xpath('//table[@id="ip_list"]//tr')[1:]
    for tr in tr_list:
        # xpath永远返回列表
        ip = tr.xpath('./td[2]/text()')[0]
        port = tr.xpath('./td[3]/text()')[0]
        ip_list.append('{}:{}'.format(ip,port))
print(ip_list)

重新发起请求爬取成功:
在这里插入图片描述

问题:

往往在进行大量请求发送的时候,经常会报出这样的一个错误:HTTPConnectionPool Max retires exceeded

原因:

1、都知道cs架构首先要建立TCP连接,TCP为了节省消耗,默认为长连接keep-alive,即连接一次,传输多次,而如果连接迟迟不断开的话,TCP连接池是有限的,满了之后无法塞本次连接进连接池,导致请求无法发送。
解决方法:设置请求头中的Connection的值为close,表示每次请求传输后断开本次连接。
在这里插入图片描述

2、ip被服务器封死了。
解决方法:更换请求ip(ip代理池)

3、请求过于频繁。
解决方法:每次requests之间使用sleep时间间隔个一两秒

验证码识别:

线上的打码平台进行验证码识别:云打码超级鹰(本次使用)打码兔,这三种大码平台用法大同小异。

超级鹰的使用流程:
1、注册,进入用户中心,点击生成一个软件ID
在这里插入图片描述
这是超级鹰的打码加个体系,一块钱=1000积分:
在这里插入图片描述
2、下载封装好的超级鹰打码的示例代码:在里边封装了一个返回图片验证码结果的函数transform_code_img,调用这个即可打码,记得填上自己的username和password以及softID(要充值积分才能用,一块就行了)
在这里插入图片描述
上图中的a.jpg是个验证码图片,测试打码是否可用成功。其他两个平台,也是要封装一下,只不过实现代码不一样。

案例:识别该网站的图片验证码

https://so.gushiwen.org/user/login.aspx

1、解析出本次登陆页面对应的验证码图片地址。

2、发起请求拿到验证码图片到本地,所有网站的验证码图片每访问一次都会变,所我们代码中也必须只请求一次。

3、调用函数打码

具体代码如下:

login_url = 'https://so.gushiwen.org/user/login.aspx'
page_str = requests.get(login_url,headers=headers).content.decode()
tree = etree.HTML(page_str)
# 拿到当前页面的验证码图片的url
img_url = tree.xpath('//div[@class="mainreg2"]/img/@src')[0]
# 拿到图片
img = requests.get('https://so.gushiwen.org'+img_url,headers=headers).content
# 一定要存到本地存
with open('./img.jpg','wb') as f:
    f.write(img)
# 调函数来识别验证码图片
result = transform_code_img('./img.jpg',1004) # 数字字母混合1-4位类型为1004
print(result)

在这里插入图片描述
打码成功,下一步我们就要做模拟登录,那肯定是要携带参数的,至少得带着验证码,用户名及密码去请求登录接口,打开抓包工具,观察点击登录后请求了哪些数据包。很轻松的找到了,就是请求的这个数据包,我们马上带上这些参数进行模拟登录
在这里插入图片描述
模拟登录代码如下:

url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
    '__VIEWSTATE': 'verfNNKZS6lYTcFINM88TFlI9t/NEqNlXgMK2p+XqTLN6bPNDXXOKx04osNq5U6p332iA5EZIRRXEbtNtXaBywUK18Mj7tbSEsGY5BC6qkDqkX87nZX89brCYjY=',
    '__VIEWSTATEGENERATOR': 'C93BE1AE',
    'from': 'http://so.gushiwen.org/user/collect.aspx',
    'email': '18xxx62xx2',
    'pwd': 'axxxxxxlyc',
    'code': result,
    'denglu': '登录',
}
# result是是前面打出的验证码,这里输出是为了确认正确
print(result)
response = requests.post(url,headers=headers,data=data)
if response.status_code == 200:
    print('请求成功,但不代表模拟登陆成功,还是需要看页面根真实登录后的页面是否一致')
response_str = requests.post(url,headers=headers,data=data).content.decode()
with open('./gushiwen.html','w',encoding='utf8') as f:
    f.write(response_str)

然而我请求之后的页面还是个登录页面,没有请求到想要的登陆后的页面。

分析:我请求的url是没错的,但有两个奇怪的请求参数__VIEWSTATE和__VIEWSTATEGENERATOR,这两个的值我在上图的请求体中是写死的,那这两个的值会不会是动态变化的?并且看样子还是加密后的,那我再次登录看这两个值的变化,发现真是变化的。

解决:一般动态变化的请求参数都会被隐藏在首页的源码中。所以老方法在抓包工具中对这个请求参数的名称进行全局搜索,或者在elements中也可以,全局请求包组成的elements,肯定不能搜值撒,要搜键。
在这里插入图片描述
果然找到了这两个键。发现他们两个是前端表单隐藏域传值,下一步就是在当前页解析出这个动态值。该页面没有ajax动态加载

__VIEWSTATE = tree.xpath('//div/input[@id="__VIEWSTATE"]/@value')[0]

依然登录失败,应该是cookie导致,用session对象发请求即可,这个其实应该在分析请求体参数之前考虑,因为cookie导致的解决更简单,直接session发请求,那到底是验证码接口还是登录接口给我们塞的cookie呢,我不知道也不需要知道,我两个请求都用session发不就行了吗下面是完整代码:

session = requests.Session() # 创建空对象session
login_url = 'https://so.gushiwen.org/user/login.aspx'
page_str = session.get(login_url,headers=headers).content.decode()
tree = etree.HTML(page_str)
# 拿到当前页面的验证码图片的url
img_url = tree.xpath('//div[@class="mainreg2"]/img/@src')[0]
# 拿到动态参数__VIEWSTATE 
__VIEWSTATE = tree.xpath('//div/input[@id="__VIEWSTATE"]/@value')[0]
# 拿到图片
img = session.get('https://so.gushiwen.org'+img_url,headers=headers).content
# 一定要存到本地存
with open('./img.jpg','wb') as f:
    f.write(img)
# 调函数来识别验证码图片
result = transform_code_img('./img.jpg',1004) # 数字字母混合1-4位类型为1004
print("{}\n{}\n{}".format(result,__VIEWSTATEGENERATOR,__VIEWSTATE))
# 模拟登录
url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
	# 警示:请求体的数据,键和值都不要用单引号 
    "__VIEWSTATE": __VIEWSTATE,
    "__VIEWSTATEGENERATOR":"C93BE1AE",
    "from": "http://so.gushiwen.org/user/collect.aspx?type=s",
    "email": "你的用户名",
    "pwd": "你的密码",
    "code": result,
    "denglu": "登录",
}
response = requests.post(url,headers=headers,data=data)
if response.status_code == 200:
    print('请求成功,但不代表模拟登陆成功,还是需要看页面根真实登录后的页面是否一致')
response_str = session.post(url,headers=headers,data=data).content.decode()
with open('./gushiwen.html','w',encoding='utf8') as f:
    f.write(response_str)

警示:请求体的数据或者以后前后端交互json数据传输,键和值都不要用单引号 ,否则出问题,必须用双引号!!!

在这里插入图片描述

梳理:模拟登录涉及到了三种反爬:

一是图片验证码,二是动态化请求体参数,三是cookie

前面学了的反爬还有:

1、robots协议
2、UA请求头
3、ajax来动态生成数据
4、图片懒加载
5、ip代理
6、js加密(最复杂)
7、js混淆

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值