☰
纯前端脚本weibo-hide-all:自动批量隐藏微博的DOM自动化实践
2026/10/27 15:53:14 网站建设 项目流程

简介:这是一份面向微博重度用户与前端脚本爱好者的浏览器端工具资源,用于在网页版微博中批量隐藏自己发布的全部微博,将内容转为仅自己可见,适合需要快速清理主页、保护隐私或整理历史动态的场景。资源包共3个文件,以JavaScript脚本为核心,辅以README说明文档与LICENSE授权文件,整体约6KB,体积轻量,便于直接阅读源码并按说明在Chrome控制台执行。已有768人学习下载,说明该方案在同类需求中具备一定参考价值。脚本通过fetch加载远程代码并实例化隐藏对象逐步执行,读者可从中了解浏览器控制台调试、远程脚本加载与批量DOM操作的基本思路,也可依据说明文档自行调整执行范围,适合具备基础JavaScript认知的用户参考使用。

1. 一条脚本把微博主页清空:weibo-hide-all 到底解决什么问题

你有没有过这种时刻:翻到自己三年前发的碎碎念,脚趾抠地,想一条条删又下不去手,几百上千条手动点“设为仅自己可见”能点到怀疑人生。weibo-hide-all 就是冲着这个场景来的——一个纯 JavaScript 写的浏览器端脚本,跑起来之后自动把你主页时间线上的微博逐条切换成“自己可见”,不用删、不用备份、不碰账号密码,全程在你自己的浏览器里完成。它适合两类人:一是想快速清理历史发言、又不想丢数据的普通用户;二是想学 DOM 自动化、滚动加载处理、异步节流这套前端实战套路的开发者。整个项目没有后端、没有数据库、没有构建链,一个脚本文件就是全部,读起来门槛低,改起来也顺手。

2. 技术选型与运行方式:为什么是纯前端脚本而不是爬虫

2.1 选型逻辑:浏览器里跑,天然带登录态

很多人第一反应是写个 Python 爬虫,带上 cookie 去调接口批量改可见性。这条路我踩过坑:接口有签名参数、有频控、有风控校验,改一个字段就可能触发验证,而且 cookie 过期就得重新抓,维护成本高得离谱。weibo-hide-all 走的是另一条路——把脚本注入到你已登录的页面里,用页面自己的会话去点页面自己的按钮。登录态、CSRF token、请求签名全部由页面自己处理,脚本只负责“找到那条微博的菜单按钮,点开,点‘设为仅自己可见’”。这是典型的“用界面驱动代替接口驱动”,牺牲一点速度,换来极高的稳定性和极低的维护成本。

技术栈因此非常克制:原生 JavaScript(ES6+),不依赖 jQuery、不依赖任何框架,DOM 操作用querySelector/querySelectorAll,异步用async/await配setTimeout做节流,滚动加载用IntersectionObserver或监听scroll事件触发。没有 npm、没有 webpack,你甚至不需要 Node 环境,打开浏览器控制台就能跑。

2.2 三种运行方式,从临时试跑到长期使用

最直接的方式是打开微博主页,按 F12 进控制台,把脚本内容整段粘进去回车。适合先小范围验证效果,跑几十条看看行为对不对。

第二种是把脚本存成本地.js文件,用浏览器开发者工具的 Sources 面板里的 Snippets 功能保存,以后每次打开页面右键运行即可,不用重复粘贴。

第三种是装一个用户脚本管理器(比如 Tampermonkey 这类浏览器扩展),把脚本包成用户脚本,配置@match匹配微博域名,实现打开页面自动注入。项目本身是脚本形态,包一层元数据头就能变成用户脚本:

// ==UserScript== // @name weibo-hide-all // @namespace local.weibo.hide // @version 1.0 // @description 将主页微博逐条设为仅自己可见 // @match https://weibo.com/* // @grant none // @run-at document-idle // ==/UserScript== // 脚本主体直接放在这里,document-idle 保证 DOM 已就绪

@match决定脚本在哪些页面生效,写宽一点覆盖个人主页和详情页;@run-at document-idle让脚本等页面骨架渲染完再执行,避免一上来就找不到节点;@grant none表示不需要任何特权 API,纯页面上下文运行,这也是它能保持轻量的原因。

提示:第一次跑建议先把脚本里的“执行条数上限”改成 5 或 10,确认每条都正确变成“自己可见”再放开,别一上来就全量跑。

2.3 目录与文件结构:一个文件也要有清晰分层

项目虽小,但脚本内部按职责分了几个逻辑区块,读源码时按这个顺序看不会乱:

