分析
一般地,QQ空间可以通过手机QQ扫码登录和账号密码登录。但是账号密码登录有时候需要验证码,为了保证登录的成功率,我们选择扫码登录的方式。
首先,进入登录界面:
https://xui.ptlogin2.qq.com/cgi-bin/xlogin?proxy_url=https://qzs.qq.com/qzone/v6/portal/proxy.html&daid=5&&hide_title_bar=1&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&appid=549000912&style=22&target=self&s_url=https://qzs.qq.com/qzone/v5/loginsucc.html?para=izone&pt_qr_app=手机QQ空间&pt_qr_link=https://z.qzone.com/download.html&self_regurl=https://qzs.qq.com/qzone/v6/reg/index.html&pt_qr_help_link=https://z.qzone.com/download.html&pt_no_auth=0
简单抓包可以发现二维码登录的接口很可能是这个(ptqrlogin看着就像是二维码登录):
看下请求这个链接需要哪些参数吧:
测试一下,可以发现大部分参数是固定的,我们只需要知道以下参数就行啦:
action
login_sig
ptqrtoken
action
很显然,action的构造方式应该是这样的:
\'0-0-\'+时间戳
login_sig
好像好几个url返回的都cookie都含有login_sig参数,选一个简单点的 https://xui.ptlogin2.qq.com/cgi-bin/xlogin?(因为它的参数都是固定的)
ptqrtoken
全局搜索一下,可以发现ptqrtoken这个参数在某个js文件里写了计算方式:
可见 ptqrtoken=hash33(qrsig),在全局搜索一下,发现hash33的定义如下:
function hash33(t) { for (var e = 0, i = 0, n = t.length; i < n; ++i) e += (e << 5) + t.charCodeAt(i); return 2147483647 & e }
转为等价python代码就是:
def decryptQrsig(qrsig): e = 0 for c in qrsig: e += (e << 5) + ord(c) return 2147483647 & e
那么,现在的问题就是qrsig这个参数如何获得呢?和login_sig参数类似,容易发现请求如下链接,在返回的cookies里可以得到qrsig这个参数的值:
它的参数也比较简单,除了t其他参数都是不变的,感觉t像个随机数,就当是个随机数呗(反正位数也都是16位)。
至此,3个最重要的参数构造方式都得到了。
代码
import requests import time import random import os import sys import re import warnings warnings.filterwarnings(\'ignore\') xlogin_url = \'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?\' qrshow_url = \'https://ssl.ptlogin2.qq.com/ptqrshow?\' qrlogin_url = \'https://ssl.ptlogin2.qq.com/ptqrlogin?\' headers = { \'User-Agent\': \'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\' } session = requests.Session() cur_path = os.getcwd() def hash33(qrsig): e = 0 for c in qrsig: e += (e << 5) + ord(c) return 2147483647 & e # 保存图片 def saveImage(img, img_path): if os.path.isfile(img_path): os.remove(img_path) with open(img_path, \'wb\') as f: f.write(img) f.close() # 展示图片 def showImage(img_path): if sys.platform.find(\'darwin\') >= 0: subprocess.call([\'open\', img_path]) elif sys.platform.find(\'linux\') >= 0: subprocess.call([\'xdg-open\', img_path]) else: os.startfile(img_path) return True # 获取pt_login_sig params = { \'proxy_url\': \'https://qzs.qq.com/qzone/v6/portal/proxy.html\', \'daid\': \'5\', \'hide_title_bar\': \'1\', \'low_login\': \'0\', \'qlogin_auto_login\': \'1\', \'no_verifyimg\': \'1\', \'link_target\': \'blank\', \'appid\': \'549000912\', \'style\': \'22\', \'target\': \'self\', \'s_url\': \'https://qzs.qq.com/qzone/v5/loginsucc.html?para=izone\', \'pt_qr_app\': \'手机QQ空间\', \'pt_qr_link\': \'https://z.qzone.com/download.html\', \'self_regurl\': \'https://qzs.qq.com/qzone/v6/reg/index.html\', \'pt_qr_help_link\': \'https://z.qzone.com/download.html\', \'pt_no_auth\': \'0\' } all_cookies = {} res = session.get(xlogin_url, headers=headers, verify=False, params=params) all_cookies.update(requests.utils.dict_from_cookiejar(res.cookies)) pt_login_sig = all_cookies[\'pt_login_sig\'] print(pt_login_sig) # 获取ptqrtoken params = { \'appid\': \'549000912\', \'e\': \'2\', \'l\': \'M\', \'s\': \'3\', \'d\': \'72\', \'v\': \'4\', \'t\': str(random.random()), \'daid\': \'5\', \'pt_3rd_aid\': \'0\' } res = session.get(qrshow_url, headers=headers, verify=False, params=params) all_cookies.update(requests.utils.dict_from_cookiejar(res.cookies)) ptqrtoken = hash33(all_cookies[\'qrsig\']) print(ptqrtoken) # 保存验证码图片 saveImage(res.content, os.path.join(cur_path, \'qrcode.jpg\')) showImage(os.path.join(cur_path, \'qrcode.jpg\')) session.cookies.update(all_cookies) # 检测二维码状态 while True: params = { \'u1\': \'https://qzs.qq.com/qzone/v5/loginsucc.html?para=izone\', \'ptqrtoken\': ptqrtoken, \'ptredirect\': \'0\', \'h\': \'1\', \'t\': \'1\', \'g\': \'1\', \'from_ui\': \'1\', \'ptlang\': \'2052\', \'action\': \'0-0-\' + str(int(time.time())), \'js_ver\': \'20010217\', \'js_type\': \'1\', \'login_sig\': pt_login_sig, \'pt_uistyle\': \'40\', \'aid\': \'549000912\', \'daid\': \'5\' } res = session.get(qrlogin_url, headers=headers, verify=False, params=params) print(res.text) if \'二维码未失效\' in res.text: break elif \'二维码已经失效\' in res.text: raise RuntimeError(\'Fail to login, qrcode has expired...\') time.sleep(2)
参考链接:
1. https://zhuanlan.zhihu.com/p/95888605
2. https://github.com/CharlesPikachu/DecryptLogin/blob/master/DecryptLogin/platforms/QQZone.py