模拟二维码登录百度

  • 写在前面
  • 准备工作
    • 二维码地址
    • 登录状态
    • 获取gid
    • 登录参数
  • 代码部分
    • 二维码展示
    • 获取cookie
  • 完整代码
  • 写在后面

写在前面

前段时间写了利用BDUSS到达百度首页,这一次尝试使用二维码模拟登录,目前网上能搜到的相关内容基本失效了,但是思路基本不变,无非是百度改了些参数。本文较为复杂,要求对python的requests模块以及Chrome审查元素有一定了解,我不确定自己是否能完全讲明白,讲多少是多少吧,各位看官请坐。

准备工作

二维码地址

打开Chrome浏览器,清理掉baidu.com下的所有cookie,如果没有则忽略。打开百度首页,F12审查元素,在右上角点击登录,切换到二维码登录,可以看到NetWork里面出现了一些相关内容:



查看getqrcode?lp=pc:



这里的imgurl代表二维码地址,在浏览器访问一下就能看到二维码,为了获得该地址,我们需要向该条目的Request URL发起请求:

https://passport.baidu.com/v2/api/getqrcode?lp=pc&qrloginfrom=pc

有些小伙伴可能看到Request URL包含有gid、callback等参数,这里并不需要,访问上面的地址就能获得imgurl。

登录状态

让页面停滞一会儿,可以看到出现了好多unicast开头的请求,就是用来验证二维码扫描状态的。



未扫描时response如下:

tangram_guid_1607686170040({"errno":1})

先分析一下需要的参数:



channel_id是不是看起来很熟悉?就是imgurl里面的sign,这个参数就搞定了。刷新几次页面,可以看到gid、callback、tt、_这四个参数发生了变化,仔细观察可以发现后面两个是毫秒级时间戳,callback变化的只有后面那串数字,也是毫秒级时间戳,gid等下再说。观察多个unicast请求,发现gid、callback是不变的,可以确定callback是一个默认字符串加上第一次unicast请求的时间戳,另外两项则是新的unicast请求发生时的时间戳。对比相邻的unicast的tt参数,相减得到的数值接近30000,即30s,再细心观察,新的unicast出现时是一个unsafe-url:



过30s左右会变成正常的Get请求,在浏览器直接访问Request URL也是在30s后得到Response(真的不是你断网了…),也就是说,二维码未被扫描的时候,每30s验证一次状态(二维码被扫描,未点击确定的时候会重新设定等待时间,也许是5s),这样的话,在使用requests发送请求时要将timeout设置为30s以上,不然无法正确验证状态。


获取gid

这个参数在getqrcode?lp=pc请求中就已经出现了,首先查看该请求调用的js文件:



打开setChannel对应的js,单击左下角的花括号可以让格式化输出,按Ctrl+F搜索“gid”,可以看到指向了guideRandom,再搜索guideRandom,得到如下信息:



需要将其转换为python代码:

#获取gid
def guideRandom():string = 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'gid = ''#下面是根据JavaScript代码写的for z in string:#获取0到1的随机数,并乘以16t = 16 * random.random()#如果是z是x或者yif z in "xy":#hex将数字转换为十六进制,后面的你肯定能读懂s = hex(int(t)) if z == "x" else hex(3 & int(t) | 8)#将十六进制数转换为字符串,删除前面0xgid += str(s).replace('0x','')else:#不是x或y则不做处理gid += z#将字符串转换为大写,返回return gid.upper()

另外关于callback的函数,可以在‘uni_loginv4_tangram_b55ba1e.js’中搜索‘callback’:



baidu.id.key是“tangram_guid”,m是时间戳加1,与上面的猜测差不多。
检测二维码扫描状态的参数分析完毕。

登录参数

用百度的app扫描二维码,但不要点确定,可以发现unicast的Response变了:



errno变为0,而且获取到channel_id,跟imgurl中的sign一致(我这里跟上面不一样是因为刷新了页面),还有一个status,不出意外的话,这个是用来判断用户是否点击确定的,app点击确定,再查看unicast的Response:



