ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

批量删除QQ空间说说脚本实战:Cookie、g_tk与接口调用解析

批量删除QQ空间说说脚本实战:Cookie、g_tk与接口调用解析

简介:2023年最新版QQ空间说说批量删除插件,专为QQ空间重度用户及需要一次性清理历史说说的人群打造,适用于新版QQ空间界面,解决逐条手动删除效率低下、难以批量管理的问题。资源共3个文件,核心为JavaScript自动删除脚本,另含Tampermonkey扩展安装包与独立的说说清理工具,压缩包仅2.74MB,轻量且便于本地部署。脚本支持在个人主页中按条件筛选目标说说,自动完成批量删除,全程无需人工逐条点击,也可通过ESC键随时停止,操作过程清晰可控。目前已有1128人学习下载,使用者可直接获得脚本代码、扩展安装包及辅助清理工具,配合插件说明即可快速上手,适合希望高效整理空间动态、具备基本浏览器扩展安装能力的普通用户与办公人群。

1. 批量删除QQ空间说说:手动删到凌晨,不如先做一个删除脚本

你有没有过这种时刻:打开QQ空间想清理黑历史,结果发现十年积累的说说翻了几页就开始手酸,手动点删除、点确认,删到凌晨还剩大半。最让人绝望的是页面滚动加载慢慢吞吞,每删除一条页面可能还会刷新位置,鼠标移过去又得重新找按钮。这个标题其实指的就是同一个需求:在2023年网页端改动之后,怎么通过脚本或插件批量删除自己的QQ空间说说。这篇文章我会把思路、抓包过程、可复现的脚本代码以及常见翻车点全部讲完,适合想亲手清理空间、又不想一条条手点的开发者。也提醒一句:所有操作只针对自己的账号,别拿这套逻辑去处理别人的内容。

2. 说说删除的本质:先看懂网页端的删除请求与参数校验

很多教程一开始就甩一段脚本让你粘贴,但一旦接口报错,你根本不知道从哪里查。批量删除说说的核心不是写脚本,而是搞清楚“删除”这个动作在网页前端到底向后端发送了什么。你只有把这个请求看懂,才可能在2023年某个小版本更新后,自己重新抓一次包,把脚本改到能跑。

2.1 为什么不能直接模拟点击:页面事件与数据加载的拆解

在QQ空间网页版,每条说说的删除按钮不是一个普通链接,而是绑定了前端事件。你要批量处理,常见做法是在控制台里循环调用click()。但很快会发现两个问题:第一,说说列表是滚动懒加载的,DOM里只有当前可视区的几十条,后面的内容要等滚动才会请求;第二,点击删除会先弹出一个确认框,确认框的按钮也有自己的异步逻辑,直接循环点击经常导致页面状态错乱,甚至把一条说说给删成了两遍。

所以真正可靠的路子是绕过前端交互,直接向后端接口发删除请求。这个思路有点像是“帮浏览器把它想做的事情做完”,但更稳定、更快,也更好控制频率。我在给某开发者处理类似需求时,就是先让他打开浏览器开发者工具,手动删一条,观察请求,然后把请求原样复制成脚本。这个流程放在2023年依然有效,因为页面可以改,但后端校验模型的整体思路不会变。

2.2 从浏览器抓包里挖出删除说说的三个关键要素

打开浏览器开发者工具,切到 Network 面板,勾选 Preserve log,在空间里手动删除一条说说,然后过滤一个类似“delete”的关键词,你会看到一个以taotao.qq.com开头的请求。这个请求实际上就是删除接口,常见路径名里会包含emotion_cgi_delete_v6这样的标识。

这个请求里最核心的三个要素是:

