Bootstrap

2021最新 QQqun乘员数据获取项目(包含js逆向)——QQqunSpider [已完结]


前言

最近啃了一个新项目,是关于某群官网的某群成员获取。本项目仅用于交流学习,若侵犯到贵公司权益请联系邮箱[email protected]第一时间删除。读者请切忌用于一切非法途径,否则后果自行承担!


项目背景

前段时间在某外包群看到一个需求:获取QQ上的QQ群成员号码…等。刚好现在有时间就打开了某群官网,进行了一波操作。肝了一个晚上终于是整得明明白白了,于是项目[QQqunSpider]问世了hhh,其中在登陆方面得到了一位大佬的技术指导,很感谢!!!下面是项目目录结构图:
在这里插入图片描述


项目实现

本项目使用requests的方式进行全程获取,登陆采用二维码扫码的方式代码实现,因为目前没找到账密登录的入口,网站好像并没有,所以采用扫码(如果有朋友知道账密登陆的入口也可告知我,我会尽量去更新账密登录方式), 登录获取cookie,为之后的数据采集提供身份验证。本项目实现并不难,从登陆到数据获取入库等流程较为简单。下面正片开始~


1. 登陆

想要获取数据,必须进行登录处理,获取cookie后才能实现完整的获取流程。本次地址:aHR0cHM6Ly9xdW4ucXEuY29tLw==

登陆页面分析

打开首页并点击登录出现如图:
在这里插入图片描述
可以看到没有其他的登陆方式,只能使用app进行扫码登陆。

接下来抓包,直接F12打开开发者工具点击All然后刷新网页点击登陆即可:
在这里插入图片描述
首先看到这一条接口地址,他是一个图片接口(也就是二维码的接口):
在这里插入图片描述
看看他的Headers:
在这里插入图片描述
经过多次重复抓包知道他是一个GET请求,提交的参数没有加密,并且所有参数均可写死。

登陆实现

获取二维码并在本地调用弹出二维码进行扫描登陆

登陆流程
① 获取登陆二维码并保存二维码至本地

现在只需要编写python代码请求二维码接口即可,定义LoginFuncRequests类并初始化参数:

def __init__(self):
    self.session = requests.session()
    self.img_path = 'cookieserverfunction/image/code.png'

    self.headers = {
        'Host': 'ssl.ptlogin2.qq.com',
        'Referer': 'https://xui.ptlogin2.qq.com/',
        'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
    }

编写code_show方法获取二维码并保存:

def code_show(self):
    url = 'https://ssl.ptlogin2.qq.com/ptqrshow?'
    data = {
        'appid': '715030901',
        'e': '2',
        'l': 'M',
        's': '3',
        'd': '72',
        'v': '4',
        't': '0.9292435301685353',
        'daid': '73',
        'pt_3rd_aid': '0',
    }
    response = self.session.get(url, params=data, headers=self.headers)
    with open('cookieserverfunction/image/code.png', 'wb') as f:
        f.write(response.content)
        f.close()
② 本地弹出二维码

这里需要注意的是当二维码获取到并弹出后服务器会定时验证二维码的有效性(包括是否过期,是否扫描,是否确认):
在这里插入图片描述
接口如图:
在这里插入图片描述
响应的内容:
在这里插入图片描述

所以这一步我们需要先处理这条接口请求,验证二维码是否被扫描是否过期或者确认。
先看看他的Headers,以及各个参数情况:
在这里插入图片描述

其中ptqrtoken、login_sig、action是变动的,首先看看ptqrtoken,搜索:
在这里插入图片描述
只有一条记录, 打上断点:
在这里插入图片描述
先看这段代码:
“ptqrlogin” == t && (o.ptqrtoken = k[“default”].str.hash33(k[“default”].cookie.get(“qrsig”)))

大致意思就是ptqrlogin=未知函数(从cookie中获取qrsig)
接下来单步执行进入下一步:
在这里插入图片描述
继续执行到断点处, 并在控制台打印返回值:
在这里插入图片描述
所以只需要知道cookie中的qrsig参数即可, 这里尝试一下搜索t的值进行搜索(因为qrsig=t=hNHL52PPCVBd6O45-6RCIEvRXjzt6ag36EUJKxX-VkkmRTdTJdDMYPTHDR*cUg):在这里插入图片描述
需要注意的是全局搜索的字符串不能含有特殊字符,否则会影响搜索结果,所以这里截取t的部分字符进行搜索:
在这里插入图片描述
很清楚明了的看到:t是二维码接口返回的cookie值;
所以我们只需要在获取二维码的时候将cookie返回获取下来即可:

def code_show(self):
    url = 'https://ssl.ptlogin2.qq.com/ptqrshow?'
    data = {
        'appid': '715030901',
        'e': '2',
        'l': 'M',
        's': '3',
        'd': '72',
        'v': '4',
        't': '0.9292435301685353',
        'daid': '73',
        'pt_3rd_aid': '0',
    }
    response = self.session.get(url, params=data, headers=self.headers)
    with open('cookieserverfunction/image/code.png', 'wb') as f:
        f.write(response.content)
        f.close()
    print(response.cookies)
    return response.cookies

接下来将js扣出改写,并编写get_ptqrtoken函数获取ptqrtoken参数:
js代码:

function ptqrtoken(t) {
    for (var e = 0, i = 0, n = t.length; i < n; ++i) e += (e << 5) + t.charCodeAt(i);
    return 2147483647 & e
}

get_ptqrtoken函数:

    def get_ptqrtoken(self, cookies):
        with open('lib/bkn.js', 'r') as f:
            js = f.read()
            f.close()
        com = execjs.compile(js)
        ptqrtoken = com.call('ptqrtoken', re.search(r'qrsig=(.*?) ', str(cookies)).group(1))
        print('ptqrtoken:', ptqrtoken)
        return ptqrtoken

现在看看action参数:action参数(0-0-1614435984861)类似于x-x-13位时间戳(先假设是这样等下请求验证便知)

接下来看看login_sig参数:在这里插入图片描述
这里清楚login_sig=S.ptui.login_sig, 我们搜索S.ptui.login_sig:
在这里插入图片描述

打下断点并刷新网页再次点击登录:
在这里插入图片描述

通过控制台输出,以及代码段:S.ptui.login_sig = S.ptui.login_sig || k[“default”].cookie.get(“pt_login_sig”)可以清楚的知道S.ptui.login_sig等于cookie中的pt_login_sig值, 接下来进行pt_login_sig值得搜索:
在这里插入图片描述
经过多次观察Headers,提交的参数为固定值在这里插入图片描述
编写pt_login_sig函数代码获取cookie即可:

def pt_login_sig(self):
    url = 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?'
    data = {
        'pt_disable_pwd': '1',
        'appid': '715030901',
        'daid': '73',
        'pt_no_auth': '1',
        's_url': 'https://qun.qq.com/',
    }
    headers = {
        'referer': 'https://qun.qq.com/',
        'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
    }
    response = self.session.get(url, params=data, headers=headers)
    print(response.cookies)
    login_sig = re.search(r'pt_login_sig=(.*?) ', str(response.cookies)).group(1)
    print('login_sig:', login_sig)
    return login_sig, response.cookies

所有参数获取完成,下面编写check_Codeshow函数向服务器发送请求:

def check_Codeshow(self, ptqrtoken, login_sig, ck):
    url = 'https://ssl.ptlogin2.qq.com/ptqrlogin?'
    action = int(time.time() * 1000)
    data = {
        'u1': 'https://qun.qq.com/',
        'ptqrtoken': ptqrtoken,
        'ptredirect': '1',
        'h': '1',
        't': '1',
        'g': '1',
        'from_ui': '1',
        'ptlang': '2052',
        'action': '0-0-' + str(action),
        'js_ver': '21020514',
        'js_type': '1',
        'login_sig': login_sig,
        'pt_uistyle': '40',
        'aid': '715030901',
        'daid': '73',
    }
    headers = {
        'referer': 'https://xui.ptlogin2.qq.com/',
        'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
    }
    response = self.session.get(url, params=data, headers=headers, cookies=ck)
    print(response.text)
    return response.text

将图片显示(showc函数)结合check_Codeshow函数:

