B站消息一键清理:油猴脚本免登录多端同步实现指南

📅 发布时间:2026/9/4 1:31:02
B站消息一键清理:油猴脚本免登录多端同步实现指南
这是一个针对 B 站“消息/通知清理”场景的油猴脚本项目。文章会围绕油猴脚本的安装、功能设计、免登录思路、多端同步逻辑、代码实现要点、自动化验证与排错展开。下文按技术手册方式组织便于直接照着操作。开头这次聊的油猴插件目标很明确一键清理 B 站网页端的未读消息和红点提示作用范围是浏览器里的消息中心、动态提醒这类场景。插件的核心卖点是 v0.2 版本开始支持“多端免登录”不需要在每个浏览器里重新扫码绑定账号装好脚本、完成一次初始化后其余设备直接沿用浏览器自身的登录态脚本只做页面内清理操作不额外收集账号信息。很多油猴用户都有这种体验B 站网页版打开以后通知铃铛上总是挂着未读数字。有些是旧动态、旧回复有些是系统通知真正重要的消息往往被埋在前面。手动点开“查看所有消息”一条条处理太慢全选又没有单独的“全部已读”快捷键。这个插件就是解决了这个“批量置为已读”的需求。v0.2 里主要增加了跨浏览器设备复用配置、清理范围分类、自动触发和手动批量清理四种模式。本文会按安装部署、功能测试、代码结构、多端免登录机制、批量任务设计、常见问题这几块展开。手里有油猴扩展、能打开 B 站网页版的用户都可以照着走一遍整个验证流程不依赖后端服务也不需要购买服务器。1. 核心能力速览先给出一张总表方便判断这个工具是否符合你的场景。能力项说明项目类型浏览器端油猴脚本UserScript适用平台Windows / macOS / Linux 上支持 Tampermonkey、Violentmonkey 的浏览器目标站点B 站网页版消息中心、动态未读、私信列表主要功能一键标记消息已读、按类型清理、自动清理开关、自定义过滤条件多端免登录v0.2 核心改进配置存在浏览器本地不额外绑定账号安装方式油猴扩展内安装脚本文件或本地开发者模式加载API 支持无独立 HTTP API但提供脚本内可调用函数方便二次开发批量任务支持遍历消息列表批量处理可限制单次清理条数建议运行环境最新版 Chrome / Edge / Firefox开启油猴扩展依赖项无 Python、无 Node.js不需要本地服务这里需要提醒的是表格里体现的是普通油猴脚本能做到的事情。具体消息接口结构和页面 DOM 随时会随 B 站改版变化如果打开页面后发现功能失效优先检查脚本更新而不是先怀疑浏览器。1.1 免登录是什么意思很多用户看到“多端免登录”会误以为脚本内置了绕过登录的手段实际上不是。这个脚本运行在已经登录 B 站的浏览器标签页里它使用的是当前页面已有的登录态。所谓“免登录”指的是在不同电脑上使用同一份脚本配置时不需要再去绑定设备、不需要输验证码、不需要扫码授权因为脚本从未依赖单独的第三方会话。它只通过浏览器自身保存的登录态读取当前用户的消息列表并在页面 DOM 上触发“已读”操作。这种设计的两个好处是不需要在油猴脚本里保存任何账号密码降低了泄露风险。换设备后只要浏览器登录状态正常脚本就能工作。需要注意的边界是脚本不能在未登录状态下读取服务端消息也不能在没有登录态的浏览器里“凭空”清理账号消息。所谓免登录是免除“给脚本单独授权”这个步骤。1.2 清理能力边界v0.2 适合处理的是网页端可见的、用户自己账号下的消息。例如回复我的内容 我的内容收到的赞系统通知私信列表已读标记它不会做的事情包括删除他人评论、批量拉黑用户、修改账号设置、抓取非公开数据。如果后续有类似“自动删除历史评论”的需求必须在合法的用户操作流程内重新设计不能简单通过模拟点击绕过确认框。任何涉及账号内容的自动化操作都要保证操作可撤销、可审计、在用户本人授权范围内进行。2. 适用场景与使用边界2.1 谁适合用如果你经常同时开着 B 站首页、动态页、私信页但又不想一直点掉红点这个脚本就很合适。它适合以下人群每天需要频繁打开 B 站网页版处理消息的运营人员。管着多个浏览器环境希望一键同步清理配置的测试人员。不习惯被未读数字干扰想保持消息列表干净的内容创作者。准备用油猴脚本做浏览器自动化练习的开发者。以运营场景为例一个账号如果每天新增几百条互动消息纯手工点已读会消耗大量时间。脚本可以将“点开消息中心、逐条进入会话、找到已读按钮、返回列表”这一套重复流程压缩成一次点击把操作时间从几分钟降低到十几秒。当然具体时间取决于网络状态和列表长度。2.2 边界和不适合的场景不适合的场景是期望清理后服务端页面上所有历史消息全部消失——脚本通常只标记已读不删除远端数据。需要跨账号批量处理——油猴脚本一般运行在单账号登录上下文内不支持并行处理多个账号。需要后台定时清理而且电脑经常关机——油猴脚本受浏览器生命周期约束浏览器没打开就没有执行时机。需要清理移动 App 内消息——脚本跑在网页端无法控制 App。2.3 合规和隐私提醒这里要特别强调即使脚本目标是“清理自己的消息”也必须注意不要做以下事情不要伪造他人身份执行操作。不要使用脚本绕过 B 站的验证码或风控机制。不要通过脚本抓取其他用户非公开数据。不要在公共电脑上长期开启“自动清理”避免操作到不该处理的会话。如果要把脚本分享给团队或发布到社区建议在说明里写清楚这是一个“当前账号网页端已读辅助工具”并提示用户自行熟悉清理操作产生的不可逆影响。涉及消息记录、私信等敏感内容时发布前做效果复核避免误点删除。3. 环境准备与前置条件3.1 浏览器和油猴扩展这个脚本依赖油猴扩展运行目前主流选择是Tampermonkey兼容性好支持 Chrome/Edge/Firefox 等多个浏览器Violentmonkey开源版本界面清爽适合注重审查的开发者我不建议用已经停止维护的旧版 Greasemonkey 测试新脚本。虽然 Greasemonkey 对旧脚本兼容性不错但 B 站页面已经大量使用现代 JavaScript旧脚本引擎可能无法正确处理页面动态渲染。建议使用较新版本浏览器。以 Chrome/Edge 浏览器为例如果浏览器版本过老可能不支持MutationObserver、async/await等特性会导致脚本解析失败。3.2 浏览器的开发者模式在本地安装脚本文件时不需要开启开发者模式。但如果要做脚本调试则需要打开开发者工具快捷键一般是 F12 或 CtrlShiftI。为了更稳定地观察页面节点变化建议在开发者工具里关闭缓存。具体方法是打开开发者工具 → Network 选项卡 → 勾选 Disable cache。3.3 网络和登录环境要求使用脚本前请确认以下条件浏览器能正常访问 B 站网页版。当前账号已经登录。需要清理的消息类型在网页端可见。如果页面使用了旧版消息入口脚本可能需要适配不同 URL。由于浏览器插件的持久化存储容量有限一般不建议在脚本内保存大量日志。v0.2 把配置项限制在几 KB 以内绝大多数浏览器都能稳定保存。4. 安装部署与启动方式4.1 在线安装脚本如果脚本已经发布到 Greasy Fork 或类似脚本市场安装流程如下打开 Greasy Fork 页面搜索脚本名称。点击“安装此脚本”。油猴扩展弹出安装确认页面。点击“安装”。打开 B 站消息中心页面确认脚本是否注入。安装完成后油猴扩展图标上会出现角标数字表示当前页面有脚本运行。如果图标没有角标可能是脚本的匹配规则没有覆盖当前页面。一个规范的脚本元数据会包括名称、命名空间、匹配 URL、版本号、授权信息。以下是一个入门模板// UserScript // name B站消息清理助手 // namespace https://example.com/bilibili-cleaner // version 0.2.0 // description 清理B站网页端未读消息支持多端浏览器本地配置同步 // author YourName // match https://www.bilibili.com/* // match https://message.bilibili.com/* // grant GM_setValue // grant GM_getValue // grant GM_registerMenuCommand // run-at document-idle // /UserScript注意namespace需要替换成你自己的域名或项目地址match需要按实际站点地址调整不能照抄。GM_setValue和GM_getValue是油猴扩展的存储 API用于保存配置。如果脚本不需要在这些扩展之间移植可以不加。4.2 本地打包加载对于开发版本通常会选择本地加载因为每次修改后可以直接在管理器里点“重新加载”不需要重复安装。本地加载有两种方式直接把.user.js文件拖进油猴扩展管理面板。在 Tampermonkey 管理面板点击“添加新脚本”把代码整体粘贴进去。推荐第二种方式便于在粘贴前顺手修改元数据。粘贴后点击 CtrlS 保存脚本会立即生效。刷新 B 站页面后即可看到效果。如果不是在 Greasy Fork 安装浏览器可能提示“扩展程序需要审查”这是正常现象。需要确认油猴扩展的安装来源是官方商店避免下载到修改过的恶意扩展。4.3 多端部署的两种方案多端部署指的是在办公室电脑、家用电脑等多个浏览器环境加载同一份脚本配置。重点在“配置同步”而不是“消息状态云端同步”。方案一手动导出导入配置油猴扩展本身不提供脚本配置的云同步后端但脚本可以在菜单里提供“导出配置”和“导入配置”选项。操作方式是在 A 电脑上点击油猴扩展菜单中的“导出配置”。浏览器会下载一个 JSON 文件。将文件通过内部网盘或本地存储发送到 B 电脑。在 B 电脑上点击“导入配置”。脚本读取 JSON 并写入本地存储。方案二使用脚本编辑器的同步功能Tampermonkey 在开启同步后可以把脚本本体和设置同步到浏览器账号。此方式适合同品牌浏览器环境但不同浏览器之间仍然无法直接同步。如果只是希望脚本代码一致用 Git 仓库保存脚本源码是更稳妥的办法。通过在多个设备上安装同一份脚本源文件功能行为就能保持一致。这里“免登录”的真正价值在于脚本配置里不需要包含用户身份信息。同步的只是清理规则、过滤关键词、启用状态、页面选择器配置不包含登录令牌。因此配置文件泄露不会直接导致账号被登录可以显著降低同步风险。4.4 启动和关闭脚本不需要单独启动服务。打开 B 站网页版油猴扩展会自动注入脚本。如果页面已经打开脚本更新后需要手动刷新页面生效。如果想临时关闭点击扩展图标在弹出的菜单里关闭对应脚本即可。不要直接卸载脚本卸载后本地保存的配置不会自动清理需要在浏览器开发者工具里手动清除对应站点的本地存储。5. 功能测试与效果验证5.1 测试前的准备工作测试前准备一个干净的测试页面打开 B 站消息中心页。确认登录状态正常。确认至少存在 2 条以上未读消息。打开浏览器控制台输入console.log(document.readyState)返回complete表示页面加载完成。测试过程中建议不要同时开启其他 B 站自动化脚本避免多个脚本同时操作 DOM 导致节点定位失败。5.2 一键清理测试一键清理是最基础的功能。点击插件菜单中的“清理全部消息”后脚本会遍历当前页面的消息列表对每条消息执行“标记已读”操作。判断成功的标准页面未读数字清零。消息项的未读标识样式消失。控制台没有报错。如果第一次点击后未读数字没有变化优先检查当前页面是否处于“全部消息”Tab。B 站消息中心有多种子页面脚本默认只处理消息列表页 DOM。如果停留在“系统通知”页清理函数可能找不到目标节点。此时可以在控制台执行以下脚本查看脚本是否已经注入window.__biliCleaner ! undefined如果返回undefined说明脚本没有在当前页面运行需要检查匹配规则。5.3 按类型清理测试v0.2 里可以按消息类型清理比如只清理“收到的赞”保留“私信”。测试时先设置过滤条件再执行清理。操作步骤点击脚本菜单进入设置面板。勾选需要清理的消息类型。点击保存。切到对应 Tab。触发清理。在开发过程中可以通过观察 DOM 来确认消息类型。B 站通知列表的每条消息通常会包含类型图标或分组标题脚本需要用文本匹配或 CSS 类名过滤。由于 B 站前端经常改动类名建议在代码里同时支持两种定位方式文本关键词匹配。>function getMessageType(node) { const text node.innerText || ; if (text.includes(赞)) { return like; } if (text.includes(回复)) { return reply; } return unknown; }这里的getMessageType只是一个辅助函数实际项目中还需要补充节点范围限制避免把页面其他区域的文本误判为消息类型。5.4 自动清理测试自动清理功能会在页面加载完成后等待一段时间再触发清理动作。这样做的原因是 B 站页面内容可能是异步渲染如果脚本在 DOM 还没生成时就执行会找不到目标节点。测试时需要验证以下场景正常进入消息中心页面加载后自动触发。首次进入页面时禁用自动清理手动点击时才触发。消息列表为空时不触发额外动作。脚本循环执行时不会产生无限循环。自动清理的标准实现可以这样写function waitForList(timeout 8000) { return new Promise((resolve) { const startedAt Date.now(); const timer setInterval(() { const list document.querySelector(.message-list); if (list || Date.now() - startedAt timeout) { clearInterval(timer); resolve(list); } }, 500); }); }waitForList会每 500ms 检查一次消息列表节点是否存在。超时后仍然 resolve因此调用方必须判断 list 是否为空。自动清理测试是否成功的判断标准是刷新页面后 3 到 5 秒内页面消息未读数从 N 变成 0。如果超过 10 秒仍然有未读数可能是选择器已失效也可能是网络请求延迟。5.5 清理条数限制测试批量处理消息时有一个潜在风险如果当前未读消息过多脚本一次性对几百条节点连续操作可能造成页面卡顿也可能触发风控限制。v0.2 建议在配置里加入“单次最大清理条数”。默认值可以设为 50测试时先设置成 3观察脚本是否只清理前 3 条。实现逻辑大致是function cleanMessages(maxCount 50) { const items getMessageItems(); const targets items.filter(isUnread).slice(0, maxCount); targets.forEach(markAsRead); }这里maxCount需要结合列表滚动位置调整。如果列表只加载了前 20 条节点即使设置 100 条也无法处理后续数据。如果要处理超过首屏加载数量的消息需要加入滚动加载机制。比较稳妥的思路是滚动到底部 → 等待列表长度增加 → 再次清理 → 直到没有新增项。5.6 网络请求模式验证油猴脚本有时会通过GM_xmlhttpRequest直接请求站点接口而不是模拟点击 DOM。测试这种模式时要重点确认请求是否需要额外鉴权参数。接口返回的数据结构是否稳定。是否需要在请求头里添加 Referer。在开发者工具的 Network 面板中观察请求如果请求返回 401 或 403说明当前会话失效或缺少必要的请求头。此时不要强行绕过正确的处理是提示用户重新登录。6. 多端免登录机制与持久化设计6.1 登录态来源多端免登录的技术本质是“不新建登录态”。很多脚本在处理网站消息时会设计独立的“登录扫码”流程但这样会带来几个问题令牌保存在脚本存储中容易泄露刷新令牌逻辑复杂更换设备后需要重新扫码。B 站清理助手 v0.2 放弃了独立授权直接依赖浏览器当前站点 Cookie 和本地会话。这里有一张简单对比设计方式是否便于多端部署安全风险脚本内保存用户账号密码低多端需要重复录入高脚本申请独立 Token中需要刷新逻辑中依赖浏览器已有登录态高浏览器登录即用低采用第三种方式后不同浏览器环境只要都登录了同一个账号脚本就能正常读取该账号下的消息中心内容。所谓“免登录”正是省去了给脚本单独授权的环节。不过要提醒一句多端免登录不等于多端可在同一时刻并行操作。两个浏览器同时处理同一个账号的同一批消息时服务端数据可能发生竞争最终结果取决于服务器处理顺序。比如 A 浏览器把消息标记为已读B 浏览器刷新后又看到已读状态这都是正常现象。6.2 本地配置存储脚本的配置信息建议存储在油猴扩展提供的GM_setValue/GM_getValue中而不是完全依赖localStorage。原因是localStorage是按域名隔离的B 站页面的localStorage如果被站点脚本意外清理会导致脚本配置丢失。GM_setValue存储在油猴扩展自己的数据目录中容量稍微大一些也不容易被页面脚本误删。保存配置的示例const DEFAULT_CONFIG { autoClean: false, maxCleanCount: 50, filterLikes: true, filterReplies: true, filterSystem: false }; function saveConfig(cfg) { const merged Object.assign({}, DEFAULT_CONFIG, cfg); GM_setValue(biliCleanerConfig, JSON.stringify(merged)); } function loadConfig() { const stored GM_getValue(biliCleanerConfig, ); if (!stored) { return Object.assign({}, DEFAULT_CONFIG); } try { return Object.assign({}, DEFAULT_CONFIG, JSON.parse(stored)); } catch (e) { return Object.assign({}, DEFAULT_CONFIG); } }通过合并默认配置可以有效避免旧版本配置文件缺少新字段导致的错误。如果 JSON 解析失败则回退到默认值。注意GM_setValue存储的值不能是对象只能是字符串或基础类型因此代码里先用JSON.stringify转换。如果油猴扩展版本支持直接存储对象也建议统一使用 JSON 字符串格式便于兼容不同扩展。6.3 跨浏览器导入导出为了让多端部署更顺畅v0.2 在扩展菜单里加入了“导出配置 JSON”和“导入配置 JSON”两个入口。菜单注册示例GM_registerMenuCommand(导出配置, () { const cfg loadConfig(); const blob new Blob([JSON.stringify(cfg, null, 2)], { type: application/json }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download bili-cleaner-config.json; a.click(); URL.revokeObjectURL(url); }); GM_registerMenuCommand(导入配置, () { const input document.createElement(input); input.type file; input.accept application/json; input.onchange (event) { const file event.target.files[0]; if (!file) { return; } const reader new FileReader(); reader.onload () { try { const cfg JSON.parse(reader.result); saveConfig(cfg); alert(导入成功); } catch (error) { alert(配置文件格式错误); } }; reader.readAsText(file); }; input.click(); });这段代码可以直接在油猴脚本里运行但会用到GM_registerMenuCommand需要在元数据里声明授权。导入导出时要注意脚本只导入配置不导入任何账号身份数据。配置文件里不存在密码、Cookie、Token 等字段。6.4 多端同步的注意事项不同浏览器对用户脚本的存储策略不同在端与端之间复制配置时需要注意以下几点如果 B 电脑上的浏览器版本过低可能不支持Blob下载要改用油猴扩展的GM_download。导入配置前应备份当前配置避免误覆盖。如果配置里包含自定义正则表达式在不同系统上转义规则可能导致差异建议统一使用 JSON 保存。在团队内部协作时可以把配置文件模板提交到代码仓库再由各成员导入。为了标识配置版本可以在配置文件里加一个version字段导入时检查版本号避免旧配置覆盖新功能参数。7. 脚本的关键逻辑与代码示例7.1 页面消息项的通用定位思路B 站消息中心消息项结构通常是外层列表容器 → 列表项元素 → 内部文本、按钮、链接。假设不确定最新 CSS 类名建议采用“语义化定位”优先通过容器中包含的文本或 aria-label 判断消息类型再定位按钮。下面用一个不依赖 B 站内部类名的示例说明function findUnreadItems(container) { const possibleItems container.querySelectorAll(li, .list-item, [rolelistitem]); const unreadItems []; possibleItems.forEach((item) { const style window.getComputedStyle(item); if (style.display none || style.visibility hidden) { return; } if (item.querySelector(.unread) || /未读/.test(item.getAttribute(aria-label))) { unreadItems.push(item); } }); return unreadItems; }这里querySelectorAll的选择器需要按页面实际结构调整。如果脚本运行时报 “container is null”说明容器选择器没有匹配到任何元素。7.2 模拟点击已读按钮标记已读最常见方式是找到未读消息对应的“已读”按钮然后触发 click。触发 click 要区分普通按钮和带事件委托的按钮。如果页面 JavaScript 通过事件委托监听直接调用element.click()可能不触发。更稳妥的方式是派发 MouseEventfunction triggerClick(el) { if (!el) { return; } const event new MouseEvent(click, { bubbles: true, cancelable: true, view: window }); el.dispatchEvent(event); }通过dispatchEvent让事件冒泡到上层监听器可以兼容更多页面逻辑。注意如果按钮上同时绑定了pointerdown或pointerup事件仅派发 click 可能不够。此时需要模拟完整的指针事件序列。但这个复杂度较高实际测试中建议先检查页面是否支持键盘操作比如按钮聚焦后按回车。7.3 控制台日志和进度提示批量处理消息时最好在脚本中输出简要日志方便判断是否卡住function logProgress(current, total) { console.log([BiliCleaner] 已处理 ${current}/${total}); }但不要把日志输出到页面上避免影响页面原有元素。可以借助油猴 UI 在角落显示一个轻量状态条状态条使用固定定位Z-index 不宜过高。最稳定的是用原生 JavaScript 创建一个 divfunction createStatusBar() { const bar document.createElement(div); bar.id bili-cleaner-status; bar.style.cssText position:fixed;z-index:99999;right:16px;bottom:16px;background:#00aeec;color:#fff;padding:8px 12px;border-radius:4px;font-size:12px;display:none;; document.body.appendChild(bar); return bar; } function showStatus(text) { const bar document.getElementById(bili-cleaner-status); if (!bar) { return; } bar.textContent text; bar.style.display block; }页面刷新后状态条会自动消失。如果脚本是异步处理列表状态条可以帮助观察任务是否完成。7.4 MutationObserver 自动响应新消息B 站网页端可能在用户停留在页面时通过 WebSocket 推送新消息。如果希望在新消息出现时自动清理可以使用 MutationObserver 监听列表容器function observeList(listContainer, callback) { const observer new MutationObserver((mutations) { for (const mutation of mutations) { if (mutation.addedNodes.length 0) { callback(); break; } } }); observer.observe(listContainer, { childList: true, subtree: true }); return observer; }Observer 的缺点是可能会频繁触发例如页面滚动时动态加载列表项也会触发回调。因此回调函数里要增加节流或防抖处理避免在短时间内执行多次清理动作。7.5 防重复执行锁清理动作执行过程中如果用户又手动点击了按钮会同时发起多个清理任务容易造成页面元素重复更新。可以在脚本中加入一个简单状态锁let cleaning false; async function runCleanTask() { if (cleaning) { console.warn([BiliCleaner] 清理任务正在执行中); return; } cleaning true; try { await doClean(); } finally { cleaning false; } }cleaning标记只存在于当前页面运行时。如果用户刷新页面标记会重置这是合理的因为旧页面的清理任务已经随页面销毁。如果要在任务执行期间阻止用户重复点击可以在清理结束时再恢复按钮置灰状态。8. 接口 API 与批量任务设计8.1 不提供独立 HTTP API从 v0.2 的架构来看这个脚本本身不提供外部 HTTP API。它是运行在浏览器里的用户脚本无法单独作为服务被其他程序调用。但脚本内部提供了可调用函数例如window.biliCleaner { runClean: runCleanTask, cleanAll: cleanMessages, exportConfig: exportConfig, importConfig: importConfig, getConfig: loadConfig, setConfig: saveConfig };如果把window.biliCleaner暴露到页面全局其他同页面运行的脚本也能调用这些函数。但要注意命名冲突建议挂到一个不太可能被覆盖的命名空间比如window.BiliCleanerApp。如果想提供跨进程 API需要另写一个浏览器扩展或本地 Node 服务。比如可以写一个最小 HTTP 服务接收 POST 请求后调用浏览器的 CDP 接口来触发油猴脚本函数。这种设计复杂度高不推荐在没有明确需求的情况下实现。8.2 批量任务的通用队列设计虽然油猴脚本不一定需要后台任务队列但如果要处理成百上千条消息最好还是构建一个简单的任务队列。一个可用队列结构如下class CleanTaskQueue { constructor(concurrency 1) { this.concurrency concurrency; this.queue []; this.running 0; } add(task) { this.queue.push(task); this.next(); } next() { while (this.running this.concurrency this.queue.length 0) { const task this.queue.shift(); this.running; task() .catch((err) console.error([BiliCleaner] task failed, err)) .finally(() { this.running--; this.next(); }); } } }使用并发数 1 是因为消息清理操作本身涉及 DOM 变更并发执行多个任务可能造成页面状态混乱。设置成按顺序执行更稳妥。8.3 批量任务失败重试策略清理操作如果失败可能是页面节点已被移除这时重试没有意义。如果是网络请求失败可以加入指数退避function retry(fn, times 3, delay 1000) { return fn().catch((error) { if (times 1) { throw error; } return new Promise((resolve) setTimeout(resolve, delay)) .then(() retry(fn, times - 1, delay * 2)); }); }这里指数退避的延迟分别是 1s、2s、4s适合页面请求类任务。如果清理操作是同步 DOM 操作添加重试反而会拖慢速度建议区分场景使用。8.4 任务状态回显为了保证批量任务可视化可以把任务状态写到状态条或者控制台。比较实用的方式是记录两个数字待处理数量和已完成数量。function runTasks(items) { const total items.length; let completed 0; items.forEach((item) { try { markAsRead(item); completed; showStatus(剩余 ${total - completed} 条); } catch (e) { console.warn(e); } }); if (completed total) { showStatus(清理完成); } }这个版本的代码没有异步等待对大量 DOM 操作可能导致页面长时间阻塞。如果消息数量在 50 条以内影响不大如果超过 200 条建议改成每隔一段时间处理一批给浏览器 UI 留下响应时间。8.5 API 调用示例模板当脚本确实需要调用 B 站页面内部接口时可以参考下面的通用模板。由于各接口参数不同不能直接用于生产环境async function requestPageApi(apiPath, params {}) { const query new URLSearchParams(params).toString(); const url ${apiPath}?${query}; const resp await fetch(url, { method: GET, credentials: include, headers: { Accept: application/json } }); if (!resp.ok) { throw new Error(HTTP ${resp.status}); } return resp.json(); }这里的credentials: include用于携带当前页面 Cookie。但要注意即使请求成功响应内容是否可信还需要验证。不能因为在页面上下文就忽略返回值的校验。9. 资源占用与性能观察9.1 如何观察脚本运行时资源占用用户脚本本身占用的内存通常很小比较关键的是页面消息列表渲染带来的内存开销。如果消息列表包含大量节点浏览器会变卡。观察方式打开开发者工具 → Performance 面板。点击录制按钮。手动触发一次清理任务。停止录制。查看主线程的 JavaScript 执行时间。如果清理任务执行时间超过 500ms说明脚本当前循环和 DOM 操作耗时较长需要拆分任务。对用户脚本来说单独看“脚本占用多少显存”不适用因为脚本运行在浏览器渲染进程中不涉及 GPU 显存计算。如果有人说某某油猴脚本“显存占用 7G”那基本是在混淆概念。9.2 CPU 与浏览器事件循环影响频繁操作 DOM 会拉高 CPU 使用率。特别是使用 MutationObserver 时如果回调里有较重的计算逻辑可能导致页面滚动卡顿。建议在回调函数中减少高频操作例如不要每次 mutation 都执行完整的列表扫描而是用一个“脏标记”延迟到空闲时间执行let needClean false; function onMutation() { if (needClean) { return; } needClean true; requestIdleCallback(() { try { runCleanTask(); } finally { needClean false; } }, { timeout: 2000 }); }9.3 清理大量消息时的性能优化一次性处理大量消息可以采用“分批清理法”。每批处理 20 到 30 条。每批之间等待 200ms。每次处理前重新获取消息列表避免操作已被移除的节点。如果列表需要滚动加载则优先滚动暂停清理。分批处理能明显降低页面无响应的概率。批与批之间用setTimeout或requestAnimationFrame隔开即可。9.4 端口占用的扩展补充油猴脚本一般不监听端口所以不会出现端口冲突问题。但是如果用浏览器开发者工具的远程调试接口来驱动脚本比如配合 Selenium 或 Puppeteer就需要注意 9222 远程调试端口是否被占用。如果遇到端口被占用可以改成其他端口启动浏览器chrome --remote-debugging-port9223这个命令只是通用调试参数实际使用需要换成你自己系统里的 Chrome 可执行文件路径避免路径不一致导致启动失败。10. 常见问题与排查方法问题现象可能原因排查方式解决方案油猴图标有角标但页面无按钮match 规则匹配但脚本执行时机不对打开控制台查看脚本版本把 run-at 改为 document-idle刷新页面清理后未读数字不消失页面异步更新消息列表已读状态由服务端返回后更新查看 Network 请求和响应等待服务端响应再刷新页面不要重复点击部分消息被跳过消息列表采用懒加载未滚动加载的项不在 DOM 中打开页面后先滚动到底部加入滚动加载逻辑或分多次清理脚本设置了自动清理但未触发B 站消息中心页面是单页应用脚本只执行一次观察 URL 是否由 hash 路由改变监听 hashchange 和 popstate 事件本地配置文件无法导入文件内容不是合法 JSON字段被截断用格式化工具检查 JSON修复文件后重新导入更换电脑后配置不生效插入脚本没被安装或配置存储域名不同对比两个环境的脚本版本和配置面板确认两台设备使用相同脚本版本菜单命令不显示油猴扩展未授权 GM_registerMenuCommand查看脚本配置里的 grant在 grant 列表中加入相应权限控制台出现 “Cannot read properties of null”DOM 节点选择器匹配失败检查容器元素是否存在更新选择器或增加等待时间浏览器提示脚本来源不可信安装渠道不是 Greasy Fork 官方页面比较下载文件内容尽量从可信来源安装清理过程页面很卡一次性处理过多 DOM 操作打开 Performance 面板检查主线程耗时分批处理减少单次操作数11. 最佳实践与使用建议11.1 第一次先小参数验证生产使用之前先把“单次最大清理条数”设置成 5 条确认所有功能正常后再调大。不要上来就清理几百条尤其是在不确定页面节点结构是否匹配的情况下。11.2 保留一份最小可运行配置建议把脚本源码和一份最小配置模板保存在代码仓库中。最小配置模板只包含默认清理类型和开关不包含任何个人信息。这样即使误操作了配置文件也能从模板快速恢复。11.3 消息清理要有日志和撤回意识油猴脚本处理消息时最好在本地保存一份操作日志记录操作时间和类型。标记已读的操作一般是可逆的但如果未来扩展出删除或隐藏评论功能就必须给每个操作加“二次确认”和“操作记录”。这里再次强调用户脚本只能做浏览器端自动化无法保证服务端数据完整可恢复。对不可逆操作一定要保持谨慎。11.4 保持脚本的可维护性油猴脚本的生命周期往往很短因为前端页面会改版。为了让脚本存活更久建议独立封装“选择器配置”和“逻辑代码”。页面改版时只修改选择器配置不重写整套逻辑。例如const SELECTORS { listContainer: .message-list, messageItem: .message-item, unreadMark: .unread, clearButton: .clear-all };后续如果 B 站消息中心的类名更换只需要更新SELECTORS对象。这种写法的好处是脚本的其余逻辑不需要改动排查问题也更方便。11.5 规范发布和声明如果要在 Greasy Fork 等平台发布建议在脚本简介里写清楚以下几点功能范围仅辅助标记当前账号的网页端消息为已读。已知限制不支持 App 端受浏览器运行状态影响。兼容性建议在 Tampermonkey 或 Violentmonkey 上运行。隐私说明不收集账号密码不采集用户数据不向第三方发送请求。免责声明页面结构和站点规则变化可能导致功能失效。这样既方便用户判断也降低了脚本被误用的风险。12. 后续可以扩展的方向如果对 v0.2 之后的功能有期待几个可行的方向是12.1 规则引擎化把“只清理赞、不清理私信、保留前三日系统通知”这类需求改成可视化规则配置。用户可以在弹窗中勾选条件和动作脚本动态生成清理策略。12.2 浏览器扩展化油猴脚本的存储边界和跨浏览器能力有限。把它们迁移成 Manifest V3 浏览器扩展后可以获得更多后台能力比如定时清理、多标签页同步、自定义右键菜单。但开发复杂度也会相应增加。12.3 支持移动端浏览器Kiwi Browser 或 Firefox Android 上的油猴扩展可以尝试运行桌面版脚本但页面结构和触摸事件差异较大。移动端适配需要单独调整选择器和按钮定位方式。12.4 接入第三方通知中心如果愿意把清理后的关键消息转发到私有通知服务可以设计一个抽象层脚本只负责输出清理结果具体发送方式由外部配置决定。建议保留本地文件输出为 JSON 作为首选方式降低服务依赖。13. 总结这个油猴脚本最值得尝试的点不是技术难度而是把“点未读红点”这种高频重复操作彻底简化。v0.2 的多端免登录设计让脚本的部署成本大幅下降对比传统独立授权方案更适合浏览器脚本生态。第一次验证时建议按照“单条清理 → 按类型清理 → 自动清理 → 批量清理”的顺序逐步进行。最容易踩的坑有两个一是页面 DOM 选择器在 B 站改版后失效二是懒加载消息列表导致只清理了首屏数据。测试时先在少量消息环境中确认逻辑再扩大清理范围。不要忽略油猴扩展菜单的配置导入导出功能多设备环境下它能节省大量重复配置时间。如果需要长期稳定使用最稳妥的做法是持续关注页面结构变化并保留一个小参数测试环境。油猴脚本的性能开销不大真正的成本在于页面适配维护。结合浏览器开发者工具和日志输出多数问题都能在几分钟内定位。