ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

油猴脚本实战:开发B站消息清理助手v0.2

油猴脚本实战:开发B站消息清理助手v0.2 B站账号用久了最磨人的往往不是视频没更而是右上角那颗消息小红点。点赞、评论、提醒、系统通知、私信只要参与过互动短短几天就能攒下几十上百条未读。逐条点开再返回效率特别低不去管它红点又一直挂在那里对有“红点强迫症”的人来说很难受。之前试过一些清理类扩展要么依赖额外的客户端要么要在不同设备上反复授权登录用起来很繁琐。后来我用油猴脚本自己整理了一个“B站消息清理助手 v0.2”日常只需要在登录B站的浏览器里运行脚本就能把当前账号的消息清理流程串起来不需要安装独立 App也不需要给脚本再维护一套登录逻辑。这篇文章就从油猴脚本的基础概念讲起带你理解这类“页面助手”的运行机制再基于 v0.2 的版本设计完整拆解功能规划、代码结构和常见坑点。文章覆盖的内容适合两类读者一类是刚开始接触用户脚本、想在浏览器里做自动化操作的新手另一类是已经能写简单 JS但想系统了解油猴脚本边界、权限、接口适配和版本迭代的进阶开发者。1. 背景B站消息清理助手到底做了什么1.1 B站消息页的真实痛点B站的消息体系并不是单一入口。打开消息中心里面至少包含回复、我、收到的赞、系统通知、私信等几类。视频互动稍微活跃一点这些分类下的未读数量就会持续增长。真正让人头疼的是B站的“未读状态”并不会因为你只看了一眼自动清空。很多手动操作需要你点进对应列表在每条消息上确认或者执行“全部已读”之类的动作。如果消息量很大这一整套流程会占用不少时间。与其每天都手动重复不如通过浏览器脚本把操作自动化。“清理”在这里并不是指删除自己发过的评论、动态或私信记录而是指把大量未读通知批量标记为已读或者针对某个消息列表执行批量整理动作。清理的目标是当前登录账号自己的消息不是去操作他人的数据这一点在做技术实现时一定要分清边界。1.2 油猴脚本和普通浏览器扩展的差别油猴脚本最早是一个叫 Greasemonkey 的用户脚本系统后来大家习惯把 Tampermonkey、Violentmonkey 这类用户脚本管理工具统称为“油猴”。在不少社区讨论中油猴脚本也会被调侃成“油泼猴”本质指的是同一套运行思路。普通浏览器扩展需要完整的 manifest.json、弹出页、后台脚本等文件想要分发通常要走应用商店审核流程油猴脚本则简单很多本质上就是一段有特定元数据注释的 JavaScript。用户把代码复制到 Tampermonkey 中保存后脚本就能在某些匹配的网页里自动运行。油猴脚本的优点是轻量、跨浏览器、开发调试方便。但它不是完全没有边界脚本能访问多少 API、能向哪些域名发请求都受油猴管理器的权限模型限制。我们不能把它理解成一个“可以随便修改任何网页”的黑客工具它本质上是在浏览器的安全机制内替用户执行可重复的前端操作。1.3 “多端免登陆”应该如何理解“B站消息清理助手 v0.2”中提到的多端免登陆很容易被误解成绕过B站登录验证这里需要先解释清楚。这个脚本的免登录指的是“不需要为脚本单独维护登录态”。当你已经通过浏览器正常登录B站账号后打开消息页面脚本复用当前页面的登录会话。在 Chrome、Edge、Firefox 或不同电脑上只要你的浏览器登录着对应B站账号脚本就能运行不需要再输入一次账号密码也不需要生成额外的 Token 填到脚本里。理解这一点对安全性非常重要脚本本身不应该保存密码也不应该保存可长期使用的 Cookie。它只应在用户主动打开B站且保持登录状态的浏览器会话中工作。凡是要求你把账号密码或 Cookie 明文写进脚本里的工具都应该保持警惕。2. 环境准备搭建油猴脚本运行环境2.1 安装 Tampermonkey开发这类脚本第一步是给浏览器安装一个用户脚本管理器。目前最常用的是 Tampermonkey另一个轻量选择是 Violentmonkey。两者的功能差异不算大下面的示例以 Tampermonkey 为例说明。不同浏览器的安装路径不完全一样但思路是一致的浏览器安装方式Microsoft Edge打开扩展管理页点击“获取 Microsoft Edge 扩展”搜索 TampermonkeyChrome在扩展程序页面找到应用商店入口搜索 TampermonkeyFirefox打开 Firefox Add-ons 页面搜索 Tampermonkey在这些操作里最需要注意的是下载来源。请尽量通过浏览器官方扩展商店安装不要下载来路不明、被打包好的“Tampermonkey 安装包”。油猴管理器本身拥有很高的页面权限如果安装包被篡改后续所有脚本都可能存在数据风险。安装完成后浏览器右上角通常会出现 Tampermonkey 的图标。点击图标可以看到“添加新脚本”“管理面板”等入口。在开始写脚本之前可以先把页面上已有的示例脚本禁用或删除避免干扰调试。2.2 创建并保存一个用户脚本用 Tampermonkey 创建脚本比较简单大致流程如下点击浏览器工具栏中的 Tampermonkey 图标。选择“创建新脚本”或“添加新脚本”。编辑器会自动生成一个脚本模板。删除模板内容粘贴自己的代码。使用Ctrl S保存或者点击编辑器的保存按钮。打开你希望脚本运行的页面刷新后观察效果。编辑器里通常会有语法高亮和简单的错误提示。如果脚本启动失败也可以在浏览器控制台看到具体报错信息。2.3 开发调试时的版本问题写这类脚本时不建议过分依赖某个具体浏览器或 Tampermonkey 的精确版本。浏览器厂商会持续调整扩展权限模型Tampermonkey 本身也会更新B站页面的 DOM 结构和接口更是在不断变化。因此文章中的示例代码会更多强调“工程思路”而不是让人复制一份就可以永久使用。你在本地验证时以自己浏览器当前稳定的版本为准。如果一段时间后脚本失效优先检查两个方向油猴脚本的元数据是否仍匹配当前页面地址以及B站消息页面的 DOM 或接口是否已经改版。3. 用户脚本的基础语法与核心执行原理3.1 从一个最小示例认识元数据一个油猴脚本和普通 JS 文件最明显的区别是它带有// UserScript开头的一段元数据。Tampermonkey 通过这些字段决定脚本在哪些页面运行、拥有哪些权限、版本号是多少。// UserScript // name 演示脚本-输出当前页面 // namespace https://local.demo/ // version 0.1 // description 一个最小的油猴脚本示例 // author your-name // match https://www.bilibili.com/* // run-at document-idle // grant none // /UserScript (function () { use strict; console.log(油猴脚本已运行, location.href); })();先来看各项字段的含义name脚本名称会显示在 Tampermonkey 管理面板中。namespace用于区分同名脚本的命名空间可以填写自己的项目主页或任意唯一字符串。version脚本版本号。修改脚本后建议递增版本号方便后续管理和排查。description脚本描述。match声明脚本可以运行在哪些 URL 上。run-at声明脚本在页面的哪个阶段执行。grant声明脚本需要使用哪些油猴专属 API。把上面的最小脚本保存后打开https://www.bilibili.com/任意页面刷新并打开浏览器开发者工具就能在 Console 中看到输出。如果看不到先检查当前页面 URL 是否匹配元数据中的match规则。3.2 match 匹配规则是脚本是否触发的关键很多新手写完脚本后发现页面毫无反应问题往往不是代码逻辑而是match没写对。match的规则和普通 URL 匹配不太一样它允许使用通配符。例如// 匹配 www.bilibili.com 下的所有页面 // match https://www.bilibili.com/* // 匹配 bilibili.com 主域名下的常见子域名 // match https://*.bilibili.com/*这里需要注意几点*不能匹配顶级域名本身的一些差异比如https://bilibili.com/*和https://www.bilibili.com/*是两种不同的规则。如果B站消息页在message.bilibili.com这类子域名下需要在元数据中补充对应的match。页面 URL 中的 hash 不会影响match比如https://message.bilibili.com/#/reply依然由https://message.bilibili.com/*命中。为了减少脚本在无关页面空转建议不要直接写成match *://*/*。脚本范围越大潜在风险越高也越容易造成不必要的性能消耗。3.3 脚本到底何时执行run-at可以设置脚本执行时机比较常用的值有document-start页面刚开始加载时就执行。document-endDOM 加载完成后执行。document-idle页面空闲时执行适合大多数普通场景。不过B站这类单页应用和动态渲染页面即使到了document-idle消息列表也不一定渲染完成。更可靠的做法是在脚本里主动“等待某个页面元素出现”再继续后续逻辑。下面是一个简单的等待函数function waitForElement(selector, timeout 10000) { return new Promise((resolve, reject) { const startTime Date.now(); const timer setInterval(() { const element document.querySelector(selector); if (element) { clearInterval(timer); resolve(element); } else if (Date.now() - startTime timeout) { clearInterval(timer); reject(new Error(等待元素超时: selector)); } }, 300); }); } waitForElement(.message-list) .then((list) { console.log(找到了消息列表, list); }) .catch((err) { console.warn(err.message); });这里的选择器.message-list只是示例。实际选择器需要你在具体页面上通过开发者工具确认不同改版版本会有差异。3.4 消息页面的开发调试方法在写清理助手前必须先学会“观察”页面。按 F12 打开开发者工具切到 Console 和 Network 标签页刷新消息页面后可以看到页面渲染时产生了哪些请求也能看到当前消息列表是用什么方式渲染出来的。如果点击“全部已读”或逐条“已读”操作时网络请求列表中出现了一个 XHR/Fetch 请求那么说明这个动作是通过接口完成的如果没有出现新的请求只是页面样式变化那么很可能只是纯前端状态修改。这类观察信息就是适配脚本的核心依据。官方接口路径不能拍脑袋编造必须以当前线上页面实际抓到的数据为准。不同时期、不同账号下的接口参数可能都有差异。4. 完整实战做一个可扩展的 v0.2 消息清理助手4.1 v0.2 的功能规划按照工具型油猴脚本的常用设计v0.2 的定位并不是“直接操作复杂后台的完整成品”而是一个“带有清晰适配层的工程化示例”。核心功能包括以下几项检测当前页面是否处于已登录状态。根据用户配置定位消息列表中的未读项。模拟“标记已读”的批量操作。每次操作之间加入延迟避免短时间高频请求造成触发风控。提供一个简单的控制台日志层方便观察执行进度。把页面相关选择器和接口地址集中管理降低后续改版带来的维护成本。4.2 代码架构的总体规划在动手写代码前先把流程拆开后续排错会容易很多。清理消息的核心步骤可以简化为这样用户打开B站消息页 ↓ 检查脚本配置与页面登录态 ↓ 获取未读消息列表 ↓ 逐条或批量标记为已读 ↓ 等待下一次刷新或输出完成日志从工程角度看这套流程中只有两个地方和B站强相关页面 DOM 选择器、消息接口地址。其他部分比如等待逻辑、延迟执行、日志输出都是可以复用的通用代码。因此脚本应该把“页面适配层”和“核心业务流程”分开。4.3 编写油猴脚本头部下面是 v0.2 版本可参考的元数据配置// UserScript // name B站消息清理助手 // namespace https://github.com/your-name/bilibili-msg-cleaner // version 0.2 // description 在当前登录的B站会话中批量清理未读消息通知 // author your-name // match https://*.bilibili.com/* // run-at document-idle // grant GM_setValue // grant GM_getValue // /UserScript这里没有使用GM_xmlhttpRequest目的是让脚本优先复用页面自己的请求上下文。不同浏览器的扩展 API 对跨域 Cookie 处理差异较大普通页面内的请求行为相对更接近B站前端本身的逻辑。如果后续需要直接调用跨域接口并且遇到 Cookie 或 CORS 限制可以再补充grant GM_xmlhttpRequest和connect。但这类改动必须结合当前页面的实际调试结果来设置不能盲目照抄。4.4 工具函数与请求封装这一段把所有通用能力集中封装后面主流程直接调用代码会清晰很多。// 文件说明B站消息清理助手 v0.2 - 通用工具部分 // 使用方式与下方主流程代码保存在同一个用户脚本中 function sleep(ms) { return new Promise(resolve setTimeout(resolve, ms)); } function log(level, message, data) { const prefix [B站清理助手]; if (level error) { console.error(prefix, message, data || ); } else if (level warn) { console.warn(prefix, message, data || ); } else { console.log(prefix, message, data || ); } } async function requestJson(url, options {}) { const response await fetch(url, { method: options.method || GET, headers: options.headers || {}, credentials: include, body: options.body || undefined, }); if (!response.ok) { throw new Error(HTTP ${response.status}: ${response.statusText}); } const contentType response.headers.get(content-type) || ; if (contentType.includes(application/json)) { return await response.json(); } return await response.text(); }credentials: include表示请求时尝试携带当前域名下的凭据。对于需要登录态的接口这一步是能否成功的关键。日志函数之所以单独抽出来是因为当消息规模较大时你需要在 Console 里快速找到脚本输出而不是被大量浏览器原生日志淹没。4.5 适配层与主流程代码接下来是核心脚本部分。页面适配层使用了“待配置”的形式因为真实接口取决于你当前页面抓包结果。你可以先按下面的代码备份再把抓包得到的地址填入。// 以下为页面适配层 // 注意这里的接口地址都是示例占位符不能直接复制生效。 // 请按第 4.6 节的操作步骤抓包后替换为真实地址。 const API_CONFIG { // 未读消息列表接口 listUrl: , // 标记单条消息已读的接口 readUrl: , }; const SELECTOR_CONFIG { // 每一条消息对应的 DOM 元素选择器 messageItem: .message-item, // 未读标识的选择器 unreadFlag: .unread, }; // 以下为主流程部分 async function getMessageList() { if (!API_CONFIG.listUrl) { log(warn, 尚未配置 listUrl请抓包后填写消息列表接口); return []; } const result await requestJson(API_CONFIG.listUrl, { method: GET, }); // 这里需要根据实际接口的 JSON 结构解析列表数据 // 下面是常见的示例写法请务必按真实返回结果调整 const list result result.data result.data.list; return Array.isArray(list) ? list : []; } async function markMessageRead(messageId) { if (!API_CONFIG.readUrl) { log(warn, 尚未配置 readUrl请抓包后填写标记已读接口); return false; } const body JSON.stringify({ id: messageId, }); await requestJson(API_CONFIG.readUrl, { method: POST, headers: { Content-Type: application/json, }, body, }); log(info, 已标记消息 messageId); return true; } async function cleanUnreadMessages() { log(info, 开始清理未读消息); const list await getMessageList(); if (!list.length) { log(info, 当前没有需要清理的消息); return; } log(info, 共发现 list.length 条消息); for (const item of list) { try { // 标记前先判断未读状态避免多余请求 if (item.unread) { await markMessageRead(item.id); } // 每次请求间隔 800ms降低请求频率 await sleep(800); } catch (error) { log(error, 单条消息处理失败 item.id, error); } } log(info, 清理流程执行完成建议刷新页面查看结果); } // 在页面中提供一个入口按钮 function createCleanButton() { const button document.createElement(button); button.textContent 清理未读消息; button.style.position fixed; button.style.right 20px; button.style.bottom 120px; button.style.zIndex 99999; button.style.padding 8px 16px; button.style.border none; button.style.borderRadius 6px; button.style.background #fb7299; button.style.color #fff; button.style.cursor pointer; button.addEventListener(click, function () { button.disabled true; cleanUnreadMessages() .catch(error log(error, 清理流程异常, error)) .finally(() { button.disabled false; }); }); document.body.appendChild(button); log(info, 清理按钮已添加到页面右下角); } (function () { use strict; createCleanButton(); })();这段代码保存后即使接口没有配置也应该能在页面上看到一个右下角按钮。点击按钮后控制台会提示“尚未配置 listUrl”或“尚未配置 readUrl”这说明脚本的整体生命线是通的。剩下的工作就是抓包、填地址、启动验证。4.6 如何抓包获取真实接口前面的代码已经反复提过抓包下面把流程完整地走一遍。B站页面改版时这个方法依然能帮你快速定位问题理论上适用于大多数网页自动化脚本的适配过程。第一步打开B站消息页面并确认当前账号处于登录状态。第二步按 F12 打开开发者工具切换到 Network 标签页。第三步在页面中手动执行一次你想自动化的操作。比如手动点击某条消息的“已读”按钮或者点击“全部已读”。第四步在 Network 面板中筛选 Fetch/XHR找到刚发出去的请求。第五步确认请求的 URL、请求方法、Header、Query String 或 POST 请求体中的参数格式。第六步把 URL 和参数格式映射到脚本中的API_CONFIG。这个过程需要一点点耐心。找到正确请求的最快办法是先记住点击操作发生的时间点然后在 Network 面板里按时间倒序找Fetch/XHR类型的记录。找到后点击记录在 Preview 或 Response 标签页确认返回值中包含消息列表或操作结果。改完接口配置后建议先在无痕窗口里用一个小号或者临时测试账号验证。不要在正式账号和大批量消息上第一时间尝试完整流程避免意外情况扩大影响面。5. 常见问题与排查清单油猴脚本开发和普通前端开发一样报错不可怕怕的是找不到发生问题的层级。下面列出我在调试这类页面助手时常见的问题供你快速对照。问题现象常见原因排查思路脚本完全不执行match 不匹配当前页面在脚本文本中加入 console.log 并确认 URL页面找不到按钮脚本可能未刷新加载保存脚本后刷新目标页面控制台报接口跨域错误页面自身的 CORS 限制了请求目标改用 GM_xmlhttpRequest 并配置 connect标记已读后刷新还是未读请求未携带登录态检查是否漏掉 credentials、Cookie 或 Header操作速度过快导致请求失败没有加入延迟或请求被风控增加 sleep、控制单次执行数量页面结构变化后找不到元素B站改版导致选择器失效重新通过开发者工具确认 DOM 结构5.1 脚本不执行脚本不执行八成是match没有覆盖当前页面。比如你在消息页测试但脚本只声明了https://www.bilibili.com/*而消息页实际运行在另一个子域名下脚本自然无法触发。排查方法是先打开 Tampermonkey 管理面板在脚本列表里检查“已启用”状态再到目标页面刷新后打开 Console看看有没有脚本输出。如果 Tampermonkey 图标上出现数字角标也可以点击图标查看当前页面有哪些脚本被允许运行。5.2 接口请求 401 或 403401 通常表示未登录或登录态失效403 则可能是缺少必要 Header、参数校验失败或被风控拦截。先确认浏览器是否真的处于登录状态。油猴脚本运行在页面里时它复用的是当前页面所携带的会话。如果你在无痕窗口或未登录状态下测试很多请求会直接失败。如果登录状态没问题抓包对比一下页面里手动发起的请求和脚本发起的请求是否存在 Header 差异。某些接口可能要求特定的 Token、User-Agent 或其他自定义请求头这些都需要补全到requestJson的headers配置中。5.3 操作频率过快被风控批量清理本身就是在短时间发起多次同类请求如果不控制频率很容易被服务端风控系统识别为异常行为。轻则部分请求失败重则临时限制接口访问。可以在循环中增加 500 到 1500 毫秒的随机延迟也可以拆成小批次执行每次只处理一部分消息然后等待一段时间再继续。对于个人账号的日常清理来说慢一点不是坏事。6. 工程最佳实践与安全建议6.1 脚本来源与代码审计使用油猴脚本最需要重视的一点是来源安全。因为脚本能修改网页内容、读取当前页面数据甚至在你授予更高权限后跨域请求恶意脚本的危害不亚于恶意扩展。不要从陌生网站复制不明脚本来处理自己的账号消息。即使用了别人公开的脚本也应该先通读一遍重点关注三件事代码里有没有把数据发送到未知域名。有没有读取并上传 Cookie、Token 等敏感内容。有没有执行与功能描述无关的隐藏逻辑。自己写脚本时也要养成习惯尽量缩小match范围不申请不必要的grant权限不让脚本在无关页面运行。6.2 配置与代码分离把页面选择器、接口地址、执行延迟这些容易变化的内容集中放在脚本顶部或独立的CONFIG对象中是一个性价比极高的维护习惯。B站页面改版时通常只需要修改配置区里的几个常量而不是去翻整个业务逻辑代码。版本号也别忘记更新。每改动一次脚本建议递增version并在脚本描述中简单记录改动范围。长期维护时版本号能帮你判断当前设备和发布版本是否一致。6.3 日志与执行进度批量操作最容易出现的问题是无法确认执行到了哪一步。可以在脚本的关键节点都加上日志例如“获取列表完成”“正在处理第 N 条”“本次执行完成”。考虑到消息数量可能不少建议在控制台统一添加前缀比如[B站清理助手]。这样在很多脚本同时输出日志时也能快速过滤出自己的内容。如果脚本以后要交给别人用可以在页面里加一个简单状态文案比让用户打开控制台更友好。6.4 账号合规与最小权限原则消息清理助手应该只处理当前登录账号自己的消息。任何要求读取他人隐私、采集未授权数据、绕过正常操作流程的行为都不应该出现在个人工具脚本中。开发过程中尽量不要在生产环境账号上反复测试高风险操作。可以先拿小号验证或者先处理少量消息观察行为是否符合预期。如果脚本要分享出去也要提醒使用者只在自己授权的账号上运行并自行承担使用风险。6.5 面对页面改版的应对方法B站前端版本迭代速度不慢脚本失效是迟早会遇到的问题。应对思路不是“想方设法写出一个永久的完整适配”而是建立一套能快速定位改版点的流程。页面 DOM 层失效时按 F12 查看新结构更新SELECTOR_CONFIG。接口层失效时重新抓包更新API_CONFIG。只要脚本没有因为某个安全策略变化导致整体无法运行这类修复通常不需要改核心流程。7. 总结与下一步学习方向这篇文章从B站消息清理的实际痛点出发完整介绍了油猴脚本的概念、环境搭建、元数据语法、脚本执行机制、消息页适配方式以及一套带适配层的 v0.2 示例代码。通过这个案例你应该能理解这类页面助手并不神秘它本质上是复用你已经登录的网页会话用 JavaScript 模拟人工操作只是把过程封装得更高效、更可维护。如果你刚开始接触油猴脚本下一步可以尝试给自己常用的其他网站写类似的小工具先做页面元素高亮、自动点击、表单填充这类低风险功能。当你熟悉了 Tampermonkey 的权限模型和浏览器安全边界后再逐步挑战需要调用接口的批量场景。直接在真实生产账号上大批量运行前请务必先小范围测试并在小号或无痕环境里确认脚本行为符合预期。如果你也折腾过自己的B站消息清理脚本欢迎在评论区分享你的适配经验和踩坑过程文章如果对你有帮助可以先收藏备用回头改版后还能翻出来对照思路。
返回列表