
简介面向需要快速清理微博主页内容的用户这份JavaScript小工具可直接在Chrome浏览器登录微博后通过控制台执行一段脚本即可隐藏全部微博。资源包仅6KB共3个文件核心隐藏逻辑weibo-hide-all.js、说明文档README.md以及开源许可LICENSE其中README提供安装与使用说明LICENSE明确分发条款JS文件即功能主体适合有一定前端基础、熟悉控制台操作的轻量级使用者。目前已有767人学习下载。借助这个脚本用户无需逐条删除或反复滚动页面即可实现微博批量隐藏兼顾隐私整理与界面视觉净化。这一能力尤其适合需要频繁更新微博、又希望随时以整洁主页示人的内容创作者脚本利用fetch远程加载逻辑整体实现方式直白后续可自行调整隐藏范围或触发条件也为喜欢研究脚本实现的爱好者提供了可直接阅读和修改的参考。1. 不是“删微博”而是“批量设为自己可见”前阵子我清理一个用了很久的社交账号历史微博三千多条绝大多数已经不想再公开。网页端没有“全部转成仅自己可见”的按钮只能一条条点开权限菜单改刷一页改几条手指都快抽筋。weibo-hide-all 这个项目就是为这种场景准备的它不删任何内容只把所有公开微博批量设为“自己可见”核心逻辑全部用 JavaScript 在浏览器本地跑不依赖服务端不借助框架。适合内容创作者清空历史、小号整理可见范围以及任何想把账号从“公开状态”重置成“私密状态”的人。它的思路并不玄学复用网页本身已具备的接口模拟权限按钮的动作再补上并发控制和断点续传。这篇文章按“为什么这么设计、核心代码是什么、怎么跑、怎么改”来拆拿到源码后你能顺着链路改出自己的版本。2. 项目结构与技术选型一个纯前端脚本怎么承担批量隐私清理2.1 为什么不用服务端隐私清理就是要在本地闭环如果你要做的是把一堆历史内容改成私密第一反应可能是“写个爬虫放到服务器上批量刷”。但仔细想一下这等于把你的账号 Cookie 交给一个中间服务这本身就是隐私风险。weibo-hide-all 的做法是把所有逻辑放在浏览器里登录态在浏览器里请求从浏览器发出凭证不经过任何第三方这就是“本地闭环”的约束。这个设计约束直接决定了技术选型。项目没有用 Vue、React也没有用 Puppeteer因为不需要复杂渲染也不需要无头浏览器。真正的依赖只有三样原生 fetch 发 POST 请求、DOM 查询定位微博卡片、localStorage 记录任务进度。压缩后的脚本可以粘进浏览器 Console 直接运行也可以包成用户脚本刷新页面后自动出现入口。这样做最大的好处是低心智负担。整个工程没有抽象层唯一的“框架”是一个自己写的队列函数。后文会看到就算平台网页接口改版你通常只需要改一个api.js文件其余部分照常工作。2.2 目录与文件职责这份资源不是单文件而是一个小工程。目录结构如下weibo-hide-all/ ├── dist/ │ ├── weibo-hide-all.user.js # 用户脚本入口带 match 规则 │ └── console.js # Console 版可直接粘贴运行 ├── src/ │ ├── config.js # 并发数、延迟、重试次数、白名单 │ ├── collector.js # 采集当前页微博卡片信息 │ ├── api.js # 封装可见范围切换请求 │ ├── queue.js # 并发队列、失败重试、进度持久化 │ └── ui.js # 在页面右上角绘制进度浮层 ├── debug.html # 本地调试入口 └── README.md # 使用说明和接口调整说明各文件职责非常清晰改动场景也容易判断文件职责哪些情况需要改src/config.js设置 batchSize、delay、白名单关键词调整请求频率和过滤规则时src/collector.js读取 DOM 里的微博卡片提取 mid 和状态页面 DOM 结构改版时src/api.js拼参数、发请求、判断返回码平台接口参数变化时src/queue.js并发控制、失败重试、断点续传想改队列策略或持久化方式时src/ui.js进度浮层和停止按钮想自定义 UI 时整个功能很小但分层是清楚的collector 只负责“哪些微博要处理”api 只负责“单个微博怎么处理”queue 只负责“怎么稳定地处理完”。后面加“按关键词跳过”或者“按年份筛选”基本只动 collector 和 config。2.3 执行流程与平台接口参数一份资源要能落地怕的是黑匣子。这个项目的完整流程可以拆成六步读取配置初始化任务队列collector 扫描当前页面所有微博卡片取到 mid、文案、当前可见状态过滤掉白名单关键词和已经是“自己可见”的微博queue 从任务队列里取 mid调用api.js的切换函数每成功一条在 localStorage 里标记该 mid 已完成失败则进入重试队列全部完成或用户点击停止后进度浮层显示统计结果。平台接口是整个项目里最容易随版本变化的部分。我在写这个脚本时没有用页面上的按钮模拟点击因为点击按钮会触发额外的前端埋点和动画慢且容易误触。更稳妥的方式是直接抓包看按钮点击后发出的真实请求。以这个项目写到的接口为例它是一个POST /ajax/statuses/set_visible表单参数基本是参数示例值含义mid5103012345678901微博内容 ID字符串visible00仅自己可见1公开groupId空指定分组可见时填分组 IDreason空平台要求填写的原因文案重点提醒一下mid不是数字而是字符串可能长达 19 位visible在不同端的语义不完全一致有的端用-1表示删除。第一次拿到项目时建议先打开开发者工具的 Network 面板搜索visible确认字段名再跑否则你可能会收获一片业务错误码。2.4 console.js 的入口逻辑从配置到任务如果你想看项目从哪一行开始执行答案在dist/console.js。它的入口逻辑很短基本是把各模块串起来(async () { const config { maxScrolls: 30, batchSize: 5, delay: 800, whiteWords: [转发抽奖], }; await scrollToLoadAll(config.maxScrolls); const feeds collectFeeds(); const doneSet loadDoneSet(); const pending feeds.filter( (item) item.isPublic !doneSet.has(item.mid) ); const progress createProgressBar(() { shouldStop true; }); const result await runQueue(pending, { batchSize: config.batchSize, delay: config.delay, onProgress: progress.update, }); progress.update({ ...result, total: pending.length }); })();这段代码是理解整个项目的钥匙先滚动加载再采集再过滤已完成最后交给队列。shouldStop是队列循环里的一个标志位点击进度浮层上的“停止”按钮时置为 true当前请求结束后队列就不再取新任务。这样做比直接window.location.reload()优雅不会把正在进行的请求掐成半完成状态。3. 核心模块拆解从抓取微博列表到完成状态切换3.1 拿全微博列表别只盯着 DOM很多人第一次写这种脚本会在控制台里粗暴地document.querySelectorAll(.feed-item)然后循环改样式。这个项目没有这么做原因有两点。第一平台页面是懒加载的滚动到页面底部才会请求下一页所以“当前 DOM 里的微博”可能只有十几条但你要处理的有几千条。第二每条微博卡片上有mid属性这是接口操作的关键凭证如果只靠文本定位后面发请求就缺参数了。项目里 collector 的做法是分两步先滚动页面把能加载的微博都加载出来再统一采集。滚动代码大致是这样export async function scrollToLoadAll(maxScrolls 30) { for (let i 0; i maxScrolls; i) { const prevHeight document.documentElement.scrollHeight; window.scrollTo(0, document.documentElement.scrollHeight); await sleep(1200); if (document.documentElement.scrollHeight prevHeight) break; } return collectFeeds(); }这里的sleep是项目里定义的一个 Promise 延时函数1200ms 是给懒加载接口一个缓冲。maxScrolls默认 30 次是因为无限滚动页面加载到后面可能触发“加载更多”按钮超过 30 次还没到底多半是页面卡住或需要手动点一次按钮。如果你要处理的微博特别多可以把 30 改成 50但不要改成 200连续滚动太久也容易触发风控。滚动完成后才真正采集卡片export function collectFeeds() { const cards document.querySelectorAll(div[action-typefeed_list_item]); const feeds []; cards.forEach((card) { const mid card.getAttribute(mid); const text card.querySelector(.node-text, .wbpro-text)?.innerText || ; const visibleButton card.querySelector(a[action-typevisible_btn])?.innerText || ; if (!mid) return; feeds.push({ mid, text, node: card, isPublic: visibleButton.includes(公开), }); }); return feeds.filter((item) item.isPublic); }说明一下里面的两个选择器div[action-typefeed_list_item]是网页版里“一条微博卡片”的通用标记a[action-typevisible_btn]是权限按钮文案会显示“公开”“仅自己可见”“分组可见”。collectFeeds把已经是“自己可见”的微博过滤掉只保留isPublic: true的避免提交无意义请求。参数说明mid是字符串后续作为请求体里的唯一业务标识text不是请求参数而是给二次开发用的比如后面要按关键词跳过某类微博。node是 DOM 节点引用用于成功或失败后原地修改样式。3.2 状态切换请求封装把一条微博改成“仅自己可见”拿到mid后核心动作是发一个 POST 请求。项目里api.js的实现是这样export async function setVisibleOnlySelf(mid, groupId ) { const form new URLSearchParams(); form.append(mid, mid); form.append(visible, 0); // 0 仅自己可见 form.append(groupId, groupId); // 空串表示不分组 form.append(reason, ); const res await fetch(/ajax/statuses/set_visible, { method: POST, credentials: include, headers: { X-Requested-With: XMLHttpRequest, Content-Type: application/x-www-form-urlencoded; charsetUTF-8, }, body: form, }); if (!res.ok) { throw new Error(HTTP ${res.status}); } const json await res.json(); if (json.code ! 100000) { throw new Error(json.msg || 业务错误码 ${json.code}); } return json; }这段代码有几个值得注意的点。第一credentials: include必不可少平台依赖 Cookie 识别登录态缺了它大概率返回 401 或登录跳转。第二Content-Type要手动指定成表单编码否则后端解析不到mid。第三响应里有一个业务码codeHTTP 200 不代表操作成功只有code 100000才算成功。很多新手在这里翻车看到控制台没有红色报错就以为跑完了实际上接口从某个时刻开始一直在报错只是被 catch 吞掉了。参数说明visible0表示改成仅自己可见groupId是给扩展功能留的位子后面想改成“分组可见”时把分组 ID 传进来同时调整visible的值reason一般留空但有些历史接口要求填“内容调整”之类的文案遇到code返回需要 reason 的提示时再补。3.3 并发队列与断点续传几百条任务怎么稳定跑完如果循环发请求一千条微博逐个await体验会很差页面还可能卡死。项目里的queue.js实现了一个很小的并发池同时最多跑 5 个请求export async function runQueue(feeds, { batchSize 5, delay 800, onProgress } {}) { const queue [...feeds]; let done 0; let failed []; async function worker() { while (queue.length) { const feed queue.shift(); if (!feed) break; try { await setVisibleOnlySelf(feed.mid); localStorage.setItem(hide_${feed.mid}, done); done 1; } catch (err) { failed.push({ ...feed, error: err.message }); } if (onProgress) onProgress({ done, failed: failed.length }); await sleep(delay Math.random() * 400); } } const workers Array.from({ length: batchSize }, () worker()); await Promise.all(workers); return { done, failed }; }这里没有第三方库就是固定batchSize个 worker 从同一个任务数组里取任务。为什么要限制并发因为平台接口不是给你无限刷的同时 5 个请求已经能充分利用浏览器连接池再多就容易触发风控。delay Math.random() * 400是刻意加的随机延迟固定延迟很容易被识别成脚本随机化之后更像人工操作。断点续传靠的是 localStorage。在worker里每个任务完成后都会写一个hide_${mid}done的标记。下次再跑collector 可以跳过已完成项export function loadDoneSet() { const keys Object.keys(localStorage).filter((k) k.startsWith(hide_)); return new Set(keys.map((k) k.replace(hide_, ))); } // 使用 const doneSet loadDoneSet(); const pending feeds.filter((f) !doneSet.has(f.mid));这里有个小坑localStorage的 key 只能存字符串如果你把整个任务对象都塞进一个 key页面一卡就会丢中间态。项目选择“一个任务一个标记”即使浏览器崩溃下次启动也能精确跳过已完成部分。失败列表是内存里的failed数组跑完会出现一个导出按钮把失败项下载成 JSON方便针对性地重跑。3.4 页面反馈与进度提示别让脚本看起来像死机没有进度提示的脚本很容易让人怀疑它是不是卡住了。项目里ui.js做的事情很轻在页面右上角插入一个半透明浮层实时显示“已完成 / 总数 / 失败”同时给成功操作的微博卡片加一个灰色边框。export function createProgressBar(onStop) { const root document.createElement(div); root.style.cssText position:fixed;top:20px;right:20px;z-index:99999;padding:12px;background:rgba(0,0,0,0.75);color:#fff;border-radius:8px;font-size:14px;; root.innerHTML span classprogress-text0 / 0/span button classstop-btn停止/button; document.body.appendChild(root); const text root.querySelector(.progress-text); const stopBtn root.querySelector(.stop-btn); stopBtn.addEventListener(click, () onStop()); return { update({ done, total, failed }) { text.textContent ${done} / ${total} / 失败 ${failed}; }, destroy() { root.remove(); }, }; }这段代码本身不难但设计上有一点值得学它把“停止”信号交给调用方。当用户点击停止onStop被触发队列里正在跑的 worker 会在下一个任务开始前检查一个isStopped标志。这样设计后停止脚本不会立刻掐断正在进行的请求而是等当前请求返回后再退出避免产生半完成状态。进度浮层用的是position:fixed所以不管页面滚到哪里都能看到实时进度。4. 避坑与运行我在实际跑这个脚本时踩过的几个坑4.1 三种启动方式先挑最省事的拿到项目后先别急着改代码确认自己能跑通一次再动。根据项目 README 里的说明有三种启动方式方式一浏览器 Console 直粘。打开社交平台主页按 F12 打开开发者工具在 Console 标签页粘贴dist/console.js的内容回车。这种方式适合临时清理缺点是刷新页面后要重新粘一次。优点是不装任何插件最快能验证。方式二用户脚本方式。在浏览器安装用户脚本插件后把dist/weibo-hide-all.user.js的内容复制进去确认match规则和你的主页地址匹配保存后刷新页面。脚本会在右上角出现“开始清理”按钮。这是最推荐的方式因为刷新后脚本还在断点续传的标记也能留存。方式三本地调试源码。如果要改src下的代码可以把仓库根目录用npx serve起一个本地服务打开debug.html页面里加载各模块的 ES Module 版本边改边跑。这个方式接近二次开发适合项目已经能通跑的人。我自己的习惯是先用方式一跑 5 条微博的“试运行”确认接口返回成功、页面状态变化符合预期再切到方式二跑全量。不要一上来就全量跑这是血泪经验。4.2 常见问题排查四条真实的翻车记录这里整理我实际遇到过的四个问题按“现象、原因、解决”一条条说。问题一脚本跑了几十条之后控制台开始连环报业务错误码而且没有任何一条成功。现象是前面明明正常突然所有请求都返回同一个业务码。原因通常不是参数写错而是请求频率太快触发了平台风控。解决方法是停掉脚本等十分钟把config.js里的delay从 800 改成 2000并发batchSize从 5 降到 2再继续。项目的断点续传这时候就起作用了已完成的标记都在不用重头开始。问题二跑完后去个人主页看发现几条微博在“公开”列表里还是公开的。原因是这些微博不在第一次采集的列表里它们是懒加载过程中新出现的或者被顶到了后面。解决方法是跑完第一轮后再执行一次scrollToLoadAll和collectFeeds只对pending继续跑。如果二次扫描还是漏就要检查页面是否有“仅粉丝可见”等状态DOM 里的权限文案和接口返回可能不一致。问题三修改成功后页面上的按钮文字没有变。这个其实不算 bug。脚本直接调了接口但页面前端数据流没有同步更新所以渲染层还是旧状态。解决办法有两个一是成功后主动更新对应 DOM 节点把a[action-typevisible_btn]的文本改成“仅自己可见”二是全部跑完后location.reload()强制刷新。项目默认走前者因为刷新次数多了会额外触发页面请求没必要。问题四localStorage 里标记了一堆完成状态但重跑时脚本仍然全量处理。原因一般是浏览器自动清了 localStorage或者你的测试域名、端口变了导致 key 不在同一个 storage 分区。解决方法是不要把 localStorage 当数据库重跑前先看一眼loadDoneSet()的长度如果明明是 0 但你又记得跑过就去排查浏览器设置。更稳妥的做法是项目提供的“导出失败列表 JSON”功能把上一次任务结果留一份备份。4.3 参数调整建议config.js里的参数决定了你要等多久、会不会触发风控。默认建议值如下参数默认值说明batchSize5同时发出的请求数delay800每个请求完成后的基础等待时间msmaxScrolls30自动滚动次数上限retryTimes3单个任务失败后的重试次数whiteWords[]白名单关键词命中就不处理如果账号历史微博数量在 1000 条以上建议把delay调到 1500batchSize保持 34。两千条微博大概要跑 50 分钟左右期间不要切页面也不要同时开着别的脚本抢请求。如果只想清理最近一年的内容可以在 collector 里加关键词过滤这个放到下一章说。5. 二次开发从“全部自己可见”到“按条件隐藏”这个项目真正的价值不是那一段队列代码而是它给出了一个清晰的边界collector 负责选目标api 负责动作queue 负责节奏。想改成任何“按条件隐藏”都不需要动队列和接口。最常见的需求是“只隐藏转发抽奖”和“只隐藏某段时间的内容”。在collectFeeds返回前加一个过滤条件const SKIP_WORDS [转发抽奖, 已编辑, #超话#]; const cleanFeeds feeds.filter((item) { if (!item.isPublic) return false; return !SKIP_WORDS.some((word) item.text.includes(word)); });这段代码很简单但它把业务规则从“全部隐藏”改成了“按黑名单隐藏”。如果你想反着来只隐藏包含指定关键词的微博把!去掉就行。注意text是采集时从 DOM 抓的如果页面只渲染了部分文案旧微博的信息不全过滤会漏这种情况建议在 collector 里改成从接口分页数据中获取完整文案而不是依赖 DOM。改成“分组可见”比关键词过滤更实用。回到api.js里的groupId参数在配置里加一个分组 ID 来源export async function setVisibleToGroup(mid, groupId) { return setVisibleOnlySelf(mid, groupId); }注意这里的visible参数已经不是0了分组可见对应的是另一个接口值。怎么拿到分组 ID在网页上打开编辑权限的分组选择框抓包那个请求或者看全局状态里的分组列表把gid记录下来。改完这一个小函数脚本就从“全部自己可见”变成了“全部移动到某个分组下”功能性质完全变了。改完代码不要直接跑全量。我的验证方法是先清空 localStorage 里的完成标记挑 3 条不重要的微博单独执行setVisibleOnlySelf(某mid)然后用无痕窗口打开个人主页确认这 3 条已经不在公开列表里。确认没问题再滚动加载几条跑batchSize2的小队列观察错误码。从那以后我每次给账号做隐私清理都强制走一遍“先小批量试跑、看返回码、看页面、再全量跑”的顺序基本没再翻过车。这个项目的好处是它不逼你用某个框架所有逻辑都能拆开改希望帮到你。本文还有配套的精品资源点击获取