可以看到status变为0,而且多了个参数"v",这个有什么用处呢?先放着不管,去看下面这个包:




这里设置了BDUSS、PTOKEN、STOKEN三个cookie,这些就是登录成功的标志,带上这三个cookie可以访问百度的大多数产品。再看看需要的参数:



bduss是不是有点眼熟?没错,就是刚才unicast返回的v,而且只有这个参数比较重要,别的要么是时间戳或固定值,就算有几个是变化的(sig、shaOne、callback等),用固定值也没有什么影响。OK,参数解析完毕。


如果跳转太快抓不到包的话,在开发者工具里面:



将网络模式改为Slow 3G(手速比网速快的可以不改),调整完毕再点击客户端上的确定,Response发生变化时快速点击左上角的小红点停止继续抓包,就不会因为刷新页面丢失抓到的包。

代码部分

代码分为两部分,第一部分是二维码的获取及展示,第二部分是获取cookie。

二维码展示

#将关于二维码的部分封装成类
class Show(object):def __init__(self,imgurl):#创建一个子线程,用于展示二维码#如果不创建子线程,在二维码关闭之前程序停滞self.t = threading.Thread(target = self.get_img)#将线程设置为后台运行self.t.setDaemon(True)#二维码的网页地址self.imgurl = imgurldef get_img(self):#访问imgurl,将二维码保存到本地res = requests.get(url = self.imgurl,headers = headers)f = open('baidu.jpg','wb')f.write(res.content)f.close()#暂停1秒,不然加载的二维码可能比较模糊time.sleep(1)#Pillow读取二维码img = Image.open('baidu.jpg')#在窗口中显示(此时并没有真的显示,因为子线程还没有启动)img.show()#调用此函数的是python子线程,展示图片的是一个windows进程#使用psutil获取进程句柄,我们可以在合适的时候将该进程关掉for proc in psutil.process_iter():#前者是win10的照片查看器,后者是win7的#如果你的图片默认程序不是照片查看器,那么此处需要修改#获取方式:打开任意一张图片,任务管理器-性能-资源监视器if proc.name() in ['Microsoft.Photos.exe','dllhost.exe']:self.proc = procbreak#启动子线程def show(self):self.t.start()#关闭二维码进程def close(self):self.proc.kill()

获取cookie

class Login(object):#初始化变量def __init__(self,login_params,check_params,show):#Show对象,控制二维码展示和关闭self.show = show#检查二维码扫描状态的URLself.check_url = 'https://passport.baidu.com/channel/unicast'#获取BDUSS等cookie的URLself.login_url = 'https://passport.baidu.com/v3/login/main/qrbdusslogin'#login_url的参数self.login_params = login_params#check_url的参数self.check_params = check_params#检查二维码扫描状态def check_status(self):#启动子线程在窗口展示二维码self.show.show()#将errno和status设定为1errno = 1status = 1#用于在while循环中关闭windows照片查看器flag = True#unicast的response不是标准json格式,需要进行正则匹配pattern = re.compile('({.*})')#开始循环while True:#访问目标地址,将timeout设置为35sreq = requests.get(url = self.check_url,headers = passport_headers,cookies = cookie,params = self.check_params,timeout = 35)#替换返回值中的不合法字符,以便转换为json格式#菜如我只能用三个replace......response = req.text.replace('\\','').replace('"{',"{").replace('}"',"}")try:#如果errno或者status其中一项为1if errno or status:#将response转换为json格式message = json.loads(re.search(pattern,response).group())#更新errnoerrno = message['errno']#如果errno变为0(用户扫描二维码),并且照片查看器未关闭if errno == 0 and flag:#关闭照片查看器,将flag设置为Falseself.show.close()flag = False#如果errno为0,则更新status值elif errno == 0:status = message['channel_v']['status']#如果errno和status都为0,则补充login_params的bduss字段,结束循环if not errno and not status:message = json.loads(re.search(pattern,response).group())self.login_params['bduss'] = message['channel_v']['v']break#更新check_paramas中两个关于时间的参数self.check_params['tt'] = get_time()self.check_params['_'] = self.check_params['tt'] + 2except Exception as e:print(e)break#获取cookiedef login(self):#带上完整参数请求登录地址login_r = requests.get(self.login_url, headers=headers, params=self.login_params,cookies = cookie)#将BDUSS、PTOKEN、STOKEN写入cookiefor key in login_r.cookies.keys():cookie[key] = login_r.cookies[key]print('登陆成功,正在保存cookie')#将cookie保存到本地f = open('auth_cookie.txt','wt')f.write(str(cookie))f.close()print('保存完毕')

