对 Bilibili.com 的视频卡片元素,以标题、UP 主、标签、双重标签、充电专属、收藏投币比、竖屏、时长、播放量、点赞率、视频分区、UP 主等级、UP 主粉丝数、UP 主简介、精选评论、置顶评论来判断匹配,添加覆盖叠加层或隐藏视频,隐藏或屏蔽热搜、附带去除广告等非视频元素的功能。
< 脚本 Bilibili 按标签、标题、时长、UP主屏蔽视频 的反馈
太强了,没在b站泡个三年写不出这么多关键词。看这几个词就能想象视频封面了
额,抱歉,同时打开了多个脚本的页面,突然想写个评论就写了发出来,然后发现发错了也删不了,这是另一个脚本的屏蔽词表达式,可能在这个脚本用不了我没有试过,评价错脚本了抱歉。
世界因你而美好
那我也来分享一个, 快速获取视频标签的脚本.
// ==UserScript==
// @name B站卡片一键复制标签
// @namespace https://github.com/
// @version 1.0
// @description 在指定视频卡片右下角添加按钮,点击复制该视频标签到剪贴板(基于脚本中获取标签的方法)
// @match https://www.bilibili.com/*
// @match https://space.bilibili.com/*
// @grant GM_setClipboard
// @connect api.bilibili.com
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const SELECTOR = 'div.pt-15px.flex.gap-x-5px.overflow-hidden.px-5px.mb-10px';
const tagsCache = {};
// av -> bv 转换(取自原脚本)
function av2bv(aid) {
const XOR_CODE = 23442827791579n;
const MASK_CODE = 2251799813685247n;
const MAX_AID = 1n << 51n;
const BASE = 58n;
const data = 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
const bytes = ['B', 'V', '1', '0', '0', '0', '0', '0', '0', '0', '0', '0'];
let bvIndex = bytes.length - 1;
let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE;
while (tmp > 0) {
bytes[bvIndex] = data[Number(tmp % BigInt(BASE))];
tmp = tmp / BASE;
bvIndex -= 1;
}
[bytes[3], bytes[9]] = [bytes[9], bytes[3]];
[bytes[4], bytes[7]] = [bytes[7], bytes[4]];
return bytes.join('');
}
function extractBv(card) {
// 遍历 a 链接寻找 BV 或 av
const anchors = card.querySelectorAll('a[href]');
let bv = null;
for (const a of anchors) {
const href = a.href || '';
// 匹配 BV
const bvMatch = href.match(/\/(BV[0-9A-Za-z]+)/);
if (bvMatch) {
bv = bvMatch[1];
break;
}
// 匹配 av
const avMatch = href.match(/\/(av)(\d+)/i);
if (avMatch) {
try {
bv = av2bv(avMatch[2]);
break;
} catch (e) {}
}
// 有时候 href 可能是相对路径 like /video/BVxxx
const relMatch = (a.getAttribute('href') || '').match(/\/(BV[0-9A-Za-z]+)/);
if (relMatch) {
bv = relMatch[1];
break;
}
}
return bv;
}
async function fetchTags(bv) {
if (!bv) return '';
if (bv in tagsCache) return tagsCache[bv];
try {
const resp = await fetch(`https://api.bilibili.com/x/web-interface/view/detail/tag?bvid=${bv}`, { credentials: 'omit' });
const j = await resp.json();
let tags = '';
if (j && Array.isArray(j.data)) {
tags = j.data
.map(t => t.tag_name.replace(/\s+/g, ''))
.filter(Boolean)
.join(',');
} else if (j && j.data && Array.isArray(j.data.tags)) {
// 兼容不同格式
tags = j.data.tags.map(t => t.tag_name || t).filter(Boolean).join(', ');
}
tagsCache[bv] = tags;
return tags;
} catch (e) {
tagsCache[bv] = '';
return '';
}
}
function copyToClipboard(text) {
if (!text) return false;
if (typeof GM_setClipboard === 'function') {
try { GM_setClipboard(text); return true; } catch (e) {}
}
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text).then(() => true).catch(() => fallbackCopy(text));
}
return fallbackCopy(text);
}
function fallbackCopy(text) {
try {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
return true;
} catch (e) {
return false;
}
}
function showTempText(btn, msg, timeout = 1400) {
const old = btn.innerText;
btn.innerText = msg;
setTimeout(() => { btn.innerText = old; }, timeout);
}
function addButtonToCard(card) {
if (card.dataset.copyTagsBtnAdded) return;
card.dataset.copyTagsBtnAdded = '1';
card.style.position = card.style.position || 'relative';
const btn = document.createElement('button');
btn.type = 'button';
btn.innerText = '📋';
btn.className = 'gm-copy-tags-btn';
Object.assign(btn.style, {
position: 'absolute',
bottom: '6px',
right: '6px',
zIndex: 999999,
padding: '4px 8px',
fontSize: '12px',
background: '#4EA8DE',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
boxShadow: '0 2px 6px rgba(0,0,0,0.2)'
});
btn.addEventListener('click', async (e) => {
e.stopPropagation();
e.preventDefault();
btn.disabled = true;
const bv = extractBv(card);
if (!bv) {
showTempText(btn, '未找到BV');
btn.disabled = false;
return;
}
const tags = await fetchTags(bv);
if (tags) {
const ok = await copyToClipboard(tags);
showTempText(btn, ok ? '已复制' : '复制失败');
} else {
showTempText(btn, '无标签');
}
btn.disabled = false;
});
card.appendChild(btn);
}
function scanAndInject() {
const cards = document.querySelectorAll(SELECTOR);
cards.forEach(addButtonToCard);
}
// 初始化扫描
scanAndInject();
// 监听 DOM 变化以处理懒加载/动态加载的卡片
const mo = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.addedNodes && m.addedNodes.length) {
scanAndInject();
break;
}
}
});
mo.observe(document.body, { childList: true, subtree: true });
})();
上面的是在bili-gate插件上用的 重新弄了个适配原版的
// ==UserScript==
// @name B站卡片一键复制标签(新版HTML适配)
// @namespace https://github.com/
// @version 1.1
// @description 在新版视频卡片右下角添加按钮,点击复制视频标签到剪贴板
// @match https://www.bilibili.com/*
// @grant GM_setClipboard
// @connect api.bilibili.com
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const SELECTOR = 'div.bili-video-card.is-rcmd';
const tagsCache = {};
function av2bv(aid) {
const XOR_CODE = 23442827791579n;
const MASK_CODE = 2251799813685247n;
const MAX_AID = 1n << 51n;
const BASE = 58n;
const data = 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
const bytes = ['B', 'V', '1', '0', '0', '0', '0', '0', '0', '0', '0', '0'];
let bvIndex = bytes.length - 1;
let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE;
while (tmp > 0) {
bytes[bvIndex] = data[Number(tmp % BigInt(BASE))];
tmp = tmp / BASE;
bvIndex -= 1;
}
[bytes[3], bytes[9]] = [bytes[9], bytes[3]];
[bytes[4], bytes[7]] = [bytes[7], bytes[4]];
return bytes.join('');
}
function extractBv(card) {
const a = card.querySelector('a.bili-video-card__image--link');
if (!a) return null;
const href = a.href || '';
const bvMatch = href.match(/\/(BV[0-9A-Za-z]+)/);
if (bvMatch) return bvMatch[1];
const avMatch = href.match(/\/(av)(\d+)/i);
if (avMatch) return av2bv(avMatch[2]);
return null;
}
async function fetchTags(bv) {
if (!bv) return '';
if (tagsCache[bv]) return tagsCache[bv];
try {
const resp = await fetch(`https://api.bilibili.com/x/web-interface/view/detail/tag?bvid=${bv}`, { credentials: 'omit' });
const j = await resp.json();
let tags = '';
if (j && Array.isArray(j.data)) {
tags = j.data.map(t => t.tag_name.replace(/\s+/g, '')).filter(Boolean).join(',');
} else if (j && j.data && Array.isArray(j.data.tags)) {
tags = j.data.tags.map(t => t.tag_name || t).filter(Boolean).join(',');
}
tagsCache[bv] = tags;
return tags;
} catch (e) {
tagsCache[bv] = '';
return '';
}
}
function copyToClipboard(text) {
if (!text) return false;
if (typeof GM_setClipboard === 'function') {
try { GM_setClipboard(text); return true; } catch (e) {}
}
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text).then(() => true).catch(() => fallbackCopy(text));
}
return fallbackCopy(text);
}
function fallbackCopy(text) {
try {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
return true;
} catch (e) {
return false;
}
}
function showTempText(btn, msg, timeout = 1400) {
const old = btn.innerText;
btn.innerText = msg;
setTimeout(() => { btn.innerText = old; }, timeout);
}
function addButtonToCard(card) {
if (card.dataset.copyTagsBtnAdded) return;
card.dataset.copyTagsBtnAdded = '1';
card.style.position = card.style.position || 'relative';
const btnHTML = `<button type="button" class="gm-copy-tags-btn"
style="position: absolute; bottom: 6px; right: 6px; z-index: 100; padding: 4px 8px; font-size: 12px; background: #17181A; color: #fff; border: none; border-radius: 6px; cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.2);">
📋
</button>`;
card.insertAdjacentHTML('beforeend', btnHTML);
const btn = card.querySelector('.gm-copy-tags-btn');
if (!btn) return;
if (btn.dataset.listenerAdded) return;
btn.dataset.listenerAdded = '1';
btn.addEventListener('click', async (e) => {
e.stopPropagation();
e.preventDefault();
btn.disabled = true;
const bv = extractBv(card);
if (!bv) {
showTempText(btn, '未找到BV');
btn.disabled = false;
return;
}
const tags = await fetchTags(bv);
if (tags) {
const ok = await copyToClipboard(tags);
showTempText(btn, ok ? '已复制' : '复制失败');
} else {
showTempText(btn, '无标签');
}
btn.disabled = false;
});
}
function scanAndInject() {
const cards = document.querySelectorAll(SELECTOR);
cards.forEach(addButtonToCard);
}
scanAndInject();
const mo = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.addedNodes && m.addedNodes.length) {
scanAndInject();
break;
}
}
});
mo.observe(document.body, { childList: true, subtree: true });
})();
非常好用,可以过滤掉很多垃圾视频,另外共享一下我这半年多写的一些屏蔽词的正则表达式,我自认为很有效的过滤掉很多广告、卖课等等的垃圾视频,其中关于编程、ai等等的割韭菜内容偏多,如果你要使用最好先筛选一下,避免误伤。使用方法:在
视频过滤
->标题关键词过滤
,填入正则表达式内。(表达式写法提示:以/开头和结尾,每行一个,其中用到两种屏蔽词,必须包含的词,和用|分割的任意词,没有用到复杂的写法,足够应付大多数场景了,也欢迎大佬共享一下更多有效屏蔽垃圾视频的表达式)