How to Build a Local QR‑Code Login Scraper for QQ Music with Python
This tutorial walks through creating a Python‑based local QR‑code login scraper for QQ Music, covering the extraction of dynamic parameters, handling of encrypted cookies, displaying and removing QR images, and ultimately obtaining a usable session for further automation.
授人以鱼不如授人以渔
There are countless web‑scraping tutorials, yet many miss the core techniques. This article presents a practical example: a local QR‑code login scraper for QQ Music that obtains the session by fetching, displaying, and deleting the QR code.
开始实战
准备工作
Goal: save the QQ Music login QR code locally, display it, and after a successful login delete the image while preserving the login information.
We first implement three helper functions: showImage to open the image on different OSes, removeImage to close and delete the image, and saveImage to write the binary data to a file.
import sys
import os
import subprocess
def showImage(img_path):
try:
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)
except:
from PIL import Image
img = Image.open(img_path)
img.show()
img.close()
def removeImage(img_path):
if sys.platform.find('darwin') >= 0:
os.system("osascript -e 'quit app \"Preview\"'")
os.remove(img_path)
def saveImage(img, img_path):
if os.path.isfile(img_path):
os.remove(img_path)
fp = open(img_path, 'wb')
fp.write(img)
fp.close()抓取二维码下载链接
Open QQ Music in a browser, press F12, click the login button and inspect the network panel. The QR code is fetched via a GET request to https://ssl.ptlogin2.qq.com/ptqrshow with parameters such as appid=716027609, e=2, l=M, s=3, d=72, v=4, and a random t value.
Repeated refreshes show that only the t parameter changes; it can be generated in Python with random.random().
params = {
'appid': '716027609',
'e': '2',
'l': 'M',
's': '3',
'd': '72',
'v': '4',
't': str(random.random()),
'daid': '383',
'pt_3rd_aid': '100497308'
}
response = session.get('https://ssl.ptlogin2.qq.com/ptqrshow', params=params)
saveImage(response.content, os.path.join(os.getcwd(), 'qrcode.jpg'))
showImage(os.path.join(os.getcwd(), 'qrcode.jpg'))登陆抓包准备
To monitor the login flow, clear previous packets in the network panel and keep the capture active. The crucial login request is sent to https://ssl.ptlogin2.qq.com/ptqrlogin with many parameters, among which ptqrtoken, action, and login_sig vary each attempt.
params = {
'u1': 'https://graph.qq.com/oauth2.0/login_jump',
'ptqrtoken': ptqrtoken,
'ptredirect': '0',
'h': '1',
't': '1',
'g': '1',
'from_ui': '1',
'ptlang': '2052',
'action': '0-0-%s' % int(time.time()*1000),
'js_ver': '20102616',
'js_type': '1',
'login_sig': pt_login_sig,
'pt_uistyle': '40',
'aid': '716027609',
'daid': '383',
'pt_3rd_aid': '100497308',
'has_onekey': '1'
}
response = session.get('https://ssl.ptlogin2.qq.com/ptqrlogin', params=params)棘手的可变加密参数
第一个参数
From the developer tools we discover that ptqrtoken is derived from the cookie qrsig using a hash33 algorithm.
function hash33(str) {
var hash = 0;
for (var i = 0, length = str.length; i < length; ++i) {
hash += (hash << 5) + str.charCodeAt(i);
}
return hash & 2147483647;
}
params.ptqrtoken = $.str.hash33($.cookie.get('qrsig'));In Python we implement the same logic:
def __decryptQrsig(self, qrsig):
e = 0
for c in qrsig:
e += (e << 5) + ord(c)
return 2147483647 & e第二个参数 1.获取
After the QR code request the cookie qrsig is available. Using session.cookies.get('qrsig') we retrieve it and then compute ptqrtoken with the function above.
第二个参数 hash33 加密
The hash33 implementation is identical to the JavaScript version shown earlier.
全部代码
import os, sys, time, subprocess, random, re, requests
from PIL import Image
def showImage(img_path):
try:
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)
except:
img = Image.open(img_path)
img.show()
img.close()
def removeImage(img_path):
if sys.platform.find('darwin') >= 0:
os.system("osascript -e 'quit app \"Preview\"'")
os.remove(img_path)
def saveImage(img, img_path):
if os.path.isfile(img_path):
os.remove(img_path)
fp = open(img_path, 'wb')
fp.write(img)
fp.close()
class qqmusicScanqr:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
self.info = 'login in qqmusic in scanqr mode'
self.cur_path = os.getcwd()
self.session = requests.Session()
self.__initialize()
def login(self, username='', password='', crack_captcha_func=None, **kwargs):
self.session.proxies.update(kwargs.get('proxies', {}))
# get pt_login_sig
params = {
'appid': '716027609',
'daid': '383',
'style': '33',
'login_text': '授权并登录',
'hide_title_bar': '1',
'hide_border': '1',
'target': 'self',
's_url': 'https://graph.qq.com/oauth2.0/login_jump',
'pt_3rd_aid': '100497308',
'pt_feedback_link': 'https://support.qq.com/products/77942?customInfo=.appid100497308'
}
response = self.session.get(self.xlogin_url, params=params)
pt_login_sig = self.session.cookies.get('pt_login_sig')
# get QR code
params = {
'appid': '716027609',
'e': '2',
'l': 'M',
's': '3',
'd': '72',
'v': '4',
't': str(random.random()),
'daid': '383',
'pt_3rd_aid': '100497308'
}
response = self.session.get(self.ptqrshow_url, params=params)
saveImage(response.content, os.path.join(self.cur_path, 'qrcode.jpg'))
showImage(os.path.join(self.cur_path, 'qrcode.jpg'))
qrsig = self.session.cookies.get('qrsig')
ptqrtoken = self.__decryptQrsig(qrsig)
# poll login status
while True:
params = {
'u1': 'https://graph.qq.com/oauth2.0/login_jump',
'ptqrtoken': ptqrtoken,
'ptredirect': '0',
'h': '1',
't': '1',
'g': '1',
'from_ui': '1',
'ptlang': '2052',
'action': '0-0-%s' % int(time.time()*1000),
'js_ver': '20102616',
'js_type': '1',
'login_sig': pt_login_sig,
'pt_uistyle': '40',
'aid': '716027609',
'daid': '383',
'pt_3rd_aid': '100497308',
'has_onekey': '1'
}
response = self.session.get(self.ptqrlogin_url, params=params)
if '二维码未失效' in response.text or '二维码认证中' in response.text:
pass
elif '二维码已经失效' in response.text:
raise RuntimeError('Fail to login, qrcode has expired')
else:
break
time.sleep(0.5)
removeImage(os.path.join(self.cur_path, 'qrcode.jpg'))
qq_number = re.findall(r'&uin=(.+?)&service', response.text)[0]
url_refresh = re.findall(r"'(https:.*?)'", response.text)[0]
self.session.get(url_refresh, allow_redirects=False, verify=False)
print('账号「%s」登陆成功' % qq_number)
return self.session
def __decryptQrsig(self, qrsig):
e = 0
for c in qrsig:
e += (e << 5) + ord(c)
return 2147483647 & e
def __initialize(self):
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36'
}
self.ptqrshow_url = 'https://ssl.ptlogin2.qq.com/ptqrshow?'
self.xlogin_url = 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?'
self.ptqrlogin_url = 'https://ssl.ptlogin2.qq.com/ptqrlogin?'
self.session.headers.update(self.headers)
qq_login = qqmusicScanqr()
session = qq_login.login()Running the script displays the QR code, waits for the user to scan it, then removes the image and prints the logged‑in QQ number. The returned session can be used for further QQ Music API requests.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
