资讯动态

Python爬取QQ群信息:接口鉴权与数据补全实战

发布时间:2026/9/17 17:32:42 来源:尧图企业网站定制
简介针对QQ号群信息抓取的小型爬虫资源包面向Python爬虫初学者及有数据收集、社群分析需求的开发者无论是个人研究还是小型项目实践均有参考价值。资源聚焦如何通过自动化脚本抓取指定QQ号关联的全部群信息涉及HTTP请求、HTML解析、数据提取与存储等关键技术点是爬虫工作流程URL收集、请求网页、解析内容、数据存储的一次精简落地。压缩包共3个文件含2个Python脚本和1个Markdown说明文档整体仅4KB脚本承载主要抓取逻辑说明文档辅助运行指导与思路梳理。目前已有376人浏览学习。通过学习可掌握Requests、BeautifulSoup等库发起请求并解析网页的方法获得可直接运行的QQ群信息抓取脚本同时了解访问频率控制、User-Agent设置等爬虫礼仪与反爬应对意识。代码量精简便于逐行阅读和二次修改兼具实用性与教学意义。1. 为什么这包 QQ 爬虫代码值得拆开看做群运营或者数据清洗的朋友经常会遇到一个看起来很简单的问题怎么把某个 QQ 号加入的所有群信息快速拉出来。这个 ZIP 里的两个 Python 脚本正好解决这个问题一个负责统计群数量一个负责补全详情体积很小也不依赖浏览器自动化而是直接请求 QQ 群管理后台的 JSON 接口。如果你在找 Python 爬虫的实战素材这个包比教科书上的天气接口例子更接近真实工程因为里面涉及 Cookie 鉴权、签名参数、接口去重和失败重试。适合已经会用 requests 但还没碰过带 bkn 签名接口的工程师也适合想给自己 QQ 机器人做群白名单的人。2. 群数据从哪里来qun.qq.com 列表接口与 bkn 鉴权2.1 为什么直接抓 JSON 接口而不用浏览器自动化这个包没有使用 Selenium 也没有用 PlaywrightREADME 里第一步就写着“请先登录 qun.qq.com 并复制 Cookie”。原因是 QQ 群管理后台存在一个稳定的 JSON 接口路径是/cgi-bin/qun_mgr/get_group_list一次 GET 请求就能拿到当前账号可见的群列表。用 requests 的 Session 维持 Cookie比开一个浏览器页面手工复制快得多。如果面对的是几百个群Selenium 方案要逐个页面切换内存占用大而且页面只要改版脚本就要跟着改。解析 JSON 接口则不同返回结构清晰字段可预期后续无论要存数据库还是生成报表都方便。这个选择符合爬虫工程里“优先接口、其次页面”的原则。2.2 bkn 参数从 skey 到鉴权散列接口自身带防跨域调用机制官方要求每次请求带bkn。这个值不能直接抓到需要从 Cookie 里的skey运算得到。资源里的qq_count.py和qq_count_detail.py都使用了同一个函数def compute_bkn(skey: str) - int: hash_val 5381 for ch in skey: hash_val (hash_val 5) ord(ch) return hash_val 0x7fffffff代码本身很短但值得解释一下。hash_val的初始值 5381 是 DJB hash 的经典种子循环里hash_val 5等价于乘以 32再把每个字符的 Unicode 码点加进去最后用 0x7fffffff截断为 31 位正数。这个实现不是逆向产物而是 QQ 前端源码里公开的算法。bkn的有效期由skey决定只要 Cookie 不过期一次登录后可以重复调用。这里需要强调的是bkn不能写死每次启动脚本后都应该根据当前 Cookie 重新计算。如果手动改过 Cookie旧bkn会直接失效接口返回错误码而不是正常数据。2.3 Cookie 准备与 zip 包结构解压这个 zip 后根目录会看到README.md、qq_count.py、qq_count_detail.py和一个SJT-code目录。README 里描述的是手动复制 Cookie 到cookie.txt的流程脚本运行时自动读取。这样做的好处是代码仓库不会残留敏感信息也方便定期更换 Cookie。从浏览器获取 Cookie 的路径是无痕窗口打开qun.qq.com登录后按 F12 进入 Network刷新页面复制任意一个/cgi-bin/请求的 Request Headers 里的 Cookie 字段。Cookie 中比较关键的几个键如下表所示Cookie 键作用失效表现uin加密后的 QQ 号用于标识访问者接口返回 ec1000skey计算 bkn 的核心密钥bkn 计算错误或返回 403p_skey部分接口需要的高权限密钥详情接口 4xx复制时不要把Cookie:前缀也放进去只要keyvalue; key2value2这一段。脚本内部会用分号切分并转换成 requests 的 Cookie 对象。提示用无痕窗口登录可以有效避免多个 QQ 号登录态混杂。如果你本机同时登录了多个 QQChrome 的 Cookie 可能指向最近激活的那个抓到的群列表会对不上。3. qq_count.py先算清“这个号加过多少群”3.1 群列表请求的组装方式qq_count.py是整个项目的入口脚本职责是发起群列表请求并把结果写到 JSON。它的核心逻辑是fetch_group_list我用简化后的代码表示import json import requests from pathlib import Path def compute_bkn(skey: str) - int: hash_val 5381 for ch in skey: hash_val (hash_val 5) ord(ch) return hash_val 0x7fffffff def load_cookie(file_path: str) - dict: text Path(file_path).read_text(encodingutf-8).strip() result {} for part in text.split(;): part part.strip() if not part: continue key, sep, value part.partition() if sep: result[key] value return result def fetch_group_list(qq_number: str, cookie_file: str cookie.txt) - list: cookies load_cookie(cookie_file) bkn compute_bkn(cookies.get(skey, )) session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Referer: https://qun.qq.com/, }) session.cookies.update(cookies) resp session.get( https://qun.qq.com/cgi-bin/qun_mgr/get_group_list, params{bkn: bkn, st: 1, t: 1, uin: qq_number}, timeout15, ) resp.raise_for_status() payload resp.json() if payload.get(ec) ! 0: raise RuntimeError(payload.get(em, 接口返回错误)) return payload.get(group_list, [])代码逻辑是典型的“构造 Session - 组装参数 - 发请求 - 解析 JSON”。params里的bkn由 Cookie 中的skey实时算出uin这里填写你想要查询的 QQ 号。值得注意是st和t在 README 的注释里写的是“列表起始位置”实际抓包时如果群非常多可以用这两个参数继续翻页一般场景下保持st1t1就行。Session 对象在这里不只是方便带 Cookie还能复用底层连接。如果你跑的 QQ 号加了几百个群Session 可以减少 TCP 握手次数这是爬虫性能优化里最基础的一层。3.2 解析群列表与字段去重接口返回的 JSON 结构一般由三部分组成ec错误码、em错误信息、group_list群数组。group_list中每个群对象的核心字段如下表字段含义是否唯一gc群号数字字符串是gn群名称否m成员数量否owner群主 QQ 号否因为群名称可以修改所以去重时必须用gc而不是gn。有些旧教程会建议按群名合并这是不严谨的同名群会互相覆盖。脚本在写入结果前可以加一层{g[gc]: g for g in group_list}.values()去重防止接口因为分页返回重复数据。3.3 保存群清单并维护原始数据下一步是把群列表落地供后面的qq_count_detail.py使用def main(): qq_number 10001 groups fetch_group_list(qq_number) result { qq: qq_number, fetch_time: 2024-06-30 12:00:00, count: len(groups), groups: groups, } Path(groups_summary.json).write_text( json.dumps(result, ensure_asciiFalse, indent2), encodingutf-8, ) print(f共获取 {len(groups)} 个群) if __name__ __main__: main()这里ensure_asciiFalse很关键否则中文群名会被写成一堆\uXXXX转义人工检查时完全没法用。indent2是为了让缩进清楚方便 git diff 时看到变化。fetch_time字段用于后面和第二次抓取做差异对比务必要保留。如果运行时报错ec1000先检查 Cookie 里的uin是否还带着前缀o。QQ 的 Cookie 中uin通常是oQQ号的形式比如o10001而脚本参数需要的是纯数字10001。两者不能混用接口校验时会对不上。4. qq_count_detail.py按群号补全信息并落成 CSV4.1 为什么还需要第二个脚本qq_count.py拿到的是群列表的概览字段已经能看到群号、群名和成员数但要进一步做运营分析时往往还需要群标签、群公告、群创建时间。这些字段在列表接口里经常被简化所以需要按群号二次请求把详情部分并到原来的行里。这就是qq_count_detail.py存在的意义。注意一个细节二次请求不能直接复用第一次的响应因为那只是查询时的快照。如果中途有人退了群成员数会变化所以详情脚本里会以第二次请求的结果为准并把旧数据备份到一个group_snapshot.json里。这样后续如果要追溯历史状态还能找到原始值。4.2 单群详情请求与重试策略详情请求的常见写法是循环群号列表每次调用一个可配置的detail_api。资源里 README 给的接口路径同样指向 qun.qq.com 的/cgi-bin/目录但不同版本返回结构不同所以脚本里用data.get(group_info, {})做兼容。核心代码片段如下import time import requests def enrich_group(group, session, detail_api, bkn, retries3): for attempt in range(retries): try: resp session.get( detail_api, params{gc: group[gc], bkn: bkn}, timeout10, ) resp.raise_for_status() data resp.json() if data.get(ec) ! 0: raise RuntimeError(data.get(em)) return {**group, **data.get(group_info, {})} except (requests.RequestException, RuntimeError) as exc: if attempt retries - 1: # 重试耗尽记录错误并保留原字段 return {**group, detail_error: str(exc)} time.sleep(1.5 * (attempt 1)) return groupretries参数控制重试次数1.5 * (attempt 1)是退避间隔。第一次失败等 1.5 秒第二次等 3 秒第三次等 4.5 秒。这个退避算法对接口风控比较友好比固定 sleep 做得更精细。注意重试前不需要重新读取 CookieCookie 没有变化时bkn也保持不变所以函数里直接传bkn进来就好。如果返回字段里恰好没有group_infodata.get(group_info, {})返回空字典合并结果仍然包含原来的群号不会因为一个群解析失败导致整行丢失。4.3 用线程池控制并发而不是无限加速群数量少时逐条请求没问题但超过一百个就要考虑限速和线程池。这里给出一种常见做法from concurrent.futures import ThreadPoolExecutor, as_completed def enrich_groups(groups, session, detail_api, bkn, max_workers3): results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: futures { executor.submit(enrich_group, g, session, detail_api, bkn): g for g in groups } for future in as_completed(futures): results.append(future.result()) return resultsmax_workers3是经验值太小浪费等待时间太大会触发请求频率限制。我一般会在脚本里加一个环境变量MAX_WORKERS压测时从 1 调到 5观察响应曲线。如果某个接口开始返回 403就说明当前并发已经超过阈值不要再往上加。线程池的另一个好处是可以配合as_completed在第一个请求失败时尽快感知。但注意不要在每个线程里创建新的 Session多个线程共用一个 Session 是可以的requests 的 Session 是线程安全的只要你不并发修改 Cookie。4.4 CSV 输出与 Excel 兼容最终结果需要给运营同学看CSV 是最小公分母。字段顺序提前定义避免每次运行顺序不一致import csv fieldnames [gc, gn, m, owner, tag, create_time, detail_error] with open(groups_detail.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesfieldnames, extrasactionignore) writer.writeheader() for g in enriched_groups: writer.writerow(g)utf-8-sig是带 BOM 的 UTF-8Excel 双击打开时不会乱码。extrasactionignore表示如果某行响应多出没定义的字段直接丢弃而不是报错。如果你希望保留所有字段可以把fieldnames改成读取第一行数据的动态列名但那样列顺序不稳定人工核对时更累。5. 抓完不等于抓完用群号交集验证数据完整性第一次跑通后很多人会直接把groups_detail.csv拿去分析。但这批数据可能有缺失比如 Cookie 过期导致返回了半截列表又比如分页参数翻页后出现重复。验证方法其实很简单把当天抓到的结果和隔天抓到的结果放一起按gc做集合运算。import json def load_gc(path): with open(path, encodingutf-8) as f: return {item[gc] for item in json.load(f)[groups]} first load_gc(groups_summary.json) second load_gc(groups_summary_nextday.json) common first second only_first first - second only_second second - first print(f共同群: {len(common)}) print(f只出现在第一次: {only_first}) print(f只出现在第二次: {only_second})common是两天都有的群only_first可能代表已退群或被封群only_second可能代表新加入的群。如果only_second突然变大先别急着分析业务很可能是第一次抓取时 Cookie 已过期没有拿到完整列表。这时候重新登录再抓一次基本能对齐。还有一个小技巧把抓到的gc列表转成 SQLite 表按群主做聚合可以快速发现群主集中度用来做 QQ 机器人的群白名单非常合适-- 在 SQLite 中按群主统计群数 SELECT owner, COUNT(*) AS group_count FROM groups_detail GROUP BY owner ORDER BY group_count DESC;机器人收到群内消息时先查这条消息的group_id是否在表里不在就忽略省去大量无效回调。这个包本身没有实现机器人逻辑但 CSV 和 JSON 两种导出格式已经足够对接。最后提醒一下任何对 qun.qq.com 的重复请求都要控制频率建议把两次抓取间隔设在 6 小时以上并且不要在脚本里做无限重试。连续失败 5 次直接退出把错误码写进日志方便下次检查。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价