def showc(self, ptqrtoken, login_sig, ck):
     # 显示二维码
     img = Image.open(self.img_path)  # 打开图片,返回PIL image对象
     plt.figure("请使用app扫描验证码", figsize=(5, 4))
     plt.ion()  # 打开交互模式
     plt.axis('off')  # 不需要坐标轴
     plt.imshow(img)
     global pausing
     pausing = True
     while pausing:
         plt.pause(0.05)
         while True:
             bg = self.check_Codeshow(ptqrtoken, login_sig, ck)
             if '登录成功' in str(bg):
                 pausing = False
                 ebg = 1
                 break
             if '二维码认证中' in str(bg):
                 pass
             if '二维码已失效' in str(bg):
                 pausing = False
                 ebg = 0
                 break
             if '本次登录已被拒绝' in str(bg):
                 pausing = False
                 ebg = 0
                 break
             time.sleep(1)
     plt.ioff()  # 显示完后一定要配合使用plt.ioff()关闭交互模式,否则可能出奇怪的问题
     plt.clf()  # 清空图片
     plt.close()  # 清空窗口
     return ebg, bg
③ app扫码确认——登陆成功

然后我们使用app扫码确认,再次抓包分析:
在这里插入图片描述
当我们点击确认之后紧接着服务器请求了一条接口:
在这里插入图片描述
并且设置了一堆cookie:
在这里插入图片描述
所以现在需要解决这条接口的参数,并进行模拟请求,先看看请求表单:
在这里插入图片描述
我设想,会不会是上面的check_Codeshow函数想服务器发送请求的时候,在我们确认之后请求响应返回的?带着设想查看了response:
在这里插入图片描述
并没有…然后我通过代码进行debug调试:
在这里插入图片描述

发现了有返回值,并且其中的链接就是上面的接口地址,所以现在只需要将
check_Codeshow函数的返回值稍作处理提取链接,然后再进行求情获取登陆cookie即可。

get_cookie函数:

    def get_cookie(self, url):
        headers = {
            'referer': 'https://xui.ptlogin2.qq.com/',
            'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
        }
        response = self.session.get(url, headers=headers, allow_redirects=False)
        print(response.cookies)
        return response.cookies

整合代码:

# -- coding: utf-8 --
# @Time : 2021/2/25 20:25
# @Author : Los Angeles Clippers
# @Email: [email protected]
# @sinaemail: [email protected]
import pickle
import re
import time

import execjs
import requests
from PIL import Image
import matplotlib.pyplot as plt