第一,请求地址和请求方法。它是一个POST请求,参数不是放在URL里,而是放在表单里,这跟你平时看到的GET参数完全不一样。第二,Cookie。请求的Cookie里有一个p_skey,它是算g_tk的原材料。g_tk是QQ空间接口常用的一个校验参数,相当于一个由Cookie派生出来的签名。第三,当前页面的qzonetoken。这是页面加载时写进源码的一次性token,会在很多接口请求里反复出现。手动删除成功后,你可以在请求的表单里找到这三个要素的值。

下面这张表是我在2023年实际抓包时会反复确认的字段:

字段来源作用
g_tk由Cookie里的p_skey本地算出接口签名
qzonetoken页面源码里的全局变量页面会话标识
tid说说列表接口返回每条说说的唯一ID
hostUin / uin当前登录QQ号说明是发在哪个空间

如果你在抓包里看到比这个多的字段,不要慌。删除接口可能还会带cmtNum、comment、source、spcnum这些参数,它们大多是从前端状态带过去的,有些是必填的,有些只是占位。我的习惯是:先原样复制一条完整请求,再逐步删字段测试,确定哪些能省略。这样比对着网上教程背参数靠谱得多。

2.3 选型:本地脚本 vs 浏览器脚本,各管哪个场景

我一般把方案分成两条线。如果你要删几百上千条,推荐用本地Python脚本,直接把列表接口和删除接口串起来,效率最高,并且可以精确控制请求间隔。如果你只是临时删几十条,环境里没有Python,那就在浏览器控制台里跑一段JavaScript,边滚动边删除,不用安装任何东西。

两条线的底层请求都是一样的,只是载体不同。Python脚本适合“批量、重复、无人值守”,浏览器脚本适合“可视化、小批量、马上看到效果”。还有一个折中做法是写成一个浏览器用户脚本,比如油猴脚本,点击一下就能在页面上自动滚动和删除。但油猴脚本本质上还是在模拟前端事件,速度上不如直接发请求,而且页面改版时选择器很容易失效。

3. 用Python脚本批量删除说说:Cookie、g_tk与请求构造全流程

这一章我会给你一个能跑的骨架。你不需要完全理解每一个参数,但一定要知道它从哪里来、怎么验证。第一步是打开QQ空间,随便滚动几下,让页面加载一些说说,再打开开发者工具,从 Network 面板里的任意一个请求里复制当前页面的Cookie。然后把这个Cookie放到脚本里。

3.1 准备工作:导出Cookie,并算出g_tk

Cookie是一长串类似uin=123; p_skey=abc; ...的文本。我们脚本里需要的是p_skey或skey。因为g_tk是用它算出来的。下面这段代码就是求g_tk的常见实现:

import re def get_g_tk(cookie_str): match = re.search(r'p_skey=([^;]+)', cookie_str) if not match: match = re.search(r'skey=([^;]+)', cookie_str) if not match: raise Exception("Cookie里没有p_skey或skey") skey = match.group(1) h = 5381 for ch in skey: h += (h << 5) + ord(ch) return h & 0x7fffffff

这个函数不是我的原创,它和QQ空间页面里用的getGtk算法一致。把skey逐字符做一次加权累加,然后按位与保留正整数。你可以用浏览器控制台验证:在Console里输入skey对应的字符串,跑一遍同样的循环,和脚本算出来的值比对。如果一致,说明Cookie复制得没问题。

另外还需要qzonetoken。在QQ空间页面里,打开开发者工具,切到Elements面板,在页面源码里搜索qzonetoken,通常能看到一个变量定义,值是一串字母数字。把它复制出来,放到脚本里。

3.2 先跑通单条删除:构造一次delete_v6请求

有了Cookie、g_tk和qzonetoken,就可以先尝试删除一条。不要直接全量跑,先选一条自己知道内容的说说,在代码里指定它的tid,看返回结果是不是0。删除接口地址和参数我一般按下面的骨架来:

