QQ号买卖这条灰色产业链,比大多数人想象的要成熟。你随便搜一下,就能找到成堆的“出号”广告,价格从几毛到几十块不等。但真正懂行的买家不会盲目扫货——他们最怕买到一堆连验证都过不了的僵尸号。
为什么卖号团伙盯上QQ群发言频率
批量注册的QQ号有个致命伤:除了注册当天登陆过一次,之后几乎零互动。腾讯的风控模型早就不只看登录IP和设备指纹了,账号的社交活跃度才是关键权重。一个号加进十个群,半年没说过一句话,跟一个每天在群里吹水、斗图、发早安的家伙,后者在风控系统里的“信任积分”高出好几个量级。
卖号团伙要的不是号,是能发包、能过验证、能扛举报的高活性载体。发言频率就是最直接的过滤器。翻群成员列表手动点?2000人的大群,翻完眼都花了,你根本量化不了谁在真活跃谁在潜水。爬虫一扫就清楚——最近7天发言超过50条的,直接标记为“可用”,剩下的全是废号。
所以你会发现,那些批量发包的号,往往是某个热门游戏群或福利群里冒泡最勤的那几个。不是巧合,是筛过的。
要把群里的“活人”筛出来,先得把人捞出来。别想着手工翻页,一屏一屏复制的时代早该过去了。我们直接让脚本接管浏览器:打开QQ客户端或者Web QQ,拉取群成员列表,再把关键字段一行行钉进CSV里。
爬虫脚本第一步:批量导出QQ群成员数据
工具怎么选才不白忙
Python 依旧是首选,理由很实际:库多、示例多、上手快。市面上两条路:一是 PyQQ(基于 ntkernel 的 TLV 封装),走的是本地协议,稳但更新慢;二是 itchat 适配 Web 端,扫码就能登,开发效率高,碰到新版本就得多试几次。我的取舍是:长期挂机用 PyQQ,临时抓一次用 itchat-uos 分支,省掉很多莫名其妙的报错。
登录这件事,还是扫码最省心
别去硬磕账号密码,腾讯的风控不吃那一套。脚本启动后生成二维码,手机确认登录,保持 session 即可。一旦出现“授权过期”,重刷二维码比任何黑科技都好使。登录成功后,先把群列表拿到手:返回的 JSON 里有 GroupName、GroupId、MemberList 三个关键字段,遍历一遍就知道你进了哪些群。这里有个小坑:新加入的群默认不回显完整成员,需要单独调用 get_group_member_list 并传入 GroupId 才能拉全量。
导出来的表,到底该放哪些列
- QQ号:唯一标识,整数型,保留纯数字方便后续匹配
- 昵称:原样保存,防止同名冲突
- 最近发言时间:转成 unix 时间戳,方便按天/周过滤
- 发言次数:统计周期内的消息计数,用于排序
字段定好,后面清洗才不会返工。脚本跑完,一个几千行的 CSV 到手,下一步才是真正见功夫的地方——从里面挑出能发包的那批“活跃号”。
发言频率分析:从原始数据到活跃度评分
CSV 到手了。几千行,QQ 号、昵称、最近发言时间、发言次数,白纸黑字。但别急着兴奋——你看到的是“原始数据”,不是“活跃度”。直接拿这个表去发包,跟闭着眼在群里随机点人没区别。系统消息、撤回通知、红包领取提醒,这些东西能把发言次数刷上去,但刷的全是垃圾。
清洗:把噪声一脚踢开
先看发言内容字段。PyQQ 拉回来的消息,每条都带 MsgType——1 是普通文本,别的多数是系统事件。我写过滤的时候直接一刀切:只保留 MsgType == 1 的条目。
还有个坑:撤回消息。QQ 群里有人撤回了,服务器会下发一条“xxx 撤回了一条消息”,这玩意也算一条记录。但你真以为撤回的人算活跃?算了吧,那是手滑。
所以我一般在清洗层加个黑名单正则:re.compile(r'(撤回|红包|签到|拍了拍)'),命中的直接抛弃。别嫌粗暴,卖号团伙要的是能稳定发广告的号,不是抢红包手速侠。
import pandas as pd
import re
df = pd.read_csv('qq_members.csv')
# 只保留普通文本消息
df = df[df['msg_type'] == 1]
# 过滤系统关键字
noise_pattern = re.compile(r'(撤回|红包|签到|拍了拍)')
df = df[~df['content'].str.contains(noise_pattern, na=False)]
窗口期:7 天,不多不少
清洗完了,现在看时间。你拉到的“最近发言时间”可能是昨天,也可能是三个月前。拿全量历史去算活跃度?那叫自欺欺人——有人三年前在群里吹过水,之后就再没冒过泡。
我倾向用最近 7 天做窗口。为什么不是 3 天?3 天太短,周末两天不说话的人太多,容易把正常用户误判成僵尸。为什么不是 30 天?30 天太长,那些每隔两周冒一句“有人吗”的号,算活跃你亏不亏。
7 天,刚好卡在“经常说话”和“偶尔冒泡”的分界线上。实现也很直白:把每条消息的时间戳转成 datetime,然后筛出 时间戳 >= now - timedelta(days=7) 的记录,按 QQ 号分组计数。
from datetime import datetime, timedelta
cutoff = datetime.now() - timedelta(days=7)
df['ts'] = pd.to_datetime(df['timestamp'], unit='s')
df_7d = df[df['ts'] >= cutoff]
freq = df_7d.groupby('qq')['content'].count().reset_index()
freq.columns = ['qq', 'msg_count_7d']
跑完这段,每个 QQ 号后面跟的数字,才是真刀真枪的发言量。
阈值:三档分类,简单粗暴
数据到手了,怎么定档?我见过有人搞得很复杂——加权平均、时间衰减、指数平滑……最后算出来的分数自己都解释不了。卖号团伙没那么闲。他们只需要知道:这个号今天能不能发出去。
我的阈值规则写在注释里,比代码还短:
- 日均 ≥ 3 次(即 7 天 ≥ 21 次):高活跃,直接进发包池
- 日均 1-2 次(7 天 7-20 次):中等,需要再观察几天,或者拿来发低频广告
- 日均 0 次(7 天 ≤ 6 次):僵尸,从名单里划掉,别浪费时间
def label_active(row):
if row['msg_count_7d'] >= 21:
return 'high'
elif row['msg_count_7d'] >= 7:
return 'medium'
else:
return 'dead'
freq['level'] = freq.apply(label_active, axis=1)
别问我 21 这个数字怎么来的。试出来的。你换个群、换批号,阈值可以微调,但逻辑就这么回事——发言频率是硬通货,一天说不到三句话的号,在腾讯的风控眼里跟没注册一样。
过滤完,高活跃的号可能只剩原来的 10%-20%。但那又怎样?剩下的每一个,都是实打实能过验证的活载体。
阈值这玩意儿,说简单也简单,说坑爹也真坑爹。前面那套按天计数的逻辑跑起来挺顺,直到我把第一批结果交出去——对面反馈:怎么把群里的管理号给干掉了?
一个被误杀的「沉默管理者」
那个号平时看着确实不怎么说话,一天撑死两三句。按≥21次的标准,妥妥划进dead池。问题是,人家是群管,负责发公告、踢人、审广告。真正活跃的号反而容易被风控盯上,这类「静默管理」在卖号眼里反而是优质资产。
我翻了原始数据才发现:他发言少,但@次数多,每周还固定传两次文件。单纯数消息条数,把他这种「低频高权」的行为全漏了。
把「看得见的动作」加权算进去
解决思路也不复杂,就是把特殊行为单独拎出来加权。我在计数时加了三个字段:
df['@count'] = df[df['content'].str.contains('@')]['qq'].groupby('qq').count()
df['file_count'] = df[df['type'] == 'file']['qq'].groupby('qq').count()
df['announce_count'] = df[df['action'] == 'announce']['qq'].groupby('qq').count()
权重我给了@×2、文件×3、公告×5,再跟基础发言量做线性叠加。公式写得很土,但够用:
score = msg_count_7d + @count * 2 + file_count * 3 + announce_count * 5
70% 到 92% 之间发生了什么
改完重新跑,原本被归为dead的那批管理号全部拉回high池。整体召回率从70%涨到92%。剩下的8%基本是真的潜水,或者发言全是表情包、链接这种对风控没用的噪声。
代价也有:脚本要多扫一遍历史记录,耗时多了一倍。不过对于半夜批量发包的人来说,时间不是问题,准确率才是命。
批量发包前的最后一道防线:二次验证
阈值调完、加权算完、列表理清了。你以为可以开个脚本往群里哐哐砸消息了?
别急。直接把那批号拎过去发,大概率第一轮没跑完就被腾讯把IP封了——连带着目标群都可能被冻结。我亲眼见过一个哥们儿,号筛选得漂亮,结果没做任何防护,十分钟内连代理IP都进了黑名单。
IP代理池:别让腾讯一眼认出你
QQ的风控没那么玄乎,但也不傻。同一IP在短时间内给成百上千个号发登录请求,哪怕每个号间隔十秒,流量特征也异常得离谱。
可以用requests.Session配合随机代理,注意别用免费池子——那些公开IP基本已经被各大平台标记了。我试过用付费的家用代理池,每发一条消息前从池子里随机取一个IP,绑定到当前session上。代码大概长这样:
import random
import requests
proxies_list = [...] # 从代理API拉到的IP列表
session = requests.Session()
proxy = random.choice(proxies_list)
session.proxies = {"http": proxy, "https": proxy}
要是觉得requests轮换麻烦,直接用scrapy的中间件也行,但QQ登录那套得单独处理。代理轮换的频次别设太高,发十条换一次IP就够了,太频繁反而触发异常检测。
账号存活检测:发之前先测一下
那批号里可能有10%-15%早就被腾讯收走了——密码对但登录不了,或者被限制加群。直接拿来发消息,不仅白费力气,还可能连累同一IP段的其他号也跟着被封。
我习惯在批量发包前跑一轮存活检测。模拟登录一下,看看返回状态码和响应体里有没有"账号受限""需短信验证"这类提示。如果是正常的cookie获取成功,标记为活号;遇到验证码拦截,标记为半死不活;连密码都不对的,直接丢弃。
存活检测也要做随机延时,否则腾讯一看这批号在同一时间段密集登录,直接给你全拉黑名单。
发包节奏控制:学学真人怎么聊天
真人的聊天是有间歇的。不会有人一分钟发六条消息,然后停两小时,再一分钟发八条。风控模型盯着这个模式盯得很死。
我的做法是分批+随机延时。把所有活号打乱,每批10-15个号,每批执行时间分散到不同的时段。单个号发完一条消息后,暂停8-20秒(用random.uniform取随机值),再发下一条。批与批之间至少间隔30分钟。
import time
import random
for batch in split_into_batches(live_accounts, batch_size=12):
for account in batch:
send_message(account, target_group, content)
delay = random.uniform(8, 20) # 秒
time.sleep(delay)
time.sleep(random.randint(1800, 3600)) # 半小时到一小时
如果目标群比较敏感,比如是商业大群或者有管理员在线盯着,建议把延时上限拉到40秒,宁可慢一点,别让消息堆得跟机器人对话似的。
IP池、存活检测、延时策略,这三样东西少一个,前面做的那堆筛选工作就可能白干了。卖号团伙最怕的不是脚本写不出来,而是号拿在手里发不出去。说到底,这行拼到最后,拼的是你对平台风控的敏感度——而不是代码写得多花哨。
法律与道德红线:爬虫脚本的合规使用边界
走到这一步,你已经能把群成员筛选、存活检测、发包节奏都串起来了。但工具再顺手,也不能越过那条线——尤其是当你手里握着一批“高活跃QQ号”的时候。
别碰未经授权的数据
《计算机信息网络国际联网安全保护管理办法》白纸黑字写着:任何单位和个人都别想利用国际联网危害国家安全、泄露国家秘密,更不能侵犯国家、社会、集体利益和公民的合法权益。说人话就是——你从别人平台扒用户数据,只要没拿到明确授权,基本就是在雷区蹦迪。见过不少脚本跑得飞起,最后平台投诉或律师函直接糊脸,原因特简单:你既不是群主,腾讯也没给你任何许可。
只动自己能动的资源
如果你只是导出自己创建或管理的群里的成员,用于内部统计、清理僵尸粉,这属于合理的数据使用范围。但如果试图绕过权限验证去获取他人群成员列表,甚至批量导出后拿去售卖,性质就变了。技术上可行 ≠ 法律上允许。有些卖号团伙会强调“我们只是做筛选”,可一旦数据来源本身非法,后续所有操作都站不住脚。
脱敏处理是最后的底线
即便你数据拿得合法,基本防护也得做到位。QQ号导出之后别傻乎乎直接存成明文CSV,哪怕过一遍 SHA-256 都行,或者只留发言次数、在线天数这种统计字段。我试过把测试库的备份随手扔云盘,三天后被人扫出来了——还好里面全是假号,但那种感觉真挺膈应。现在不管项目多小,我都会让 save_as_csv(accounts, fields=["active_days", "msg_count"], salt="fixed_string_not_empty") 这种调用默认把敏感标识滤掉。
技术这东西,跑得快是好事,但跑偏了就麻烦了。规矩就在那儿,碰不碰得看自己——反正晚上能睡踏实,比什么都强。
评论