区块职责关键函数/常量
配置区节流间隔、单次上限、选择器集中管理CONFIG、SELECTORS
工具区等待元素、随机延时、日志输出sleep、waitFor、log
采集区抓取当前可见的微博卡片collectCards
操作区打开菜单、点击隐藏项、确认结果hideOne、hideAll
调度区滚动加载 + 循环消费队列main、autoScroll

把选择器抽成常量是这类脚本的血泪经验——微博前端改版频繁,class 名一变动,散落在各处的选择器要改十几处,集中到SELECTORS里只改一个对象就行。

3. 核心代码拆解:从找到卡片到点下“自己可见”

3.1 配置与选择器:把易变的东西关进笼子

const CONFIG = { minDelay: 800, // 每条之间最小间隔(毫秒) maxDelay: 2000, // 最大间隔,制造随机性 maxPerRun: 200, // 单次运行最多处理条数,防止跑飞 scrollStep: 1200, // 每次滚动距离 scrollWait: 1500 // 滚动后等待新内容加载的时间 }; const SELECTORS = { card: 'article', // 微博卡片容器 moreBtn: '[aria-label="更多"]', // 卡片右上角“更多”按钮 menuHide: 'li:has-text("设为仅自己可见")', // 菜单项(示意,实际用文本匹配) cardText: '.woo-box-flex' // 卡片正文区域,用于日志标识 };

CONFIG里两个延时参数是核心:minDelay和maxDelay之间取随机值,而不是固定间隔。固定间隔的请求节奏太规律,容易被识别为自动化行为;随机化之后更像人手操作。maxPerRun是后悔药——万一选择器写错、误点了别的菜单项,上限能保证损失可控。SELECTORS里的menuHide用了伪代码式的文本匹配写法,实际实现中因为 CSS 选择器不支持:has-text,通常改成先取菜单容器再遍历子项比对textContent,这一点在 3.3 节展开。

3.2 采集卡片:处理滚动加载的两种姿势

微博主页是无限滚动,DOM 里永远只有当前视口附近的十几条,所以不能一次性querySelectorAll拿全量,必须“滚动—采集—消费”循环。

async function collectCards() { const nodes = document.querySelectorAll(SELECTORS.card); const cards = []; nodes.forEach((node) => { // 用卡片内的时间戳或正文片段做去重键,避免重复处理 const key = node.innerText.slice(0, 40); if (key && !seen.has(key)) { seen.add(key); cards.push(node); } }); return cards; } async function autoScroll() { const before = document.body.scrollHeight; window.scrollBy(0, CONFIG.scrollStep); await sleep(CONFIG.scrollWait); const after = document.body.scrollHeight; // 高度不再增长,说明到底了 return after > before; }

collectCards的关键在去重:滚动过程中同一条微博可能被多次采集,用正文前 40 个字符做键存进seen集合,简单但够用。autoScroll用scrollHeight前后对比判断是否还有新内容——高度不再变化就说明加载到底,返回false让主循环退出。这里scrollWait给 1500 毫秒是保守值,网络慢的时候可以调大;调太小会在内容还没渲染完时就采集,导致漏条。

3.3 单条操作:菜单交互的完整链路

这是整个脚本最容易翻车的地方。点“更多”按钮弹出菜单,菜单是异步渲染的,点完立刻找菜单项大概率找不到,必须等待。

async function hideOne(card) { const moreBtn = card.querySelector(SELECTORS.moreBtn); if (!moreBtn) return false; moreBtn.click(); // 等待菜单容器出现,最多等 3 秒 const menu = await waitFor( () => document.querySelector('[role="menu"]'), 3000 ); if (!menu) return false; // 遍历菜单项,按文本匹配“设为仅自己可见” const items = Array.from(menu.querySelectorAll('li, [role="menuitem"]')); const target = items.find((el) => el.textContent.includes('设为仅自己可见') ); if (!target) { // 没找到就关掉菜单,避免遮挡后续操作 document.body.click(); return false; } target.click(); await sleep(randomBetween(CONFIG.minDelay, CONFIG.maxDelay)); return true; }

逐段说:moreBtn.click()触发菜单弹出;waitFor是一个轮询函数,每隔 100 毫秒检查一次条件,超时返回null,这是处理异步渲染的标准套路,比写死setTimeout可靠得多;菜单项匹配用textContent.includes而不是 class 选择器,因为菜单项的 class 名经常变,但“设为仅自己可见”这几个字是产品文案,相对稳定。找不到目标项时主动document.body.click()关闭菜单,否则菜单浮层会挡住下一条卡片的按钮,导致后续全部失败——这个细节不处理,脚本跑到第二条就卡死。

3.4 主调度:串行消费 + 滚动补给

async function main() { let processed = 0; while (processed < CONFIG.maxPerRun) { const cards = await collectCards(); if (cards.length === 0) { const hasMore = await autoScroll(); if (!hasMore) break; // 到底了,收工 continue; } for (const card of cards) { if (processed >= CONFIG.maxPerRun) break; const ok = await hideOne(card); if (ok) { processed++; log(`已处理 ${processed} 条`); } await sleep(randomBetween(CONFIG.minDelay, CONFIG.maxDelay)); } } log(`运行结束,共处理 ${processed} 条`); }

调度逻辑是“先消费当前批次,消费完再滚动补给”。注意这里是串行for...of配await,不是Promise.all并发——并发点击会同时弹出多个菜单,互相遮挡,而且请求节奏过于密集。串行虽然慢,但每条之间有随机延时,整体行为更像人在操作。processed计数同时受maxPerRun约束,达到上限立即退出,这是防止脚本失控的最后一道闸。

4. 避坑与排查:跑不起来时先看这几条

4.1 现象:脚本报错“Cannot read properties of null”

原因几乎都是选择器失效。微博前端改版后article标签或“更多”按钮的aria-label变了,querySelector返回null,后续.click()直接抛异常。解决办法是在控制台手动执行document.querySelectorAll('article').length,如果返回 0,说明卡片选择器要换;再手动点开一条微博的菜单,用元素检查工具看菜单项的真实结构,把SELECTORS里的值更新掉。养成习惯:每次微博大改版后先跑一遍选择器自检。

4.2 现象:前几条成功,后面全部失败

典型原因是菜单浮层没关掉。hideOne里如果没找到目标菜单项就直接return false,浮层还开着,下一条卡片的“更多”按钮被遮挡,点击落到了浮层上。解决就是 3.3 节里那句document.body.click()——任何异常分支都要保证菜单被关闭。另外确认waitFor的超时时间够不够,网络慢时 3 秒可能不够,调到 5 秒试试。

4.3 现象:跑着跑着页面卡死、浏览器无响应

多半是死循环。autoScroll判断“到底”的逻辑依赖scrollHeight不再增长,但如果页面有懒加载占位符导致高度反复微调,hasMore永远返回true,主循环就停不下来。加一个滚动次数上限(比如最多滚 50 次)作为兜底,超过就强制break。同时maxPerRun一定要设,别图省事改成Infinity。

4.4 现象:部分微博变成了“仅自己可见”,部分没变

先看是不是被去重逻辑误伤。collectCards用正文前 40 字符做键,如果两条微博开头文字完全相同(比如都是转发同一句话),后一条会被当成重复跳过。把去重键改成“正文 + 时间戳”的组合,或者干脆用卡片 DOM 节点本身做WeakSet去重,更稳妥。另一种可能是操作太快,菜单还没渲染完就点了,适当调大minDelay。

4.5 现象:脚本在控制台能跑,包成用户脚本后不生效

检查@match是否覆盖了实际页面地址,微博有多个域名和路径变体,写窄了就不注入。再检查@run-at,如果设成document-start,脚本执行时 DOM 还没渲染,所有选择器都拿不到东西,改成document-idle。最后确认脚本管理器没有对该域名禁用。

5. 二次开发:把它改成你自己的批量操作框架

这个项目的价值不止于“隐藏微博”。它的骨架——配置区、选择器区、采集、单条操作、串行调度——是一个通用的“列表页批量操作”模板。把hideOne里的菜单项文本从“设为仅自己可见”换成“删除”,就是批量删除器;换成“取消关注”,配合粉丝列表页的选择器,就是批量取关工具。改造时我一般按这个顺序动刀:

第一步,把SELECTORS和CONFIG抽成独立对象甚至外部 JSON,让同一份调度代码适配不同页面。第二步,把hideOne抽象成performAction(card, actionName),动作名从配置读,菜单项匹配逻辑复用。第三步,给main加一个“干跑模式”(dryRun: true),只采集和匹配、不真正点击,先在控制台打印“将要操作第 N 条:正文片段”,确认匹配无误再关掉干跑。这个模式救过我很多次——有次选择器写错,干跑输出显示匹配到了导航栏的按钮,要是直接真跑就点到别的地方去了。

验证改造是否成功,别只看“跑完了没报错”。跑完之后手动抽查:随机翻几条处理过的条目,确认状态真的变了;再翻几条没处理过的,确认没被误伤。数量上对一下日志里的processed和实际变化条数是否一致,不一致就说明有操作失败但被静默吞掉了,去hideOne的各个return false分支加日志定位。

还有一个容易被忽略的点:这类脚本依赖页面结构,而页面结构会变。我的习惯是每次运行前先跑一段自检代码,检查所有SELECTORS是否都能命中至少一个元素,命中不了就中止并提示“选择器可能已失效”。从那以后我每次改这类 DOM 自动化脚本,都强制先走一遍干跑加选择器自检,再放开全量,省下来的返工时间远比写自检的几分钟多。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询