本帖最后由 啦la啦 于 2026-1-4 02:07 编辑
此笔记记录脚本B站弹幕显示发送次数与点赞数 开发过程
B站弹幕DOM探索
F12 → 元素 → 查找 搜索弹幕文本,找到弹幕元素
<!--典型弹幕元素-->
<div aria-live="polite" role="comment"
class="bili-danmaku-x-dm bili-danmaku-x-roll bili-danmaku-x-show"
style="--opacity: 0.8; --fontSize: 25px; --fontFamily: SimHei, 'Microsoft JhengHei', Arial, Helvetica, sans-serif; --fontWeight: bold; --color: #ffffff; --textShadow: 1px 0 1px #000000,0 1px 1px #000000,0 -1px 1px #000000,-1px 0 1px #000000; --offset: 730.5388359130914px; --translateX: -861px; --duration: 8.21s; --top: 33.125px;"
>
哈哈哈
</div>
经研究得到以下结论:
- 所有弹幕都有
bili-danmaku-x-dm类
- 显示中的弹幕有
bili-danmaku-x-show类,显示结束后不会把弹幕元素从DOM移除,而是移除bili-danmaku-x-show类,等待复用
- 外层弹幕容器的类名为
bpx-player-row-dm-wrap
B站弹幕加工探索
由以上DOM探索可知弹幕DOM元素并不含弹幕的id等信息,得找到弹幕是如何加工成DOM元素的。
在DevTools(F12)中右键弹幕容器(bpx-player-row-dm-wrap) → 中断于 → 子树修改
播放视频,结果中断于ndp.223.xxxxxx.js文件的一个名为render方法中(有时中断于名为init的方法,F8继续运行一下),此方法根据弹幕信息,为弹幕DOM元素添加各种style属性。
e.prototype.render = function(t, e) {
var n, r, o, i = this.textData, a = i.color, s = i.border, c = i.borderColor, u = i.colorfulImg, l = i.isHighLike, f = i.likes, p = this.config.setting, h = p.bold, d = p.fontFamily, m = p.fontBorder, y = p.opacity, g = Ft(this.outlineColor(a)), v = "".concat(H, "-dm ").concat(H, "-roll"), b = "";
s && "transparent" !== c && !(null === (n = this.modeInfo) || void 0 === n ? void 0 : n.isUpSlogan) ? (b += "border: 1px solid ".concat(Ft(c), ";"),
this.width += 4,
this.height += 4) : (null === (r = this.modeInfo) || void 0 === r ? void 0 : r.isUpSlogan) && (v += " ".concat(H, "-upslogan"),
this.width += 12),
this.font = (h ? "bold" : "normal") + " " + 2 * this.fontSize + "px " + d,
b += "--opacity: ".concat(Math.max(y, .1) + "", ";"),
b += "--fontSize: ".concat(this.fontSize, "px;"),
b += "--fontFamily: ".concat(d, ", Arial, Helvetica, sans-serif;"),
b += "--fontWeight: ".concat(h ? "bold" : "normal", ";"),
b += "--color: ".concat(Ft(a), ";"),
this.element.setAttribute("aria-live", "polite"),
this.element.setAttribute("role", "comment"),
u || (b += "--textShadow: ".concat(this.getShadow(g, m), ";")),
b += this.renderRealTimeOptimization(null === (o = this.textData) || void 0 === o ? void 0 : o.isRealTime),
l || f || u ? this.renderSpecialType(e || v, b) : (this.hasEmoji || this.textData.prefix || this.textData.suffix ? this.element.innerHTML = lt(this.text) : this.element.textContent = lt(this.text),
this.element.className = e || v,
this.element.style.cssText = b),
this.textData.prefix && this.textData.prefix instanceof HTMLElement && (this.textData.prefix.style.height = "".concat(1.2 * this.fontSize, "px"),
this.textData.prefix.style.width = "auto",
this.element.insertBefore(this.textData.prefix, this.element.firstChild)),
this.textData.suffix && this.textData.suffix instanceof HTMLElement && (this.textData.suffix.style.height = "".concat(1.2 * this.fontSize, "px"),
this.textData.suffix.style.width = "auto",
this.element.appendChild(this.textData.suffix))
}
this.textData包含各种弹幕信息。具体含义参考 protobuf弹幕 DanmakuElem
{
"stime": 884.647, // 发送时进度条时间
"mode": 5,
"size": 25,
"color": 16646914,
"uhash": "6a3439b",
"text": "哈哈哈",
"date": 1766993886,
"weight": 10,
"dmid": "2012449591689219328", // 弹幕唯一id(查询点赞数需要)
"attr": 1048576,
"oid": 35045576155, // 视频唯一chatid(查询点赞数需要)
"dmFrom": 1,
"likes": undefine,
"rawMode": 5,
"modeInfo": {
"cid": 35045576155
},
"pool": 0,
"uname": "",
"id_str": "2012449591689219328", // 弹幕id通常超出Number范围
"border": false,
"borderColor": 6750207,
"isHighLike": false,
"isMine": false,
"prefix": null,
"suffix": null,
"on": true
}
到此为止就很简单了,交给AI即可。hook render 函数,把需要的弹幕信息塞入弹幕DOM元素的dataset里;然后MutationObserver监控弹幕的DOM元素,有新弹幕出现就从dataset里获取oid、dmid查询点赞数,显示在弹幕旁。
其他
点赞数查询API:bilibili-API-collect 弹幕点赞查询
角标使用的SVG:
- 发送图标:iconfont找了一个
- 点赞图标:B站自带点赞弹幕图标(悬浮于弹幕上,右键点赞图标 → 检查 → svg复制下来)
角标按热度增亮公式:
高亮角标即角标添加背景色,亮度即背景色不透明度(0~1),选用公式 $1-e^{-kx}$
由于合并数与点赞数不在一个数量级,热度为合并数与点赞数的加权和:
$hot=(sendCount-1)\cdot sendMul+likes\cdot likeMul$
发送次数为1相当于合并数为0,因此sendCount需减1
热度为0时也要有一定的亮度,设为b0,公式修正为:
$brightness=1-(1-b0)e^{-k \cdot hot}$
还剩参数k决定曲线上升速率,设参数h90表示亮度达到90%时的热度值,计算k:
$$
\begin{aligned}
0.9 &= 1-(1-b_0)e^{-k \cdot h_{90}} \
e^{-k \cdot h_{90}} &= \frac{0.1}{1-b_0} \
k &= -\frac{1}{h_{90}}\ln\left(\frac{0.1}{1-b_0}\right) \
k &= \frac{1}{h_{90}}\ln(10-10\cdot b_0)
\end{aligned}
$$
最终四个参数决定亮度曲线:
| 参数 |
含义 |
默认 |
| b0 |
热度为0时的亮度 |
0.2 |
| h90 |
亮度为90%时的热度 |
500 |
| sendMul |
合并数倍率 |
5 |
| likeMul |
点赞数倍率 |
1 |
这几个参数没给UI,可通过控制台 __DM_ADAPT__.setBadgeHighlightCurve(0.2, 500, 5, 1) 控制。