class LoginFuncRequests(object):
    def __init__(self):
        self.session = requests.session()
        self.img_path = 'cookieserverfunction/image/code.png'

        self.headers = {
            'Host': 'ssl.ptlogin2.qq.com',
            'Referer': 'https://xui.ptlogin2.qq.com/',
            'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
        }

    def code_show(self):
        url = 'https://ssl.ptlogin2.qq.com/ptqrshow?'
        data = {
            'appid': '715030901',
            'e': '2',
            'l': 'M',
            's': '3',
            'd': '72',
            'v': '4',
            't': '0.9292435301685353',
            'daid': '73',
            'pt_3rd_aid': '0',
        }
        response = self.session.get(url, params=data, headers=self.headers)
        print(response.status_code)
        with open('cookieserverfunction/image/code.png', 'wb') as f:
            f.write(response.content)
            f.close()
        print(response.cookies)
        return response.cookies

    def get_ptqrtoken(self, cookies):
        with open('lib/bkn.js', 'r') as f:
            js = f.read()
            f.close()
        com = execjs.compile(js)
        ptqrtoken = com.call('ptqrtoken', re.search(r'qrsig=(.*?) ', str(cookies)).group(1))
        print('ptqrtoken:', ptqrtoken)
        return ptqrtoken

    def pt_login_sig(self):
        url = 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?'
        data = {
            'pt_disable_pwd': '1',
            'appid': '715030901',
            'daid': '73',
            'pt_no_auth': '1',
            's_url': 'https://qun.qq.com/',
        }
        headers = {
            'referer': 'https://qun.qq.com/',
            'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
        }
        response = self.session.get(url, params=data, headers=headers)
        print(response.cookies)
        login_sig = re.search(r'pt_login_sig=(.*?) ', str(response.cookies)).group(1)
        print('login_sig:', login_sig)
        return login_sig, response.cookies

    def check_Codeshow(self, ptqrtoken, login_sig, ck):
        url = 'https://ssl.ptlogin2.qq.com/ptqrlogin?'
        action = int(time.time() * 1000)
        data = {
            'u1': 'https://qun.qq.com/',
            'ptqrtoken': ptqrtoken,
            'ptredirect': '1',
            'h': '1',
            't': '1',
            'g': '1',
            'from_ui': '1',
            'ptlang': '2052',
            'action': '0-0-' + str(action),
            'js_ver': '21020514',
            'js_type': '1',
            'login_sig': login_sig,
            'pt_uistyle': '40',
            'aid': '715030901',
            'daid': '73',
        }
        headers = {
            'referer': 'https://xui.ptlogin2.qq.com/',
            'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
        }
        response = self.session.get(url, params=data, headers=headers, cookies=ck)
        print(response.text)
        # print(response.cookies)
        return response.text

    def showc(self, ptqrtoken, login_sig, ck):
        # 显示二维码
        img = Image.open(self.img_path)  # 打开图片,返回PIL image对象
        plt.figure("请使用app扫描验证码", figsize=(5, 4))
        plt.ion()  # 打开交互模式
        plt.axis('off')  # 不需要坐标轴
        plt.imshow(img)
        global pausing
        pausing = True
        while pausing:
            plt.pause(0.05)
            while True:
                bg = self.check_Codeshow(ptqrtoken, login_sig, ck)
                if '登录成功' in str(bg):
                    pausing = False
                    ebg = 1
                    break
                if '二维码认证中' in str(bg):
                    pass
                if '二维码已失效' in str(bg):
                    pausing = False
                    ebg = 0
                    break
                if '本次登录已被拒绝' in str(bg):
                    pausing = False
                    ebg = 0
                    break
                time.sleep(1)
        plt.ioff()  # 显示完后一定要配合使用plt.ioff()关闭交互模式,否则可能出奇怪的问题
        plt.clf()  # 清空图片
        plt.close()  # 清空窗口
        return ebg, bg

    def get_cookie(self, url):
        headers = {
            'referer': 'https://xui.ptlogin2.qq.com/',
            'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
        }
        response = self.session.get(url, headers=headers, allow_redirects=False)
        print(response.cookies)
        return response.cookies

    def run(self):
        cookies = self.code_show()
        ptqrtoken = self.get_ptqrtoken(cookies=cookies)
        login_sig, ck = self.pt_login_sig()
        ebg, bg = self.showc(ptqrtoken, login_sig, ck)
        if ebg == 0:
            self.run()
        else:
            url = re.search(r"ptuiCB\('0','0','(.*?)'", bg).group(1)
            print(url)
            lck = self.get_cookie(url=url)
            cookie_name = re.search(r'uin=(.*?)&', bg).group(1)
            with open("cookied/{}_cookie.txt".format(cookie_name), 'w') as f:
                f.write(str(lck))
            return lck, cookie_name

获取cookie结果:
在这里插入图片描述
到这里登陆成功,并且成功获取cookie。

2. 抓取分析

点击群管理 →成员管理 ,到以下界面:
在这里插入图片描述

接口参数分析

F12打开开发者工具,随便点击一个群进行抓包:
在这里插入图片描述
看看响应:
在这里插入图片描述
所以这里就是我们想要的信息,看看表单参数:
在这里插入图片描述

相关js逆向

这里的gc是群号码,bkn不知道,先搜索一波:
在这里插入图片描述
打下断点,并刷新网页:
在这里插入图片描述
通过控制台打印,他就是bkn参数,进入函数看看:
在这里插入图片描述
打下断点后我们再执行到断点处:
在这里插入图片描述
通过控制台打印,这段js代码即是bkn的算法:

o.getCSRFToken = function() {
        var e = o.cookie("skey");
        if (e) {
            for (var t = 5381, n = 0, r = e.length; n < r; ++n)
                t += (t << 5) + e.charAt(n).charCodeAt();
            return this.CSRFToken = 2147483647 & t
        }
    }

