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.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
How to Build a Local QR‑Code Login Scraper for QQ Music with Python

授人以鱼不如授人以渔

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonQR codeqq musicWeb ScrapingrequestsLogin Automation
MaGe Linux Operations
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.