简介使用 JavaScript 开发的一款微博批量隐藏工具主要面向希望快速将历史微博批量设置为“仅自己可见”的普通博主、自媒体运营者以及内容维护人员。工具以浏览器脚本形式提供可在登录微博网页版后通过浏览器开发者工具直接调用自动完成批量隐藏省去逐条修改的繁琐操作。压缩包内共含 3 个文件核心 JS 脚本负责批量隐藏逻辑Markdown 文档说明运行环境与调用方式开源许可证文件则标明授权条款整包体积仅 6KB结构轻量适合快速阅读与二次定制。目前已有七百六十一人浏览学习适合有一定浏览器调试经验的前端初学者或普通用户。下载后可以同时获得可运行的脚本、简明使用文档以及开源授权信息为清理个人微博时间线、保护内容隐私提供了一套低成本、易上手的自动化解决思路。1. 几百条历史微博想一键藏起来weibo-hide-all 到底在做什么换工作、清空运营账号、或者单纯不想让老同学考古你的深夜吐槽——很多人面对几百上千条历史微博时第一反应是删号第二反应是一点点删掉。但删号等于把账号连同粉丝、赞、互动记录全部抹掉代价太大。最理性的做法其实是把所有公开微博批量改成「仅自己可见」内容还在自己手里别人却看不到任何痕迹。weibo-hide-all 解决的就是这件事在浏览器里用一段油猴脚本把整页整页的微博逐条从公开改成「自己可见」。它比手动一条条点快几十倍也比直接抓接口更不容易触发风控。这篇文章带你从零把它跑起来顺便把那些会让脚本翻车的暗坑一个个排掉。2. 批量隐藏微博的方案选型为什么油猴脚本比 Python 接口方案活得久2.1 微博的可见性权限一个只对单条微博生效的开关微博每条内容在发布时有一个「可见范围」设置网页端的编辑入口藏在微博卡片右下角的更多菜单里选项大致分为公开、好友可见、仅自己可见等。这个字段只对单条微博生效微博官方并没有提供「全选历史微博→批量设为仅自己可见」的入口。早年微博管理后台有过类似的内容管理能力后来也收窄了。这意味着任何批量操作都必须走两种路径之一要么模拟用户在网页上逐条点击要么模拟浏览器调用微博内部的接口。微博的接口并不像开放平台那样有官方文档和频率配额网页版接口带登录态、带签名参数部分还依赖上下文 token。直接抓接口一个人手动点没事一旦开始批量请求风控模型会识别出脚本行为轻则弹验证码重则临时冻结账号的写操作。2.2 三条技术路线对比油猴脚本、Python 模拟接口、RPA 自动化做这个需求最常见的有三套方案我把它们的差异列在下面方便你按自己的技术背景选。方案实现方式风控距离维护成本适合谁油猴脚本浏览器内注入 JS模拟真人点击菜单按钮近请求由真实浏览器环境发出中微博改版时需微调选择器个人账号本文推荐Python 模拟接口用 requests 复现网页端 AJAX直接改可见性字段远请求特征明显易被识别高签名算法一变就失效不推荐除非你本来就在做逆向研究RPA 自动化Playwright / AutoHotkey 驱动浏览器做点击录制近但重运维成本高中高录制脚本脆弱批量代运营几十个账号的团队选型时有一个容易被忽略的判断维度代码到底是替用户点了按钮还是替用户伪造了请求。油猴脚本属于前者浏览器把完整的 Cookie、User-Agent、点击轨迹都带上了服务端看到的就是一个正常用户在操作。Python 模拟接口属于后者需要自己维护签名、token、请求头一旦漏掉某个动态字段返回的就是一串风控提示。2.3 选型结论贴近浏览器真实操作绕开签名与风控我的建议很明确个人清理账号历史微博不要碰接口逆向那套。油猴脚本几十行就能跑装个 Tampermonkey 扩展进微博主页等着它自己干活就行。不需要本地 Python 环境不需要抓包逆向接口也不需要在命令行和浏览器之间来回倒腾数据。这套方案唯一要啃的骨头是「定位页面元素」。微博前端改版比较频繁卡片样式、菜单层级、按钮文案都变过多次。应对思路也简单脚本里不依赖过于具体的 class 名称尽量用文本匹配来找菜单项这样改版时只需要微调一两行配置。后面第 3 章的代码就是把这种思路落地的完整实现你直接复制就能用。3. 撸一个最小可用的油猴脚本从卡片定位到菜单点击3.1 油猴脚本头配置匹配范围、运行时机、权限声明打开 Tampermonkey 的管理面板新建一个脚本把下面这段作为骨架粘进去。头部信息决定脚本在哪些页面生效、以什么权限运行。// UserScript // name weibo-hide-all 自己可见 // namespace local.weibo.hide // version 0.1.0 // description 将我的主页微博逐条设为仅自己可见 // match https://weibo.com/u/* // match https://weibo.com/* // grant none // run-at document-idle // /UserScriptmatch第一行匹配微博个人主页的u/数字ID路径第二行覆盖所有微博页面避免你从「我的主页」点进一条微博详情后脚本失效。grant none表示不调用 GM 系列 API脚本直接在页面上下文中运行好处是访问 DOM 更快、也不会有沙箱隔离带来的各种跨作用域问题。run-at document-idle等页面基本渲染完再执行避免脚本启动时卡片还没加载出来。注意一点油猴脚本的匹配规则不支持域名通配符https://weibo.com/*已经能覆盖桌面版的所有子路径不需要写成二级域名通配。3.2 核心逻辑文本匹配菜单项与 MutationObserver 自动接力这段代码是脚本的主体。它做的事情可以拆成三步找到页面上的微博卡片 → 点开卡片右上角的更多菜单 → 找到「自己可见」选项并点击。我刻意不用固定的 class 链来找按钮而是用「包含特定文字」来定位菜单项因为文字是产品文案改版频率远低于 CSS 类名。(function () { use strict; // ---- 与页面结构相关的选择器微博改版时优先改这一块 ---- const CARD_SELECTOR .card-wrap, article; const MENU_BUTTON_SELECTOR [class*arrow], [class*more], a.two-toolbar; // ---- 运行参数第 4 章会详细展开 ---- const CONFIG { minStep: 300, // 两次点击之间最小间隔 ms maxStep: 900, // 最大间隔 ms随机抖动防风控 batch: 10, // 每处理 10 条休息一次 restMs: 8000, // 休息时长 ms maxRetry: 3 // 单张卡片失败重试次数 }; const processed new WeakSet(); // 已经处理过的卡片后续不再碰 function sleep(ms) { return new Promise(resolve setTimeout(resolve, ms)); } function randomDelay() { return CONFIG.minStep Math.random() * (CONFIG.maxStep - CONFIG.minStep); } // 在某个容器内按文字查找菜单项并点击 async function pickByText(container, texts) { if (!container) return false; const items [...container.querySelectorAll(li, a, button, span)]; for (let i 0; i texts.length; i) { const target items.find(el el.textContent.trim().includes(texts[i])); if (target) { target.click(); return true; } } return false; } // 处理单张卡片点开菜单 → 点自己可见 async function hideOne(card) { for (let attempt 0; attempt CONFIG.maxRetry; attempt) { try { const btn card.querySelector(MENU_BUTTON_SELECTOR); if (!btn) throw new Error(菜单按钮不存在); btn.click(); await sleep(300 Math.random() * 200); // 一级菜单里直接出现「自己可见」的情况 const menu card.parentElement.querySelector([class*dropdown], [class*menu]); const ok await pickByText(menu, [自己可见]); if (!ok) { // 新版页面一级菜单只有「设置可见范围」需要进二级弹窗 const ok2 await pickByText(menu, [可见范围]); if (ok2) { await sleep(400); await pickByText(document, [仅自己可见]); } else { throw new Error(菜单结构不符合预期); } } // 取消可能出现的确认弹窗点「确认」 await pickByText(document, [确 定, 确定]); processed.add(card); return true; } catch (e) { console.warn(单条处理失败:, e, card); await sleep(500); } } return false; } // 主处理循环收集未处理卡片逐条执行 async function processQueue() { const cards [...document.querySelectorAll(CARD_SELECTOR)] .filter(c !processed.has(c) c.querySelector(a[href*weibo.com])); for (let i 0; i cards.length; i) { const ok await hideOne(cards[i]); if (i 0 i % CONFIG.batch 0) { await sleep(CONFIG.restMs); } if (!ok) { console.warn(连续失败建议检查页面结构); } await sleep(randomDelay()); } } // 监听 DOM 变化滚动加载的新微博自动进入队列 const observer new MutationObserver(() { processQueue(); }); observer.observe(document.body, { childList: true, subtree: true }); // 页面加载完先跑一轮 setTimeout(() processQueue(), 1500); })();逻辑上有个地方需要解释pickByText的候选文本数组我只写了[自己可见]但实际页面里这个菜单项可能叫「设为自己可见」或「仅自己可见」。includes的判断是「包含」而不是「完全等于」所以这三个变体都能命中。后面处理确认弹窗时写[确 定, 确定]是因为微博部分弹窗按钮在两个字之间留了空格视觉上仍然是「确定」。processed用 WeakSet 而不是给卡片加标记属性原因会在避坑章节展开。MutationObserver 监听整个 body 的节点变化滚动加载也好、弹窗渲染也好新出现的卡片都会触发新一轮processQueue不需要手动写滚动逻辑。3.3 把脚本装进浏览器调试面板用法与运行验证保存脚本后打开自己的微博主页注意是weibo.com/u/你的数字id这样的路径油猴角标会显示当前页面有脚本运行。此时不要马上切走打开浏览器开发者工具F12切到 Console 面板观察。脚本每成功处理一条微博卡片会从「公开」状态变成「仅自己可见」页面上那条微博的右下角会出现一个锁形图标或者「仅自己可见」的灰色小字。如果你看到 Console 里不断打印菜单结构异常之类的警告先手动点开一条微博的更多菜单对照文案然后回去微调pickByText里的候选文本。运行过程中页面会像被人隔空操作一样自己滚动、自己点菜单这是正常的。不要碰鼠标去抢操作否则会打断脚本的点击节奏反而容易误触其他菜单项。4. 参数怎么调时间范围、可见性判断与速率控制4.1 只处理指定日期之前的微博全量处理所有历史微博有时候太激进很多人的诉求是「只把某段时间的黑历史藏起来」。给脚本加一层时间过滤逻辑上是在收集卡片时读每条微博的发布时间早于设定日期的才入队。// 在 CONFIG 里加一个配置项 const CONFIG { beforeDate: 2020-01-01, // 空字符串表示不限制处理全部 // ...其他参数不变 }; // 判断卡片时间是否早于设定日期 function isBeforeDate(card) { if (!CONFIG.beforeDate) return true; const timeEl card.querySelector(a[href*weibo.com], time); if (!timeEl || !timeEl.title) return false; const cardTime new Date(timeEl.title.replace(/-/g, /)).getTime(); if (isNaN(cardTime)) return false; const limitTime new Date(CONFIG.beforeDate.replace(/-/g, /)).getTime(); return cardTime limitTime; }微博卡片上的时间有两种展示几秒前、几分钟前这种相对时间以及老微博的绝对时间。相对时间无法直接和日期比较所以脚本读的是timeEl.title里的完整时间字符串。把连字符换成斜杠再传给 Date 构造器是为了兼容 Safari 对时间字符串的解析限制。没读到时间的卡片默认跳过宁可不处理也不要误伤。4.2 判断当前可见性跳过已经「自己可见」的卡片脚本跑第二次、或者中间断掉重跑时已经设为自己可见的微博会被重新处理一遍。纯点「自己可见」没有副作用但会浪费请求次数而且微博对被重复设置的卡片也可能有额外风控。更稳妥的做法是点击菜单后先看菜单里出现的是哪个方向的文案。async function getVisibleState(card) { const btn card.querySelector(MENU_BUTTON_SELECTOR); btn.click(); await sleep(300); const menu card.parentElement.querySelector([class*dropdown], [class*menu]); const text menu ? menu.innerText : ; // 点击菜单外区域把它关掉避免挡住下一次点击 document.body.click(); if (text.includes(设为公开) || text.includes(改为公开)) return self; if (text.includes(自己可见) !text.includes(设为公开)) return public; return unknown; }核心判断逻辑是菜单里如果出现了「设为公开」说明当前这条微博已经在自己可见状态直接跳过如果出现「自己可见」说明当前还是公开状态可以执行切换。把这个函数放到hideOne开头返回self时直接标记为已处理并跳过能显著减少无效请求也让「断线续跑」变得安全。4.3 速率控制参数随机延迟、批量休息与失败重试速率控制是这套脚本能不能活过五分钟的关键。微博对个人账号的日常操作频率有隐形限制连续点击几十次「更多菜单」就会触发滑块验证或者要求重新登录。我实践的推荐参数是间隔 300 到 900 毫秒随机抖动每处理 10 条休息 8 秒。参数推荐值激进值保守值作用minStep / maxStep300 / 900150 / 4001000 / 2000模拟真人点击的不规律节奏batch10205每处理多少条触发一次长休息restMs8000300015000长休息时长maxRetry315单卡片失败后的重试次数出现验证码时立刻把maxStep改成 2000 以上、batch改成 5让脚本节奏立刻慢下来先跑个十分钟观察一下。另外我习惯让每次操作的间隔带随机抖动网上很多脚本翻车都是因为没有抖动、固定 200 毫秒点击一次服务端一眼就能识别成脚本。5. 避坑指南改版、验证码、误伤与中断续跑的五个现场5.1 弹出验证码后脚本假死现象脚本运行几分钟后页面弹出滑块验证之后 Console 里一直打印菜单找不到页面也不再自动滚动。原因连续高频点击触发了风控微博把菜单接口的响应替换成了验证码页面脚本顺着原有逻辑去点卡片菜单时全部落空。解决脚本里加一个全局开关当检测到页面出现「拖动滑块」等文本时进入暂停模式等待人工处理。人工通过验证后手动刷新页面脚本从当前已处理位置继续。另外按第 4 章的保守参数重新调整速率不然验证完了还是会继续撞墙。5.2 新版微博菜单里只剩「设置可见范围」现象点开更多菜单找不到「自己可见」这一项只有一个「设置可见范围」。原因微博改版后把可见性收进了二级弹窗一级菜单只保留入口。不同账号、不同时间线看到的菜单结构不一样这也是为什么脚本不能写死选择器。解决pickByText里先找一级菜单的「可见范围」入口点击后等弹窗渲染出「仅自己可见」选项再点击。代码中await pickByText(document, [仅自己可见])这个分支就是干这个的。如果你发现弹窗里文案变成了「进入仅自己可见」把候选文本数组改成[进入仅自己可见, 仅自己可见]。5.3 滚动加载太快导致漏卡片现象脚本跑完一轮刷新页面发现还有尾部几条微博是公开状态再往下滚动又能刷出新的。原因微博是滚动分页脚本的 MutationObserver 虽然监听了 DOM 变化但页面滚到底部加载下一页需要时间脚本判定「当前收集到的卡片都处理完了」就停了实际还有内容没加载出来。解决在processQueue的最后加一段滚动逻辑把页面滚到底部等待 1 到 2 秒再执行一轮收集。循环几次直到滚动后没有新增卡片为止。也可以手动往下拖几屏等全部历史微博都渲染完再让脚本跑但历史微博特别多时手动拖不现实还是交给自动滚动。5.4 误伤了「好友可见」的微博现象处理完发现有几条原本设置好友可见的微博变成了仅自己可见。原因脚本只判断菜单里有没有「自己可见」关键词而好友可见微博的菜单里也有「设置可见范围」入口进入二级弹窗后脚本看到「仅自己可见」就点了。解决进入二级弹窗后先读取弹窗里当前选中的可见性选项。如果「好友可见」已经是选中态直接关闭弹窗跳过这条。这个判断在getVisibleState函数的text分支里补上if (text.includes(好友可见)) return friends然后让hideOne对这个返回值直接标记完成、不做点击。5.5 执行一半断掉重跑重复弹菜单现象浏览器被关掉或者页面崩溃重新打开脚本后所有处理过的微博又被点了一遍菜单。原因processed是 WeakSet存在内存里页面一关就没了。脚本没有持久化记录处理进度。解决把已处理的 mid 存进 localStorage。每处理一条就localStorage.setItem(weiboHideDone, JSON.stringify([...doneSet]))下次运行时先读这个数组过滤掉已经处理过的卡片。微博卡片的 mid 可以从外层容器的mid属性拿拿不到时用a[href*weibo.com]的 href 作为替代标识。这样断线重跑时脚本会跳过已完成条目只处理新增部分。6. 验证与后悔药让「自己可见」在账号上真正成立脚本跑完不等于任务完成至少要过三关验证才敢说「隐藏成功」。第一关用手机浏览器开无痕模式访问你的主页能看到的每一条微博都是漏网之鱼第二关在微博搜索框里搜你昵称加你曾经发过的关键词正常情况下搜不到已设为自己可见的内容第三关在电脑端打开任意一条历史微博的正文链接显示「作者设置仅自己可见」的提示页说明单条生效。我自己的习惯是跑完之后再执行一次getVisibleState的批量扫描把仍然公开的卡片挑出来重新处理一遍。增量维护方面发布框右下角的可见范围选择器可以固定成「仅自己可见」这样以后新发的微博默认就是隐藏状态不用再跑脚本。最后说后悔药。批量操作前先去微博的账号数据管理入口申请导出全部历史数据备份文件一般是打包下载的链接。万一哪天想把某条微博恢复公开脚本里对应的反向逻辑是把菜单里的「设为公开」点一遍但频率控制要比隐藏时更保守恢复操作触发风控的概率反而更高。这个脚本我维护了挺长一段时间最大的教训是每次微博改版后都要重新确认菜单文案别的都还好。你跑的过程中如果遇到脚本停住先看 Console 报错是不是选择器失效再看是不是验证码拦截大概率就这两件事。希望帮到你。本文还有配套的精品资源点击获取
