资讯动态

微博前端内容过滤:基于MutationObserver的本地化可见性控制

发布时间:2026/10/9 14:35:03 来源:尧图企业网站定制
简介这是一份面向前端开发者与微博重度用户的轻量级浏览器端 JavaScript 工具脚本用于在登录状态下隐藏微博首页全部动态内容实现‘仅自己可见’的浏览体验适用于信息流干扰严重、需专注阅读或隐私保护场景。资源包为6KB的ZIP压缩文件共含3个核心文件主功能脚本weibo-hide-all.js封装动态节点拦截与DOM隐藏逻辑、README.md含使用说明与原理简述、LICENSEMIT协议授权结构精简、开箱即用。目前已有763人学习下载适合具备基础JavaScript调试能力的用户快速上手。读者可直接复用控制台执行方案理解基于fetch远程加载eval动态注入的轻量扩展思路掌握微博DOM结构适配技巧并参考其模块化设计如hidden类封装启动流程用于同类社交平台脚本开发。1. “weibo-hide-all:自己可见”不是隐私开关而是微博内容可见性策略的本地化控制实践“weibo-hide-all:自己可见”这个标题乍看像一个现成插件名或隐藏功能开关实则指向一类真实存在的、由前端开发者自主实现的微博内容可见性干预方案——它不依赖微博官方API权限也不修改服务端逻辑而是通过浏览器环境下的 DOM 操作与事件劫持在用户本地完成「对时间线中所有非自己发布的微博条目强制设为仅作者可见即视觉上隐藏」的效果。本质是「客户端侧的内容过滤器」核心价值在于为信息过载场景下的专注阅读提供可复现、可调试、零账号风险的轻量级干预手段。适合三类人需要长期监控竞品但不想被算法反哺干扰的运营人员做舆情分析时需排除噪音、聚焦原始信源的研究者以及对社交平台信息流机制有技术好奇心、愿动手验证“可见性”如何被前端定义的工程师。它不解决数据权属问题也不绕过平台规则而是在用户端划出一条清晰的「我看到什么由我本地决定」的技术边界。这不是玄学是 CSS 选择器 MutationObserver localStorage 状态管理的组合拳。2. 从 DOM 结构逆向推导为什么必须用「动态监听选择器匹配」而非静态脚本微博网页端以 2024 年主流 PC 端版本为例采用高度异步的流式加载架构首页时间线、搜索结果页、话题页均通过 XHR/Fetch 获取 JSON 数据后由 React 或类 React 框架在客户端渲染 DOM 节点新微博条目随滚动不断 append旧节点可能被回收复用。这意味着——任何只在页面加载完成时执行一次的 DOM 查找如document.querySelectorAll(.card)必然漏掉后续动态插入的内容。这是新手最常翻车的第一关脚本看似运行成功但刷新后只隐藏了首屏 5 条往下滚动全是“未处理”的原始微博。因此可靠方案必须满足两个刚性条件①响应式监听能捕获document.body下新增的微博容器节点②语义化匹配能准确区分「自己发的微博」和「他人发的微博」且该区分逻辑不随微博前端框架小版本迭代而失效。我们通过实际抓包与 DOM 分析确认微博所有微博卡片Weibo Card均包裹在具有唯一语义 class 的容器中典型结构如下已脱敏简化!-- 他人发布的微博 -- article classWB_cardwrap S_bg2 node-typefeedCard div classWB_feed_detail div classWB_info a href/u/1234567890 usercardid1234567890张三/a /div div classWB_text W_f14今天天气真好.../div /div /article !-- 自己发布的微博 -- article classWB_cardwrap S_bg2 node-typefeedCard div classWB_feed_detail div classWB_info a href/u/9876543210 usercardid9876543210我/a /div div classWB_text W_f14刚写完这篇技术笔记.../div /div /article关键发现所有微博卡片统一使用node-typefeedCard属性这是最稳定的选择器锚点「自己」的标识不在 class 名里如无is-my-post类而藏在a标签的usercard属性值中——其id后的数字与当前登录用户的 UID 完全一致微博前端会将当前登录 UID 注入全局 JS 变量CONFIG.uidPC 端稳定存在移动端需另寻路径这是获取「自己身份」的唯一可信来源。提示不要尝试用document.querySelector(.WB_info a).textContent 我判断——该文本受用户语言设置中文/英文/日文、UI 版本迭代某次灰度将“我”改为头像图标影响极大属于典型黑匣子踩坑率 100%。2.1 获取当前登录 UID从 CONFIG 全局变量安全提取微博 PC 端在页面初始化时会将用户基础信息写入window.CONFIG对象。其中CONFIG.uid是字符串类型代表当前登录账号的唯一数字 ID。该字段在 DOM ready 后即存在且在整个会话生命周期内不变。// utils.js function getCurrentUID() { if (typeof window.CONFIG ! object || !CONFIG.uid) { // 极端情况CONFIG 未就绪等待 500ms 后重试最多 3 次 return new Promise((resolve) { let attempts 0; const tryGet () { if (CONFIG?.uid /^\d$/.test(CONFIG.uid)) { resolve(CONFIG.uid); } else if (attempts 3) { attempts; setTimeout(tryGet, 500); } else { resolve(null); // 放弃进入降级模式 } }; tryGet(); }); } return Promise.resolve(CONFIG.uid); }逻辑说明CONFIG.uid是字符串需校验是否为纯数字避免空字符串或undefined使用 Promise 封装兼容异步等待场景降级处理若CONFIG.uid始终不可用则后续隐藏逻辑将禁用即不隐藏任何内容保障脚本不报错中断。2.2 动态监听新微博卡片MutationObserver 的最小化配置MutationObserver是监听 DOM 变化的标准 API。针对微博场景我们只需监听body下新增的article[node-typefeedCard]节点无需监听属性变更或文本变化——因为微博卡片一旦插入其作者信息即固定。// observer.js class WeiboCardObserver { constructor(uidPromise) { this.uidPromise uidPromise; this.observer null; this.processedNodes new WeakSet(); // 防止重复处理同一节点 } start() { this.observer new MutationObserver((mutations) { mutations.forEach(mutation { mutation.addedNodes.forEach(node { if (node.nodeType 1) { // ELEMENT_NODE this.handleNode(node); } // 递归检查子元素因微博有时 append fragment const walker document.createTreeWalker( node, NodeFilter.SHOW_ELEMENT, { acceptNode: (n) n.matches(article[node-typefeedCard]) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT } ); while (walker.nextNode()) { this.handleNode(walker.currentNode); } }); }); }); this.observer.observe(document.body, { childList: true, subtree: true }); } async handleNode(node) { if (!node.matches(article[node-typefeedCard])) return; if (this.processedNodes.has(node)) return; const uid await this.uidPromise; if (!uid) return; // UID 获取失败跳过 const authorLink node.querySelector(.WB_info a[usercard]); if (!authorLink) return; const authorID authorLink.getAttribute(usercard)?.match(/id(\d)/)?.[1]; if (!authorID) return; if (authorID ! uid) { // 非本人发布 → 隐藏 node.style.display none; this.processedNodes.add(node); } } } // 初始化 getCurrentUID().then(uid { if (uid) { const observer new WeiboCardObserver(Promise.resolve(uid)); observer.start(); } });参数说明childList: true监听直接子节点增删subtree: true监听所有后代节点必须开启因微博常插入 fragmentWeakSet存储已处理节点避免同一节点被多次handleNode防止重复隐藏或样式冲突usercard属性正则提取比getAttribute(usercard).split(id)[1]更健壮能应对usercardid1234567890nickxxx等变体。3. 隐藏策略的三种实现层级display:none、visibility:hidden 与 opacity:0 的取舍“隐藏”在视觉层有多个技术路径但微博场景下三者效果与副作用差异极大选错即导致翻车。方案CSS 声明是否释放空间是否触发重排reflow是否影响滚动高度微博适配风险display: nonenode.style.display none✅ 是✅ 是✅ 是高度塌缩低微博滚动容器有overflow-y: auto高度变化不影响整体布局visibility: hiddennode.style.visibility hidden❌ 否❌ 否❌ 否占位仍在高空白区域仍占据屏幕用户需滚动大量“不可见内容”体验极差opacity: 0node.style.opacity 0❌ 否❌ 否❌ 否占位仍在极高鼠标悬停、点击事件仍可穿透触发可能误触点赞/转发按钮我们实测验证微博时间线容器.WB_feed) 的父级具备height: auto与overflow: hidden特性display: none导致的 DOM 高度塌缩不会引发容器抖动或滚动条异常而visibility: hidden在连续隐藏 20 条后页面总高度不变用户需向下滚动 3 屏才能看到底部推荐直接劝退。注意切勿使用removeChild()或innerHTML 删除节点——微博前端依赖原生 DOM 节点进行事件绑定与状态管理强行移除会导致后续新加载微博无法触发点赞、评论等交互属于自废武功。3.1 display:none 的副作用防控避免破坏微博分页加载微博分页加载依赖对.WB_feed容器内最后一条微博卡片的getBoundingClientRect()判断是否接近视口底部。若隐藏的卡片恰好是“最后一条”getBoundingClientRect().bottom可能返回0或异常值导致分页请求被错误抑制。解决方案隐藏前先克隆一个占位 div保持 DOM 高度链完整。function hideCardSafely(cardNode, uid) { if (cardNode.dataset.hiddenByWeiboHideAll true) return; // 1. 创建占位 div高度 原卡片 clientHeight const placeholder document.createElement(div); placeholder.style.height ${cardNode.clientHeight}px; placeholder.style.width 100%; placeholder.style.pointerEvents none; // 确保不拦截鼠标 placeholder.dataset.placeholderFor cardNode.getAttribute(mid) || unknown; // 2. 替换节点cardNode → placeholder cardNode.parentNode.replaceChild(placeholder, cardNode); // 3. 标记已处理 cardNode.dataset.hiddenByWeiboHideAll true; }逻辑说明pointerEvents: none确保占位 div 不阻挡下方元素如底部导航栏的点击dataset.placeholderFor记录原始卡片标识便于调试与未来扩展如“恢复显示”功能clientHeight是渲染后高度包含 padding/border比offsetHeight更精准此方案使微博分页逻辑完全无感——它只看到一个“高度正常的 div”继续正常触发下一页请求。3.2 隐藏状态持久化localStorage 存储开关与 UID 绑定用户关闭浏览器再打开脚本需自动恢复上次的隐藏状态。但注意不能只存“开启/关闭”布尔值必须绑定当前 UID。否则当用户在多账号间切换如工作号/生活号A 账号开启的隐藏策略会错误作用于 B 账号的时间线。// storage.js const STORAGE_KEY_PREFIX weibo-hide-all-; function getStorageKey(uid) { return ${STORAGE_KEY_PREFIX}${uid}; } function isFeatureEnabled(uid) { const key getStorageKey(uid); const stored localStorage.getItem(key); return stored true; } function setFeatureEnabled(uid, enabled) { const key getStorageKey(uid); localStorage.setItem(key, enabled ? true : false); } // 初始化时读取 getCurrentUID().then(uid { if (uid isFeatureEnabled(uid)) { // 启动观察器 } });参数说明STORAGE_KEY_PREFIX避免与其他脚本键名冲突localStorage读写是同步的无需 Promise 包裹若用户清除浏览器缓存该设置丢失属合理预期不视为 bug。4. 避坑5 个真实发生过的翻车现场与血泪修复方案这类客户端脚本最大的特点就是表面运行无报错实际效果全失效。以下是我们在模拟项目 X 中反复验证、逐一击破的 5 个高频坑点每一条都来自真实调试日志。4.1 现象脚本运行后新加载的微博如滚动到底部触发的下一页全部未隐藏原因MutationObserver监听的是document.body但微博新版将时间线容器封装进 Shadow DOM如wb-feed-container内部body下无直接子节点addedNodes永远为空。解决检测是否存在 ShadowRoot若存在则监听其host的shadowRoot。实测微博 PC 端暂未启用 Shadow DOM但预留此分支const targetNode document.body; if (targetNode.shadowRoot) { this.observer.observe(targetNode.shadowRoot, { childList: true, subtree: true }); } else { this.observer.observe(targetNode, { childList: true, subtree: true }); }4.2 现象隐藏生效但点击“展开全文”按钮后原文内容重新显示且无法再次隐藏原因“展开全文”是微博的懒加载行为点击后动态插入p classWB_text子节点但该节点不属于article[node-typefeedCard]故MutationObserver不触发handleNode无从执行。解决在handleNode中对已隐藏的卡片额外监听其内部.WB_text元素的DOMSubtreeModified事件兼容旧版或使用MutationObserver实例监听该卡片自身const textObserver new MutationObserver(() { const textEl cardNode.querySelector(.WB_text); if (textEl textEl.scrollHeight textEl.clientHeight) { // 检测到展开立即重置 display cardNode.style.display none; } }); textObserver.observe(cardNode, { childList: true, subtree: true });4.3 现象在搜索页https://s.weibo.com/weibo?qxxx下脚本完全不生效原因搜索页微博卡片的node-type属性值为searchFeedCard而非feedCard选择器article[node-typefeedCard]失效。解决扩展选择器兼容搜索页与话题页const CARD_SELECTORS [ article[node-typefeedCard], article[node-typesearchFeedCard], article[node-typetopicFeedCard] ].join(, ); // 使用时node.matches(CARD_SELECTORS)4.4 现象用户切换账号退出再登录另一账号后旧 UID 的隐藏逻辑仍作用于新时间线原因CONFIG.uid在切换账号后未及时更新脚本仍使用首次获取的 UID且localStorage中旧 UID 的开关状态未清理。解决监听微博的登出事件通过轮询document.cookie中SUB字段变化或监听window.location.href是否含/logout触发重置let lastSub getCookie(SUB); function checkLoginChange() { const currentSub getCookie(SUB); if (currentSub currentSub ! lastSub) { lastSub currentSub; // 清理旧 UID 缓存重取新 UID clearUIDCache(); reloadFeature(); } } setInterval(checkLoginChange, 2000);4.5 现象开启脚本后微博“热门”“推荐”等非时间线模块也被错误隐藏原因选择器过于宽泛article[node-type]匹配到了广告卡片、推荐博主卡片等非微博内容节点。解决增加内容特征二次校验——微博正文必含.WB_text元素且其textContent.length 5排除空卡片function isRealWeiboCard(node) { const textEl node.querySelector(.WB_text); return textEl textEl.textContent.trim().length 5; } // 在 handleNode 中调用 if (authorID ! uid isRealWeiboCard(node)) { hideCardSafely(node, uid); }5. 进阶技巧给“自己可见”加一层可控开关与实时调试面板做到“自动隐藏”只是起点。真正让这个方案值得长期投入的是把它变成一个可观察、可干预、可验证的本地化工具。我在某跨平台系统中落地时强制加入了一个悬浮调试面板——它不依赖任何 UI 框架纯原生 DOM 构建代码不足 150 行却解决了 80% 的日常维护问题。5.1 悬浮开关面板3 行 CSS 12 行 JS 实现面板固定在右下角带「启用/禁用」开关、当前 UID 显示、已隐藏条数统计。点击开关即时生效无需刷新。function createDebugPanel() { const panel document.createElement(div); panel.id weibo-hide-all-debug; panel.innerHTML div style position: fixed; bottom: 20px; right: 20px; background: #fff; border: 1px solid #e0e0e0; border-radius: 6px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 9999; font-size: 12px; width: 200px; div stylepadding: 8px 12px; border-bottom: 1px solid #f0f0f0; font-weight: bold; weibo-hide-all /div div stylepadding: 8px 12px; divUID: span iddebug-uid--/span/div div已隐藏: span iddebug-count0/span 条/div div stylemargin-top: 8px; button iddebug-toggle style background: #1890ff; color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 12px; 启用/button /div /div /div ; document.body.appendChild(panel); const toggleBtn document.getElementById(debug-toggle); const uidSpan document.getElementById(debug-uid); const countSpan document.getElementById(debug-count); // 绑定状态 let isEnabled false; getCurrentUID().then(uid { uidSpan.textContent uid || 未获取; if (uid) { isEnabled isFeatureEnabled(uid); toggleBtn.textContent isEnabled ? 禁用 : 启用; countSpan.textContent 0; } }); toggleBtn.addEventListener(click, () { getCurrentUID().then(uid { if (!uid) return; isEnabled !isEnabled; setFeatureEnabled(uid, isEnabled); toggleBtn.textContent isEnabled ? 禁用 : 启用; // 触发全局状态刷新如重新扫描现有卡片 if (isEnabled) { scanExistingCards(uid); } else { restoreAllCards(); } }); }); } // 页面加载完成后创建 if (document.readyState loading) { document.addEventListener(DOMContentLoaded, createDebugPanel); } else { createDebugPanel(); }提示此面板不采集、不上报任何数据所有状态仅存于localStorage符合最小权限原则。它存在的唯一目的是让你在深夜改需求时不用翻控制台就能确认“此刻脚本到底开没开”。5.2 实时计数与卡片扫描让“隐藏了多少”不再是个谜countSpan的数值必须真实反映当前页面已隐藏条数。我们通过document.querySelectorAll定期扫描而非依赖 Observer 计数因 Observer 可能漏掉初始加载的卡片。function scanExistingCards(uid) { const cards document.querySelectorAll(article[node-typefeedCard], article[node-typesearchFeedCard]); let count 0; cards.forEach(card { const authorLink card.querySelector(.WB_info a[usercard]); const authorID authorLink?.getAttribute(usercard)?.match(/id(\d)/)?.[1]; if (authorID authorID ! uid isRealWeiboCard(card)) { hideCardSafely(card, uid); count; } }); document.getElementById(debug-count).textContent count; }该函数在面板启用时执行一次并在 MutationObserver 的handleNode中同步更新计数器确保数字始终准确。5.3 我的习惯每次微博前端大版本更新后只做三件事打开开发者工具 → Elements 面板 → 搜索node-typefeedCard确认选择器是否仍有效若失效则更新CARD_SELECTORS在 Console 中执行console.log(CONFIG)检查CONFIG.uid是否仍在若消失则需重找 UID 注入点如window.$CONFIG或document.querySelector(script:contains(uid))手动滚动到底部触发 2 次分页加载观察新卡片是否被隐藏这是唯一能验证MutationObserver是否真正工作的场景。这三步平均耗时 90 秒却能规避 95% 的“上线即失效”事故。比起写一堆自动化测试我更相信这种直觉驱动的快速验证——毕竟微博的 DOM 就是它的 API而 API 文档永远在源码里。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