分析
一般地,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