完整代码

# -*- coding: utf-8 -*-
"""
Created on Fri Dec 11 08:36:50 2020@author: ljc545w
"""# -*- coding: utf-8 -*-
#用到的包
#注:requests和Pillow为第三方包,需要使用pip命令安装
import requests
#时间管理带师
import time
#获取随机数
import random
#正则、json字典
import re,json
#Pillow
from PIL import Image
#线程
import threading
#进程
import psutil#请求头
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36',}passport_headers = {'Host': 'passport.baidu.com','User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36','Referer': 'https://www.baidu.com/'}
#定义一个空字典用于保存cookie
cookie = {}#获取毫秒级时间戳
def get_time():cur_time = int(round(time.time() * 1000))return cur_time#获取gid
def guideRandom():string = 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'gid = ''#下面是根据JavaScript代码写的for z in string:#获取0到1的随机数,并乘以16t = 16 * random.random()#如果是z是x或者yif z in "xy":#hex将数字转换为十六进制,后面的你肯定能读懂s = hex(int(t)) if z == "x" else hex(3 & int(t) | 8)#将十六进制数转换为字符串,删除前面0xgid += str(s).replace('0x','')else:#不是x或者y则不做处理gid += z#将字符串转换为大写,返回return gid.upper()#获取必要参数
def GetParams():#这个是百度首页Base_url = 'https://www.baidu.com/'#先获取一些固定的cookie,如BAIDUID、PSTM等Base_res = requests.get(url = Base_url,headers = headers)for key in Base_res.cookies.keys():#这里有一定概率引发异常,可能是因为某个cookie为空,预处理一下try:cookie[key] = Base_res.cookies[key]except:pass#用于登录的参数(这里还没有加入bduss)login_params = {#获取时间戳'v': get_time(),'u': 'https://www.baidu.com/','loginVersion': 'v4','qrcode': '1','tpl': 'mn','apiver': 'v3',#获取时间戳'tt': get_time(),'traceid': None,#这里也是时间戳,不过是秒级,所以直接调用time方法,然后取整'time': round(time.time()),'alg':'v3',#下面四个参数虽然会变化,但是写死也没什么影响,实测不写都可以'sig': 'SXFidmJBL3NzS0lNaERoL1pzckVxaFdSS0lrSDQ5SUNPWFdjUDRhVE5rZkVOMEpzN2FpZExBTDRFejEwOFBvdw==','elapsed': 18,'shaOne': '0011f17de7b0a1b99aa70a6f1ea881c14d404379','callback': 'bd__cbs__1sgowp',}#获取二维码地址的urlpass_url = 'https://passport.baidu.com/v2/api/getqrcode?lp=pc&qrloginfrom=pc'#这里返回的是标准json格式,所以可以调用json方法而无需使用text方法response = requests.get(url = pass_url,headers = headers).json()#获取channel_idchannel_id = response['sign']#拼接callbackcallback = "tangram_guid_" + str(get_time() + 1)#延迟0.5s,callback的时间戳与tt的时间戳相差在500左右time.sleep(0.5)#用于检查二维码扫描状态的参数check_params = {"channel_id":channel_id,"tpl":"mn","gid":guideRandom(),"callback":callback,"apiver":"v3",#获取时间戳"tt":get_time(),#加不加2应该都没什么影响"_":get_time() + 2,}#拼接出完整的imgurlimgurl = "https://" + response['imgurl'].replace('\\','')#返回三个变量return imgurl,login_params,check_paramsclass Show(object):def __init__(self,imgurl):self.t = threading.Thread(target = self.get_img)self.t.setDaemon(True)self.imgurl = imgurldef get_img(self):res = requests.get(url = self.imgurl,headers = headers)f = open('baidu.jpg','wb')f.write(res.content)f.close()time.sleep(1)img = Image.open('baidu.jpg')img.show()for proc in psutil.process_iter():if proc.name() in ['Microsoft.Photos.exe','dllhost.exe']:self.proc = procbreakdef show(self):self.t.start()def close(self):self.proc.kill()class Login(object):def __init__(self,login_params,check_params,show):self.show = showself.check_url = 'https://passport.baidu.com/channel/unicast'self.login_url = 'https://passport.baidu.com/v3/login/main/qrbdusslogin'self.login_params = login_paramsself.check_params = check_paramsdef check_status(self):self.show.show()errno = 1status = 1flag = Truepattern = re.compile('({.*})')while True:req = requests.get(url = self.check_url,headers = passport_headers,cookies = cookie,params = self.check_params,timeout = 35)response = req.text.replace('\\','').replace('"{',"{").replace('}"',"}")try:if errno or status:message = json.loads(re.search(pattern,response).group())errno = message['errno']if errno == 0 and flag:self.show.close()flag = Falseelif errno == 0:status = message['channel_v']['status']if not errno and not status:message = json.loads(re.search(pattern,response).group())self.login_params['bduss'] = message['channel_v']['v']breakself.check_params['tt'] = get_time()self.check_params['_'] = self.check_params['tt'] + 2except Exception as e:print(e)breakdef login(self):login_r = requests.get(self.login_url, headers=headers, params=self.login_params,cookies = cookie)for key in login_r.cookies.keys():cookie[key] = login_r.cookies[key]print('登陆成功,正在保存cookie')f = open('auth_cookie.txt','wt')f.write(str(cookie))f.close()print('保存完毕')#如果不想继续使用获取的cookie,可以使用这个函数
def logout():#百度退出登录的urllogout_url = 'https://passport.baidu.com/?logout'#从文本中读取cookief = open('auth_cookie.txt','r',encoding='utf-8')logout_cookie = f.read()f.close()#转换为json格式。注意!!!如果文本中是单引号,要将其替换为双引号logout_cookie = json.loads(logout_cookie.replace("'","\""))#发起post请求,原本的cookie将失去作用requests.post(url = logout_url,headers = headers,cookies = logout_cookie)print('已退出登录')def run():#获取三个重要变量imgurl,login_params,check_params = GetParams()#声明一个对象控制二维码的显示show = Show(imgurl)#将两个参数和show传递给Login类,获取用于控制登录的对象login = Login(login_params,check_params,show)#调用类中的方法,检查二维码扫描状态login.check_status()#调用类中方法获取cookielogin.login()#入口函数
if __name__ == '__main__':#logout()run()

