一个 Tampermonkey / 脚本猫 用户脚本,在 B站 UP 主空间的右侧边栏嵌入一块统计面板,根据接口返回的发布时间计算该 UP 主的更新频率:多久更一次、一周更几条、更新时间准不准、习惯在什么时段发、下次大概什么时候更新。
数据来自 B站自己加载的接口,脚本只是旁路读取,不额外发请求、不改动页面。
打开任意 UP 主的空间,右侧会出现这样一块卡片:
{UP Name}
MID {int} · 12 条样本
━━━━━━━━━━━━━━━━━━━━━━━
1.6
次 / 周
稳定周更 · 约 6.8 次/月 · 平均每 4.4 天 一次
预计下次:2026-10-23(还有 2.2 天)
平均间隔 4.4 天 中位间隔 4.0 天
最短间隔 3.0 天 最长间隔 10.0 天
时间跨度 1.6 个月 统计条数 12
更新规律性
稳定 ████████████░░░░░░░ 57%
常发时段
16时 ██ 1 条
17时 ███████████████████ 10 条
19时 ██ 1 条
每周分布
周日 ██████ 1 条
周一 ██████████████████ 3 条
...
最近更新
1.8 天前 2026-10-19 17:17(Video title)
5.8 天前 2026-10-15 17:22(Video title)
...
[清空缓存] [复制统计]
一句话概括:1.6 次/周,平均 4.4 天一条,比较准时,习惯下午 5 点发。
「最近更新」每行显示 距今天数 · 绝对时间(标题)。同一支视频若同时来自动态与投稿接口,两个来源会合并成一条,标题取自接口返回的稿件信息。
https://space.bilibili.com/3546565008427419,右侧面板会自动出现。脚本只匹配 https://space.bilibili.com/*,不会在视频页、首页等其它页面生效。
面板不会主动去抓数据,而是等你正常浏览时,读取页面已经请求回来的接口响应。所以:
滚动得越多,样本越多,结果越准。脚本会按 UP 主分开缓存,下次再来看这个 UP 主,不用重新滚动,缓存 7 天有效,期间会不断合并新样本。
| 按钮 | 作用 |
|---|---|
↻ |
补齐/刷新。已有足够样本时顺手补拉一次投稿列表;样本不足时尝试主动请求(见下文说明) |
清空缓存 |
清除当前 UP 主的本地样本,重新开始统计 |
复制统计 |
把核心结论复制成一段文本,方便贴到别处 |
脚本暴露了 window.__BiliUpFreq,方便自行核对:
__BiliUpFreq.computeFor('3546565008427419') // 查看当前汇总结果
__BiliUpFreq.parseRelativeTime('昨天 17:17', Date.now())
__BiliUpFreq.config // 运行期配置
脚本同时支持两个接口,字段不同但都归一到「毫秒时间戳」:
| 来源 | 接口 | 时间字段 |
|---|---|---|
| 动态页 | /x/polymer/web-dynamic/v1/feed/space |
modules.module_author.pub_ts(秒级时间戳)回落:同处的 pub_time(如 昨天 17:17、10月15日) |
| 主页 / 投稿页 | /x/space/wbi/arc/search |
list.vlist[].created(秒级时间戳) |
优先用时间戳,而不是文本。 接口里 pub_time 是给人看的相对时间,用它回推会有误差:
| pub_time | 由其文本推算 | 真实时间(pub_ts) | 误差 |
|---|---|---|---|
昨天 17:17 |
2026-10-19 17:17 | 2026-10-19 17:17 | 0.4 分钟 |
10月15日 |
2026-10-15 00:00 | 2026-10-15 17:22 | 约 17 小时 |
前者精确到分钟,后者只有「月日」没有时刻。所以脚本以 pub_ts / created 为准;只有在时间戳缺失或非法时,才回落到 pub_time 文本解析。
文本解析器覆盖了 B站实际会出现的各种写法:今天/昨天/前天 HH:MM、N秒/分钟/小时/天前、星期五 12:30、10月15日、2025-12-01、2024年5月1日,并处理了跨年歧义(12 月看到「1月5日」算明年,1 月看到「12月30日」算去年)。
设按时间降序取到 N 条样本,时间戳为 t₁ > t₂ > … > tₙ(t₁ 最新):
| 指标 | 计算方式 |
|---|---|
| 平均间隔 | 所有相邻间隔的算术平均 |
| 中位间隔 | 相邻间隔的中位数(先剔除超过 180 天的「断更」样本) |
| 最短 / 最长间隔 | 同上,剔除断更后取极值 |
| 时间跨度 | t₁ − tₙ |
| 每周更新 | (N − 1) ÷ (跨度 ÷ 7 天) |
| 每月更新 | (N − 1) ÷ (跨度 ÷ 30.4375 天) |
| 规律性 | 1 − 变异系数 = 1 − 标准差 ÷ 平均间隔,再截断到 0 以上 |
| 常发时段 | 把每条样本按本地时区取「小时」,统计分布 |
| 每周分布 | 同上,取「星期几」 |
| 预计下次 | 最新一条时间 + 中位间隔 |
几个刻意的设计取舍:
bvid,与投稿接口返回的是同一支视频。脚本统一用 bvid(其次 aid)作为去重键,两个来源相遇时只保留一条并补齐缺失的标题,避免凭空冒出「间隔 6 分钟」的假样本。两种页面的右栏结构不同,脚本会自动识别并挂到右侧边栏的最前面:
| 页面 | 右栏容器 | 卡片容器 |
|---|---|---|
动态页 space.bilibili.com/{mid}/dynamic |
.space-dynamic__right |
.dynamic-aside-section |
主页 / 投稿页 space.bilibili.com/{mid} |
.space-home .aside |
.home-aside-section |
选侧栏而不是悬浮窗,有实际原因:右栏本来就是放「关于这个 UP」这类卡片的地方(认证、公告、充电、个人资料),更新频率属于同一类信息;它是布局里占位的列,不会遮挡动态或视频列表,滚动时也不挡视线。
面板用 .dynamic-aside-section / .home-aside-section 包了一层,直接继承站点原生的卡片间距;配色全部取 B站主题变量(--bg2、--text1、--brand_blue 等,均带 fallback),因此自动跟随深色模式和 BewlyCat 等主题插件。
空间站是单页应用,切换 tab 时右栏会被 Vue 重建。脚本用 MutationObserver 监听,发现面板没了就自动挂回原位,不需要刷新页面。
GM_setValue(脚本管理器存储),没有时回落到 localStorage。只保存精简后的「ID + 时间戳 + 类型 + 标题」以及 UP 主昵称、头像,不保存完整接口 JSON。1. 主动拉取不一定成功(这是 B站接口的限制,不是 bug)
arc/search 与动态接口都要求 wbi 签名,且带风控。脚本用标准 wbi 算法实测仍可能返回 412,因此主路径始终是「截获页面自己的请求」。↻ 按钮的主动请求属于尽力而为:成功就用,失败静默降级,不影响已截获的数据。
2. 样本越多越准,但首次进入是空的
面板不会凭空出现数据,需要页面真的请求过接口。刚打开空间页时若列表还没加载,会显示「正在等待数据…」,滚动一下即可。
3. 统计的是「该 UP 主发了什么」,不是「内容质量」
转发、动态、视频的更新节奏本质不同。默认只统计视频稿件(DYNAMIC_TYPE_AV 与投稿列表),避免转发抽奖之类把节奏搅乱。想统计专栏或图文,改脚本里的 ONLY_TYPES 即可。
4. 低频 UP 主出现「规律性 0%」是正常的
极端案例:某 UP 主 25 条视频横跨 5 年,间隔从 0.3 天到 887.9 天。这种「长期低频 + 偶尔爆发」的节奏,变异系数必然大于 1,规律性被截断为 0。此时更有意义的是「平均每 2.5 个月一条」这个结论本身,而不是规律性。
脚本顶部 CFG 对象:
| 参数 | 默认值 | 说明 |
|---|---|---|
ONLY_TYPES |
['DYNAMIC_TYPE_AV', 'VIDEO'] |
参与统计的类型。改成 [] 可统计全部 |
INCLUDE_PINNED |
false |
是否把置顶动态计入 |
MAX_GAP_DAYS |
180 |
超过该天数的间隔视为断更,不计入中位数/规律性;0 表示不过滤 |
MAX_SAMPLES |
200 |
参与统计的最大样本数(取最近 N 条) |
CACHE_TTL |
7 天 | 本地缓存有效期 |
改完后保存、刷新页面即可生效。
findAsideTarget)仅供个人学习与自用。脚本依赖 B站公开接口,不对数据的完整性与接口的长期可用性作保证。