1. 项目背景与需求分析
在内容社区平台中,活跃用户与机器人账号的边界往往比较模糊。以虎嗅网为例,某些用户账号可能以极高的评论频率发布低质量内容,这些账号的评论页数常常达到数百页之多。这类账号要么是营销机器人,要么是极端偏执的刷屏用户,无论哪种情况,他们的评论都会严重干扰正常用户的阅读体验。
传统的手动屏蔽方式存在几个痛点:
- 需要逐个点击用户主页查看评论数量,操作繁琐
- 无法预防性屏蔽新出现的刷屏账号
- 页面刷新后需要重复操作
这个油猴脚本正是为了解决这些问题而生。它通过自动化检测用户历史评论量,实现对刷屏账号的智能过滤。核心思路是:如果一个用户的评论需要100页以上才能展示完(按虎嗅默认每页20条计算,即超过2000条评论),那么其内容大概率不值得阅读。
2. 技术实现方案解析
2.1 整体架构设计
脚本采用"监控-采集-决策-执行"的工作流:
- 监控层:通过MutationObserver监听DOM变化,实时捕获新加载的评论
- 采集层:从评论元素中提取用户ID,调用虎嗅API获取历史评论数据
- 决策层:比对用户评论页数与预设阈值(默认100页)
- 执行层:对超标账号的评论进行视觉隐藏
这种架构的优势在于:
- 完全前端实现,不依赖后端服务
- 实时响应页面变化
- 决策逻辑可灵活调整
2.2 关键代码解析
用户ID提取逻辑
javascript复制function extractUserId(commentElement) {
// 多策略兼容提取
const strategies = [
// 策略1:会员链接提取
() => {
const links = commentElement.querySelectorAll('a[href*="/member/"]');
for (const link of links) {
const match = link.href.match(/\/member\/(\d+)/);
if (match) return match[1];
}
},
// 策略2:data属性提取
() => {
let el = commentElement;
for (let i = 0; i < 5; i++) {
const uid = el.dataset.uid || el.dataset.userId;
if (uid) return uid;
el = el.parentElement;
}
},
// 策略3:class/id模式匹配
() => {
const elements = commentElement.querySelectorAll('[class*="user"],[id*="user"]');
for (const el of elements) {
const match = el.className.match(/user[_-]?(\d+)/) ||
el.id.match(/user[_-]?(\d+)/);
if (match) return match[1];
}
}
];
for (const strategy of strategies) {
const uid = strategy();
if (uid) return uid;
}
return null;
}
这段代码体现了健壮性设计的几个要点:
- 多策略兼容确保不同页面结构下的可用性
- 采用DOM遍历而非固定路径,适应虎嗅可能的界面改版
- 逐步回退策略,优先使用最可靠的提取方式
API请求管理
javascript复制const pendingChecks = new Set();
function getUserCommentPages(uid) {
if (pendingChecks.has(uid)) {
return new Promise((resolve) => {
setTimeout(() => resolve(getUserCommentPages(uid)), 500);
});
}
pendingChecks.add(uid);
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://api-web-account.huxiu.com/web/comment/commentList',
data: `platform=www&page=1&uid=${uid}`,
onload: (response) => {
pendingChecks.delete(uid);
try {
const data = JSON.parse(response.responseText);
resolve(data?.data?.total_page || 0);
} catch (e) {
reject(e);
}
},
onerror: (err) => {
pendingChecks.delete(uid);
reject(err);
}
});
});
}
这里有两个关键技术点:
- 请求队列管理:使用Set记录进行中的请求,避免重复查询
- 错误隔离:单个请求失败不会影响整体运行
3. 核心功能实现细节
3.1 动态评论检测
脚本采用三级检测机制确保不漏掉任何评论:
- 初始扫描:页面加载完成后2秒执行全量检查
- DOM监听:通过MutationObserver捕获动态加载的内容
- 定时轮询:每2秒补充检查一次(应对某些特殊渲染情况)
javascript复制function setupDetection() {
// 初始延迟检测
setTimeout(checkAllComments, 2000);
// DOM变化监听
const observer = new MutationObserver((mutations) => {
if (mutations.some(m => m.addedNodes.length > 0)) {
setTimeout(checkAllComments, 1000);
}
});
observer.observe(document.body, { childList: true, subtree: true });
// 定时轮询
setInterval(checkAllComments, CHECK_INTERVAL);
}
3.2 评论隐藏策略
不同于简单的display:none,脚本采用更友好的隐藏方式:
- 保留DOM位置避免页面跳动
- 添加视觉提示说明有内容被过滤
- 设置data-filtered标记供后续识别
javascript复制function hideComment(commentElement) {
// 创建替代元素
const placeholder = document.createElement('div');
placeholder.className = 'comment-filtered-placeholder';
placeholder.innerHTML = `
<div style="padding:8px 12px;background:#f8f8f8;color:#999;">
已过滤高活跃度用户评论
<a href="#" style="margin-left:8px;color:#666;"
onclick="this.parentNode.nextElementSibling.style.display='block';this.parentNode.remove();return false;">
显示
</a>
</div>
`;
// 插入替代元素
commentElement.before(placeholder);
commentElement.style.display = 'none';
commentElement.dataset.filtered = 'true';
}
4. 性能优化实践
4.1 请求批处理与限流
为避免短时间内发起过多API请求:
- 设置5个请求的批量大小
- 批次间添加500ms间隔
- 失败请求自动重试2次
javascript复制async function batchCheck(comments) {
const BATCH_SIZE = 5;
const RETRY_LIMIT = 2;
for (let i = 0; i < comments.length; i += BATCH_SIZE) {
const batch = comments.slice(i, i + BATCH_SIZE);
await Promise.all(batch.map(comment => {
let retryCount = 0;
const check = async () => {
try {
await checkComment(comment);
} catch (err) {
if (retryCount++ < RETRY_LIMIT) {
await new Promise(r => setTimeout(r, 1000 * retryCount));
return check();
}
console.error(`检查失败:`, err);
}
};
return check();
}));
if (i + BATCH_SIZE < comments.length) {
await new Promise(r => setTimeout(r, 500));
}
}
}
4.2 本地缓存策略
虽然脚本默认不启用缓存,但可以通过localStorage实现优化:
javascript复制const cache = {
get(uid) {
const data = localStorage.getItem(`huxiu:${uid}`);
return data ? JSON.parse(data) : null;
},
set(uid, data) {
localStorage.setItem(`huxiu:${uid}`,
JSON.stringify({
data,
timestamp: Date.now()
}));
},
clean() {
Object.keys(localStorage)
.filter(k => k.startsWith('huxiu:'))
.forEach(k => {
const { timestamp } = JSON.parse(localStorage.getItem(k));
if (Date.now() - timestamp > 86400000) { // 24小时
localStorage.removeItem(k);
}
});
}
};
5. 常见问题排查指南
5.1 用户ID提取失败
现象:控制台出现"无法提取用户ID"警告
排查步骤:
- 检查元素结构是否变化
- 在控制台执行
document.querySelector('.comment-item').outerHTML查看最新结构 - 根据实际结构补充提取策略
5.2 API返回页数为0
可能原因:
- 虎嗅API限制未登录用户的查询
- 接口响应格式变更
- 用户确实没有评论
解决方案:
javascript复制// 修改API请求添加登录态
headers: {
'X-Token': localStorage.getItem('huxiu_token') || ''
}
5.3 隐藏后出现空白区域
优化方案:
javascript复制function optimizeLayout() {
document.querySelectorAll('[data-filtered]').forEach(el => {
if (el.previousElementSibling?.classList.contains('comment-filtered-placeholder')) {
el.previousElementSibling.style.marginBottom =
window.getComputedStyle(el).marginBottom;
}
});
}
// 在每次过滤后调用
6. 扩展与定制建议
6.1 参数配置化
在脚本头部添加可配置项:
javascript复制// ==UserConfig==
// @configname 过滤阈值
// @configid threshold
// @configtype number
// @configdefault 100
// @configdescription 设置触发过滤的评论页数阈值
// ==/UserConfig==
const config = {
threshold: typeof GM_config !== 'undefined' ?
GM_config.get('threshold') : 100,
// 其他配置项...
};
6.2 多平台适配
通过修改@match规则实现:
javascript复制// @match https://*.huxiu.com/*
// @match https://*.huxiu.cn/*
// @match https://*.huxiu.net/*
6.3 可视化统计
添加统计面板:
javascript复制function showStats() {
const stats = {
total: 0,
filtered: 0,
lastUpdated: new Date()
};
return {
addFiltered() {
stats.total++;
stats.filtered++;
updateUI();
},
addPassed() {
stats.total++;
updateUI();
}
};
function updateUI() {
// 渲染统计信息到页面角落
}
}
这个脚本的核心价值在于其设计思路的通用性——通过用户行为特征而非内容本身进行过滤,这种模式可以迁移到任何内容平台。我在实际使用中发现,配合适当的阈值调整(比如针对不同平台设置不同的页数阈值),可以消除90%以上的刷屏干扰,同时几乎不会误伤正常用户。