写在后面

花了整整一天,暂时把百度的二维码登录搞明白了,这篇文章中的代码可能不久就会失效,我大概是不会再更新,不,是肯定不会。


这是爬虫小白对二维码登录的一次尝试,如有不足之处,恳请各位大佬批评指正。

Python模拟二维码登录百度相关推荐

  1. 百度网盘PC端扫描二维码登录时无法加载二维码问题解决方法

    问题: 今天在PC端扫描登录百度网盘时,二维码无法加载出来,具体情况如图: 解决方法: 1.打开IE浏览器 2.打开工具 3.打开Internet选项 4.打开高级选项,重置IE设置 5.点击确定,打 ...

  2. Python自动化测试实现二维码登录

    在网上搜了许久,也没有找到合适的绕过二维码登录网页,所以用了下面的笨蛋办法,注释的代码是使用cookie解决但是最终没有实现, 希望厉害的朋友遇见此文章,在评论下如何可以实现绕过二维码进行登录!!! ...

  3. Python解析二维码、条形码

    Python解析二维码.条形码 1 前言 2 二维码知识小科普 2.1 什么是二维码 2.2 二维码的结构 2.3 二维码的绘制过程 3 Python解析二维码 3.1 准备工作: 3.2 pyzba ...

  4. python生成二维码_python生成二维码的实例详解

    python生成二维码的实例详解 版本相关 操作系统:Mac OS X EI Caption Python版本:2.7 IDE:Sublime Text 3 依赖库 Python生成二维码需要的依赖库 ...

  5. java微信二维码登录

    1.注册 微信开放平台:https://open.weixin.qq.com 2.邮箱激活 3.完善开发者资料 4.开发者资质认证 准备营业执照,1-2个工作日审批.300元 5.创建网站应用 提交审 ...

  6. 初学python制作二维码以及最新感悟

    初学python制作二维码 一.安装Python 推荐百度经验链接. 二.安装pip模块 Python 3.4以后版本默认安装了pip,但是由于不是最新故需要升级,pip的升级命令为(开始->c ...

  7. 微信扫描二维码登录第三方平台

    嗯...... 最近做了一个微信扫码登陆第三方平台功能,说下步骤就行,反正原理你们网上直接百度,我这里写了,估计也没几个人有耐心看 第一步 生成一个链接 https://open.weixin.qq. ...

  8. Python制作二维码简易步骤

    附件 Python制作二维码简易步骤 ------------------------------------------- 附:Python制作二维码简易步骤 附:Python爬取整本小说 附:Py ...

  9. java二维码登录实现

    二维码登录原理 让服务端知道是那个用户要登录,验证通过后 服务端通过webscoket 告知 前端 登录成功即可 前端二维码登录实现 链接: 仿知乎pc登录注册二维码登录页面. 下载下来修改一下即可使 ...

最新文章

  1. C++/C union使用记一下锅
  2. 程序员离职原因的最佳回答_程序员面试被问离职原因,如实回答不适应996,面试官答复尴尬了...
  3. HDU - 1024 Max Sum Plus Plus 最大m段子段和+滚动数组优化
  4. array python 交集_Python基础(二)——列表和元组
  5. 机器学习降维算法五——KPCA算法
  6. Spring Boot WebFlux 上手教程
  7. 力压腾讯!《原神》连续5个月成中国手游海外收入冠军
  8. paip.提升用户体验-------在C++ Builder 中为Form窗体添加背景图片
  9. 产品必备-产品FDD模板(PRD)
  10. ELK 收集 Tomcat 日志
  11. 数字图像处理(极简) 第一章 概述(docx)
  12. 爬虫爬取数据时如何快速换IP?极光IP轻松搞定
  13. 超级详细的Maven使用教程
  14. 人体颈椎神经分布图高清,颈椎部神经分布图高清
  15. 计算机上的32位是什么意思啊,解答32位是什么意思
  16. 【色彩管理】色彩管理之灰平衡
  17. python判断手机号运营商_python手机号码运营商归属测试
  18. Vuforia开发问题记录(四)------- Vuforia AR项目在小米8 SE上运行黑屏
  19. iOS —label自动换行
  20. 植物大战僵尸-阳光数目修改及阳光基址

热门文章

  1. Redis设计与实现——对象
  2. 2022-2028年全球与中国饲料核苷酸行业市场需求预测分析
  3. C++对象模型-读书笔记
  4. TreeMap、TreeSet简介
  5. 仿射密码 python实现
  6. ubuntu使用meld/beyond compare 做git的diff工具
  7. R语言 数据正态化+标准化
  8. 回归里出现双峰的解决办法
  9. 【雅思】金山词霸-单词学习(41-80)
  10. JS Date时间各种格式互转