通过var e = o.cookie(“skey”)可知, e等于cookie中的skey的值,所以在这里将cookie的skey值取出,传入js函数即可得到bkn的值,编写getbkn函数:

def getbkn(self, skey):
    with open('lib/bkn.js', 'r') as f:
        js = f.read()
        f.close()
    com = execjs.compile(js)
    bkn = com.call('getbkn', skey)
    print(bkn)
    return bkn

数据抓取-数据保存

可以看到我的qq上有很多个群,所以如果是一个一个qq群号码去写的话很麻烦,所以看看服务器有没有返回我qq上的所有qq群,随意找个群号进行搜索:
在这里插入图片描述
有结果了,看看Headers:
在这里插入图片描述
只有一个bkn参数,接下来编写get_gc函数获取所有的群号码列表:

def get_gc(self, cookies, bkn):
    data = {
        'bkn': bkn
    }
    response = requests.post(self.url, headers=self.headers, data=data, cookies=cookies)
    jsdata = json.loads(response.text)
    print(jsdata)
    gc = re.findall(r"'gc': (.*?),", str(jsdata))
    gn = re.findall(r"'gn': '(.*?),", str(jsdata))
    print(gc)
    print(gn)
    print(len(gc), len(gn))
    return (gc, gn)

加下来定义SearchGroupMembers类进行数据的获取:
首先定义初始化参数:

def __init__(self, gcgn, bkn, cookies):
    self.url = 'https://qun.qq.com/cgi-bin/qun_mgr/search_group_members'
    self.bkn = bkn
    self.gc = gcgn[0]
    self.gn = gcgn[1]
    self.cookies = cookies

    self.fp = open('datas/qq.csv', 'a', encoding='gb18030', newline='')
    self.fw = csv.writer(self.fp)

    self.fps = open('datas/qq_email.csv', 'a', encoding='gb18030', newline='')
    self.fws = csv.writer(self.fps)

    self.headers = {
        'origin': 'https://qun.qq.com',
        'pragma': 'no-cache',
        'referer': 'https://qun.qq.com/member.html',
        'sec-ch-ua': '"Chromium";v="88", "Google Chrome";v="88", ";Not A Brand";v="99"',
        'sec-ch-ua-mobile': '?0',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36',
    }

下面编写获取数据函数getMembers:

def getMembers(self):
    o = 0
    for gc, gn in zip(self.gc, self.gn):
        print(gn)
        i = 0
        for pg in range(0, 1000000, 21):
            data = {
                'gc': gc,
                'st': pg,
                'end': 20 + pg,
                'sort': '0',
                'bkn': self.bkn,
            }
            response = requests.post(self.url, headers=self.headers, cookies=self.cookies, data=data)
            jsdata = json.loads(response.text)
            try:
                mems = jsdata['mems']
            except KeyError as e:
                break
            if len(mems) == 0:
                break
            for item in mems:
                i += 1
                o += 1
                qq = item['uin']
                qq_email = str(qq) + '@qq.com'
                nick = item['nick']
                print(i, o, qq, nick)
                self.fw.writerow([qq, nick])
                self.fws.writerow([str(qq), qq_email, nick])

    self.fp.close()
    self.fps.close()

启动函数:

from cookieserverfunction.logincodeshow import LoginFuncRequests
from CrawlSpider.get_group_list import GetGroupList
from CrawlSpider.search_group_members import SearchGroupMembers


if __name__ == '__main__':
    logins = LoginFuncRequests()
    lck, cookie_name = logins.run()
    ggl = GetGroupList()
    gcgn, bkn, cookies = ggl.run(lck, cookie_name)
    sgm = SearchGroupMembers(gcgn, bkn, cookies)
    sgm.getMembers()

执行结果:
在这里插入图片描述
数据保存情况:
在这里插入图片描述
挺意外我群友居然有将近5w个的hhhhh~

总结

好好学习 天天向上 不掉头发 事业有成~


码字不易,如果本篇文章对你有帮助请点个赞,谢谢~
合作及源码获取vx:tiebanggg 【注明来意】
QQ交流群:735418202
需源码请关注微信公众号回复【QQqunSpider】获取 :在这里插入图片描述

*注:本文为原创文章,转载文章请附上本文链接!否则将追究相关责任,请自重!谢谢!

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;