文章目录
前言
最近啃了一个新项目,是关于某群官网的某群成员获取。本项目仅用于交流学习,若侵犯到贵公司权益请联系邮箱[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】获取 :
*注:本文为原创文章,转载文章请附上本文链接!否则将追究相关责任,请自重!谢谢!