import requests session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": "https://user.qzone.qq.com/你的QQ号", }) cookie_str = "uin=123; p_skey=abc; ..." # 从浏览器复制 cookie_dict = {} for item in cookie_str.split(";"): item = item.strip() if "=" in item: k, v = item.split("=", 1) cookie_dict[k] = v session.cookies.update(cookie_dict) g_tk = get_g_tk(cookie_str) qzonetoken = "这里填页面上找到的token" del_url = "https://h5.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_delete_v6" payload = { "g_tk": g_tk, "qzonetoken": qzonetoken, "uin": "你的QQ号", "hostUin": "你的QQ号", "tid": "要删除的说说tid", "format": "json", "json_escaped": "1", } resp = session.post(del_url, data=payload) print(resp.status_code, resp.text)

这里的关键在于用同一个session对象去发请求,这样Cookie才能保持。请求体里的uin和hostUin必须是你自己空间的QQ号。tid是每条说说的ID,可以从列表接口拿到,也可以从页面元素上抠。第一次运行如果不通过,先看返回文本是不是-10000,如果是,基本就是g_tk算错或Cookie过期。

3.3 全量遍历:用msglist_v6拉取所有说说的tid并删除

单条能删成功,剩下的事情就简单了:先拉列表,拿到所有tid,再循环调用删除接口。QQ空间说说列表接口有一个经典路径,常见是emotion_cgi_msglist_v6,POST参数里带pagenum和pagesize,返回JSON里有一个msglist数组,数组里的每个元素就包含tid`。

import time import json list_url = "https://h5.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msglist_v6" def fetch_page(page=1, size=10): payload = { "g_tk": g_tk, "qzonetoken": qzonetoken, "uin": "你的QQ号", "hostUin": "你的QQ号", "format": "json", "json_escaped": "1", "pagenum": page, "pagesize": size, } resp = session.post(list_url, data=payload) return resp.json() def delete_one(tid): del_payload = { "g_tk": g_tk, "qzonetoken": qzonetoken, "uin": "你的QQ号", "hostUin": "你的QQ号", "tid": tid, "format": "json", "json_escaped": "1", } return session.post(del_url, data=del_payload).json() page = 1 while True: data = fetch_page(page, 10) msglist = data.get("msglist") or [] if not msglist: break for item in msglist: tid = item.get("tid") result = delete_one(tid) print("删除", tid, "返回", result.get("code")) time.sleep(2) page += 1

这段代码的逻辑说明:先请求第1页,每页10条,如果msglist为空就停止。循环里的time.sleep(2)是必要的,否则接口很快会触发频率限制。注意delete_one里的删除接口地址del_url要和上一节定义的一致。实际运行时,如果发现一页有20条,但只删了10条,说明pagesize没有生效,或者接口每次返回的条数和前端展示不同,你需要观察返回JSON里的total和totalpages来调整。

4. 用浏览器脚本在页面内批量删除说说:不装环境也能跑

如果你的电脑上没有Python,或者你不想复制Cookie那么麻烦,还可以直接在浏览器控制台里跑JavaScript。这个方案的好处是所见即所得,脚本就运行在当前页面上,天然拥有登录态,不需要手动处理Cookie和token。

4.1 控制台脚本:从当前页面循环删除可见说说

打开QQ空间说说列表页,按F12进入控制台,粘贴下面这段代码。它的思路是:找到页面上所有说说的DOM节点,点掉每条说说对应的删除按钮,再点掉确认框的确认按钮,然后等页面更新。

function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function deleteVisible() { const feeds = document.querySelectorAll('.feed-item, .f-info'); for (const feed of feeds) { const delBtn = feed.querySelector('a[title="删除"], .js_delete'); if (delBtn) { delBtn.click(); await sleep(300); const confirmBtn = document.querySelector('.mod_confirm_btn, .btn-primary'); if (confirmBtn) confirmBtn.click(); await sleep(500); feed.remove(); } } } await deleteVisible(); console.log('当前页删除流程结束');

这段代码里的选择器是我根据常见页面结构写的,但QQ空间页面在不同年份、不同登录方式下类名可能完全不同。你在自己的页面上要先按F12,用Elements工具点一下删除按钮,看它实际挂在哪个标签上,再把.js_delete换成你看到的类名。不要指望一段选择器永远通吃,这是浏览器方案最需要花时间调的地方。

4.2 油猴脚本:自动滚动、自动确认的完整流程

上面的控制台脚本只能处理当前可视区的说说。如果说说很多,需要自动滚动加载。更省事的做法是写一个油猴脚本,通过MutationObserver监听页面新增的说评,自动去点删除。下面是一个可扩展的骨架:

function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function handleFeed(feed) { const delBtn = feed.querySelector('a[title="删除"], .js_delete'); if (!delBtn) return false; delBtn.click(); await sleep(400); const confirmBtn = document.querySelector('.mod_confirm_btn, .btn-primary'); if (confirmBtn) confirmBtn.click(); await sleep(600); return true; } const observer = new MutationObserver(() => { const feeds = document.querySelectorAll('.feed-item, .f-info'); feeds.forEach(feed => handleFeed(feed)); }); observer.observe(document.body, { childList: true, subtree: true }); async function autoScroll() { let lastHeight = 0; while (document.body.scrollHeight !== lastHeight) { lastHeight = document.body.scrollHeight; window.scrollTo(0, document.body.scrollHeight); await sleep(1500); } } autoScroll();

油猴脚本的本质是让页面在加载时自动执行这段逻辑。它比控制台脚本舒服的地方是,你刷新页面后,它会继续监听新出现的说说节点,不用每次手动粘贴。但它也有明显的副作用:如果选择器写得太宽松,可能会误点其他页面的元素。我的建议是不要直接全量自动运行,先开着控制台观察几次,确认点击、确认、刷新这三步都稳定。

4.3 两种方案使用边界与效率对比

本地Python脚本一次请求拿到10条说说的tid,然后一条请求删一条,整个过程不依赖页面渲染,速度主要取决于网络延迟,通常每分钟可以处理20到30条,实际要看你的睡眠间隔。浏览器脚本则要等页面渲染、确认框动画、滚动加载,每分钟能处理5到8条就算不错。

但Python脚本对数据的要求更高,你要能构造出让后端满意的完整表单;浏览器脚本只要点中按钮就行,前端会自动补全所有参数。所以我的判断是:如果你只是偶尔清理,用浏览器方案;如果你要把几年的内容一次性清掉,趁早用Python方案,并且做好频率控制。

5. 批量删除说说常见问题排查:2023版5个翻车现场

这一章我会讲几个我实际踩过、或者给朋友排查时遇到的典型问题。每条都按现象、原因、解决来写。你在执行脚本时如果遇到报错,先对照这里。

5.1 现象:删几十条后,接口返回“操作频繁”,然后被短暂限制

用Python脚本全量删除时,前两分钟一切正常,删到大概三四十条,接口开始返回错误,错误信息里带着“操作频繁”之类的提示,过一两个小时又能恢复。原因是删除接口有频率限制,它不是按秒算,而是按时间和数量综合计算。解决方法是把sleep从2秒加到3到5秒,并且随机化,避免机器式的固定节奏。另外不要在多个地方同时跑脚本,一个人在一个IP下高频请求最容易被触发限制。

5.2 现象:脚本请求成功,code返回0,但说说数量没变少

这是最让人头大的情况。接口明明说删成功了,但回到空间一看,说说还在那里。原因之一是有些说说并不是普通文本类型,比如带投票、带长文、或者当年发的“特别关心”动态,这类说说的删除需要额外的参数,比如comment、source、spcnum等。另一个原因是列表接口返回的tid可能是新版的业务ID,后端删除接口里还需要传abstime或者appid这类字段。解决方法是手动删一条这样的说说,抓包看看它比普通说说多了哪些表单字段,然后补进delete_one函数。

5.3 现象:网上的教程参数和我抓到的不一样

很多人拿2020年甚至更早的教程来套,发现接口域名不是原来的user.qzone.qq.com,变成了h5.qzone.qq.com,参数里也少了某个旧字段。原因是QQ空间前端代码会不定期迭代,接口路径和参数都会微调。解决方法很简单:不要背参数,以你自己浏览器里抓到的为准。手动删一条,把请求复制为cURL,然后用curl.trillworks.com这类工具转成Python代码作对照,把缺失字段补上。

5.4 现象:删除后刷新,说说还在但点击提示已删除

这跟5.2有点类似,但现象更诡异:刷新后说说还在,点进去又显示已删除。这种情况大概率不是接口问题,而是删除请求里的hostUin和你当前访问的空间不是同一个。比如你登录了A账号,却在B账号的空间页面上跑了脚本,hostUin被脚本写死了A,于是它把B空间当成了自己空间来删,接口校验能通过,但数据根本不是那个空间的内容。解决方法是把脚本里的uin和hostUin都改成当前登录账号,并且确认浏览器地址栏里访问的确实是自己的空间。

5.5 现象:Cookie失效导致请求返回-10000

脚本已经跑了一会儿,突然所有请求都返回-10000。原因是你在其他设备上登录了同一个QQ号,之前的Cookie会被踢掉,或者Cookie里的p_skey有效期本来就很短。解决方法是回到浏览器重新复制Cookie,同时关闭其他设备的登录。另外要注意,不要随便把Cookie贴给第三方工具,因为Cookie就是你的登录凭证,脚本只该在本地跑,别为了一张截图把Cookie贴到网页表单里。

6. 批量删之前先做这两件事:备份与验证

删说说不是终点,删完还要能证明自己删干净了,并且万一误删能后悔。所以这一章我给你一个最小可用方案:备份和验证。

6.1 备份说说:把列表接口的JSON落盘,至少留下评论和时间

在Python脚本里,列表接口返回的msglist数组里除了tid,通常还有createTime、content、commentlist等字段。你要做的只是在拿到数据后顺手写进本地文件,不要等到删完再后悔。代码其实就一行:

import json def backup_shot(): with open("backup.json", "a", encoding="utf-8") as f: f.write(json.dumps(data, ensure_ascii=False) + "\n")

注意要按行追加,每条说说单独一行。这样即使中间出错,你也能按行统计备份进度。备份文件里至少要有三个字段:tid、createTime、content。如果有评论统计,顺手把commentNum也存下来。

6.2 验证删除:用数字对比确认删干净了

删除全部跑完后,重新请求一遍列表接口,把返回的total字段打印出来。如果总数变成0,说明普通说说已经清空;如果总数没变,说明还有一部分说说没删成功。此时你肯定想快速定位是哪条,我建议你在备份文件里记录每个tid的删除结果,删完后再去检查没有删除成功的tid,单独重试。

也可以写个简单验证函数:

def verify_empty(): data = fetch_page(1, 1) print("剩余说说数:", data.get("total", 0))

6.3 我的习惯:小批量试删、分批执行、别把睡眠时间调太短

我每次写这类脚本,第一件事不是全量跑,而是先构造一个测试用的tid清单,选3到5条内容不太重要的说说试删。确认每一条都能删成功,再把删除函数接上列表循环。整个删除过程我会按100条一批去跑,每批之间休息5分钟。

这个习惯救过我很多次。有一次是在某个模拟项目X里处理类似需求,脚本跑着跑着接口突然需要二次验证,如果我一开始就全量跑,那几千条说说可能全部卡死在当前页。分批执行还有一个好处:每批结束都能及时把备份文件存盘,就算脚本崩溃,也不会白删。

希望这篇文章能帮你把QQ空间说说的清理做成一件能掌控的事。删之前备份,删的时候控制频率,删完再验证一次,这三步做完,你基本就不会被“批量删除”这件事坑到了。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进