// ==UserScript==
// @name NodeSeek 用户画像生成器
// @name:zh-CN NodeSeek 用户画像生成器
// @name:en NodeSeek User Profiler
// @namespace https://github.com/tunecc/NodeSeek-User-Profiler
// @version 3.4
// @description 自动爬取NodeSeek用户的评论导出Markdown/CSV、生成符合 NodeSeek 生态的 AI 分析指令。
// @description:en Automatically crawl NodeSeek users' comments, export them as Markdown/CSV, and generate AI analysis commands that comply with the NodeSeek ecosystem.
// @author Tune
// @author Tune
// @homepage https://github.com/tunecc/NodeSeek-User-Profiler
// @source https://github.com/tunecc/NodeSeek-User-Profiler
// @license MIT
// @match https://www.nodeseek.com/space/*
// @match https://nodeseek.com/space/*
// @icon https://www.nodeseek.com/static/image/favicon/android-chrome-192x192.png
// @grant GM_setClipboard
// @run-at document-end
// @downloadURL https://update.greasyfork.icu/scripts/559093/NodeSeek%20%E7%94%A8%E6%88%B7%E7%94%BB%E5%83%8F%E7%94%9F%E6%88%90%E5%99%A8.user.js
// @updateURL https://update.greasyfork.icu/scripts/559093/NodeSeek%20%E7%94%A8%E6%88%B7%E7%94%BB%E5%83%8F%E7%94%9F%E6%88%90%E5%99%A8.meta.js
// ==/UserScript==
(function() {
'use strict';
// --- 配置区域 ---
const CONFIG = {
API_CONCURRENCY: 3, // 🚀 API阶段并发数 (保持高速)
DEEP_CONCURRENCY: 1, // 🛡️ 深挖阶段并发数 (强制单线程,最安全)
API_DELAY: 100, // API 请求间隔 (ms)
DEEP_DELAY: 100, // 深挖 请求间隔 (ms)
COOLING_DELAY: 5200, // 🧊 触发限速后的冷却时间
PER_PAGE_FLOOR: 10 // 硬编码:每页10楼
};
// 状态管理
let state = {
isRunning: false,
phase: 1, // 1=API, 2=深挖
processedPages: 0,
maxPage: 10,
totalItems: 0,
deepMode: false,
deepProgress: 0,
currentPostId: 0,
currentPage: 0
};
let allReplies = [];
let replyMap = new Map();
// --- 1. 样式注入 ---
function injectStyles() {
const style = document.createElement('style');
style.innerHTML = `
:root {
--ns-bg: rgba(255, 255, 255, 0.92);
--ns-border: rgba(0, 0, 0, 0.08);
--ns-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
--ns-primary: linear-gradient(135deg, #007AFF, #00C6FF);
--ns-success: linear-gradient(135deg, #34C759, #30D158);
--ns-orange: linear-gradient(135deg, #FF9500, #FFB340);
--ns-purple: linear-gradient(135deg, #AF52DE, #BF5AF2);
--ns-danger: linear-gradient(135deg, #FF3B30, #FF453A);
}
.ns-panel {
position: fixed; top: 100px; right: 20px; width: 300px;
background: var(--ns-bg); backdrop-filter: saturate(180%) blur(25px);
border: 1px solid var(--ns-border); border-radius: 20px;
box-shadow: var(--ns-shadow); font-family: -apple-system, sans-serif;
padding: 24px; z-index: 99999; animation: ns-pop 0.4s cubic-bezier(0.19, 1, 0.22, 1);
}
@keyframes ns-pop { from { opacity:0; transform:scale(0.9) translateY(10px); } to { opacity:1; transform:scale(1) translateY(0); } }
.ns-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.ns-title { font-size: 18px; font-weight: 700; color: #1d1d1f; letter-spacing: -0.5px; }
.ns-close { cursor: pointer; opacity: 0.4; transition: 0.2s; font-size: 18px; }
.ns-close:hover { opacity: 1; transform: rotate(90deg); }
.ns-input-wrap { display: flex; align-items: center; justify-content: space-between; background: #fff; border-radius: 12px; padding: 10px 14px; margin-bottom: 10px; border: 1px solid rgba(0,0,0,0.06); box-shadow: 0 2px 5px rgba(0,0,0,0.02); }
.ns-input { border: none; outline: none; font-size: 16px; font-weight: 600; width: 60px; text-align: center; color: #007AFF; }
.ns-label-row { display: flex; align-items: center; gap: 6px; font-size: 14px; color: #333; font-weight: 500; }
.ns-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 15px; }
.ns-stat { background: #fff; padding: 12px; border-radius: 14px; text-align: center; box-shadow: 0 4px 10px rgba(0,0,0,0.03); border: 1px solid rgba(0,0,0,0.04); }
.ns-stat-label { font-size: 11px; color: #86868b; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; font-weight: 600; }
.ns-stat-val { font-size: 15px; font-weight: 800; letter-spacing: -0.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ns-progress-track { height: 6px; background: rgba(0,0,0,0.06); border-radius: 3px; overflow: hidden; margin: 20px 0 10px 0; }
.ns-progress-fill { height: 100%; background: var(--ns-primary); width: 0%; transition: width 0.3s; }
.ns-btn { width: 100%; border: none; padding: 14px; border-radius: 14px; color: #fff; font-size: 15px; font-weight: 600; cursor: pointer; transition: transform 0.1s, opacity 0.2s; box-shadow: 0 8px 20px rgba(0,0,0,0.12); margin-bottom: 10px; display: flex; align-items: center; justify-content: center; gap: 8px; }
.ns-btn:active { transform: scale(0.96); }
.ns-btn:hover { opacity: 0.95; }
.ns-btn:disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(100%); }
.ns-btn-start { background: var(--ns-success); }
.ns-btn-stop { background: var(--ns-danger); }
.ns-btn-md { background: var(--ns-orange); box-shadow: 0 4px 15px rgba(255, 149, 0, 0.25); }
.ns-btn-copy { background: var(--ns-primary); box-shadow: 0 4px 15px rgba(0, 122, 255, 0.25); }
.ns-btn-csv { background: var(--ns-purple); box-shadow: 0 4px 15px rgba(175, 82, 222, 0.25); }
.ns-btn-clear { background: var(--ns-danger); margin-top: 5px; box-shadow: 0 4px 15px rgba(255, 59, 48, 0.25); }
.ns-actions { display: flex; flex-direction: column; gap: 2px; }
.ns-toast { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px 30px; border-radius: 16px; box-shadow: 0 10px 40px rgba(102, 126, 234, 0.5); z-index: 20000; font-size: 15px; font-weight: bold; text-align: center; line-height: 1.5; white-space: pre-line; animation: nsFadeIn 0.3s ease-out; max-width: 80%; }
@keyframes nsFadeIn { from { opacity:0; transform: translate(-50%, -40%); } to { opacity:1; transform: translate(-50%, -50%); } }
.ns-switch { position: relative; display: inline-block; width: 44px; height: 26px; }
.ns-switch input { opacity: 0; width: 0; height: 0; }
.ns-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e5e5ea; transition: .4s; border-radius: 34px; }
.ns-slider:before { position: absolute; content: ""; height: 22px; width: 22px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
input:checked + .ns-slider { background-color: #34C759; }
input:checked + .ns-slider:before { transform: translateX(18px); }
.ns-help-icon { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; background: #86868b; color: white; font-size: 12px; font-weight: bold; margin-left: 6px; cursor: pointer; opacity: 0.6; transition: 0.2s; }
.ns-help-icon:hover { opacity: 1; transform: scale(1.1); background: #007AFF; }
`;
document.head.appendChild(style);
}
// --- 2. 入口按钮 ---
window.addEventListener('load', () => {
setTimeout(() => {
injectStyles();
initBtn();
}, 1000);
});
function initBtn() {
if (document.getElementById('ns-entry-btn')) return;
const btn = document.createElement('div');
btn.id = 'ns-entry-btn';
btn.innerHTML = '📊';
btn.style.cssText = `
position: fixed; bottom: 80px; right: 20px; width: 52px; height: 52px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; border-radius: 50%; text-align: center; line-height: 52px;
cursor: pointer; z-index: 99998; box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
font-size: 24px; transition: transform 0.2s; user-select: none;
`;
btn.onmouseover = () => btn.style.transform = 'scale(1.1) rotate(5deg)';
btn.onmouseout = () => btn.style.transform = 'scale(1) rotate(0deg)';
btn.onclick = () => {
if (!window.location.hash.includes('/comments')) {
const baseUrl = window.location.href.split('#')[0];
if(confirm('请先进入【回复列表】页面。\n点击确定跳转...')) {
window.location.href = baseUrl + '#/comments/1';
setTimeout(() => location.reload(), 100);
}
return;
}
createControlPanel();
};
document.body.appendChild(btn);
}
// --- 3. 控制面板 ---
function createControlPanel() {
if (document.getElementById('ns-panel')) return;
const panel = document.createElement('div');
panel.id = 'ns-panel';
panel.className = 'ns-panel';
panel.innerHTML = `
准备就绪
`;
document.body.appendChild(panel);
document.getElementById('ns-close').onclick = () => panel.remove();
document.getElementById('ns-start').onclick = startExtraction;
document.getElementById('ns-stop').onclick = stopExtraction;
document.getElementById('ns-md').onclick = exportToMarkdown;
document.getElementById('ns-csv').onclick = exportToCSV;
document.getElementById('ns-copy').onclick = copyToClipboard;
document.getElementById('ns-clear').onclick = clearData;
document.getElementById('ns-help-tip').onclick = () => {
showToast(`💡 深挖模式说明\n\n1. 自动获取被截断的长回复完整内容\n2. 默认急速模式 (150ms/次),触发限速后自动冷却 (5.2s)\n3. 如果间隔设置的低,能在F12控制台看到too many requests \n`, 6000);
};
const realMax = detectTotalPages();
if (realMax > 1) {
document.getElementById('ns-pages').value = realMax;
updateStatus(`已自动检测到 ${realMax} 页数据`);
}
}
// --- 4. 核心提取逻辑 ---
function detectTotalPages() {
const pagination = document.querySelector('div[role="navigation"][aria-label="pagination"]');
if (!pagination) return 1;
let max = 1;
const links = pagination.querySelectorAll('.pager-pos');
links.forEach(el => {
const txt = el.innerText.trim().replace(/\.\./g, '');
const num = parseInt(txt);
if (!isNaN(num) && num > max) max = num;
});
return max;
}
async function startExtraction() {
const uidMatch = window.location.href.match(/\/space\/(\d+)/);
if (!uidMatch) return showToast("❌ 请在用户空间页面使用");
const uid = uidMatch[1];
const inputPages = parseInt(document.getElementById('ns-pages').value) || 10;
const isDeep = document.getElementById('ns-deep-mode').checked;
state.isRunning = true;
state.phase = 1; // 🟢 1: API阶段
state.processedPages = 0;
state.maxPage = inputPages;
state.deepMode = isDeep;
state.deepProgress = 0;
state.deepTotal = 0;
state.currentPostId = 0;
state.currentPage = 0;
allReplies = [];
replyMap.clear();
toggleUI(true);
updateStatus("🚀 正在建立 API 连接...");
const tasks = [];
for (let i = 1; i <= inputPages; i++) tasks.push(i);
const apiWorker = async () => {
while (tasks.length > 0 && state.isRunning) {
const page = tasks.shift();
try {
updateStatus(`⚡ 正在API请求第 ${page} 页...`);
const res = await fetch(`/api/content/list-comments?uid=${uid}&page=${page}`);
const json = await res.json();
if (json && json.comments && json.comments.length > 0) {
const newItems = json.comments.map(item => {
let text = item.text || "无内容";
// 🟢 严格智能判断:仅匹配 " ..." (空格+三个点)
const isTruncated = text.endsWith(" ...");
// 如果开启深挖模式,且内容被截断,则 isFull=false (需要挖)
// 否则 isFull=true (不需要挖,直接用)
const needDig = state.deepMode && isTruncated;
return {
page: page,
post_id: item.post_id,
floor_id: item.floor_id,
title: item.title || "无标题",
content: text,
isFull: !needDig, // 取反:不需要挖 = 它是完整的
url: `https://www.nodeseek.com/post-${item.post_id}-1#${item.floor_id}`
};
});
newItems.forEach(item => {
allReplies.push(item);
replyMap.set(`${item.post_id}-${item.floor_id}`, item);
});
state.totalItems = allReplies.length;
} else {
if (json.comments && json.comments.length === 0) tasks.length = 0;
}
state.processedPages++;
updateUI();
await sleep(CONFIG.API_DELAY);
} catch (e) {
console.error(`Page ${page} Error:`, e);
await sleep(1000);
}
}
};
const threads = [];
for (let i = 0; i < CONFIG.API_CONCURRENCY; i++) threads.push(apiWorker());
await Promise.all(threads);
if (state.isRunning && allReplies.length > 0 && state.deepMode) {
await startDeepScanning();
} else {
finish();
}
}
async function startDeepScanning() {
state.phase = 2; // 🟢 2: 深挖阶段
// 🟢 过滤出真正需要深挖的任务
const deepTasks = allReplies.filter(item => !item.isFull);
state.deepTotal = deepTasks.length;
state.deepProgress = 0;
if (state.deepTotal === 0) {
updateStatus("✨ 没有需要深挖的内容,跳过...");
await sleep(500);
finish();
return;
}
updateUI();
updateStatus(`🔍 智能深挖:${state.deepTotal} 条截断内容...`);
const deepWorker = async () => {
while (deepTasks.length > 0 && state.isRunning) {
const item = deepTasks.shift();
try {
let targetPage = Math.ceil(item.floor_id / CONFIG.PER_PAGE_FLOOR);
if (targetPage < 1) targetPage = 1;
state.currentPostId = item.post_id;
state.currentPage = targetPage;
updateUI();
updateStatus(`📥 正在深挖: 帖子${item.post_id} (剩${deepTasks.length})`);
const res = await fetch(`/post-${item.post_id}-${targetPage}`);
// 🚨 防封关键逻辑:检测 403/429
if (res.status === 429 || res.status === 403) {
updateStatus(`🚨 触发限速 (${res.status}),冷却 ${CONFIG.COOLING_DELAY}ms...`);
await sleep(CONFIG.COOLING_DELAY);
deepTasks.unshift(item); // 失败重试
continue;
}
if (res.status === 200) {
const text = await res.text();
const doc = new DOMParser().parseFromString(text, 'text/html');
const floorLinks = doc.querySelectorAll('.floor-link');
floorLinks.forEach(link => {
const currentFloorId = parseInt(link.innerText.replace('#', ''));
const mapKey = `${item.post_id}-${currentFloorId}`;
const targetItem = replyMap.get(mapKey);
// 只有那些被标记为不完整的目标才需要更新
if (targetItem && !targetItem.isFull) {
const container = link.closest('.content-item') || link.closest('.post-item') || link.closest('li');
if (container) {
const contentEl = container.querySelector('.post-content');
if (contentEl) {
const cleanEl = contentEl.cloneNode(true);
const quotes = cleanEl.querySelectorAll('blockquote');
quotes.forEach(q => {
const qt = q.innerText.replace(/\n/g, ' ').trim();
const mark = document.createTextNode(` (引用上下文: ${qt}) `);
q.parentNode.replaceChild(mark, q);
});
targetItem.content = cleanEl.innerText.trim();
targetItem.isFull = true;
}
}
}
});
state.deepProgress++;
updateUI();
}
await sleep(CONFIG.DEEP_DELAY);
} catch (e) {
console.error(`Fetch failed: ${item.post_id}`, e);
await sleep(1000);
}
}
};
const dThreads = [];
for (let i = 0; i < CONFIG.DEEP_CONCURRENCY; i++) dThreads.push(deepWorker());
await Promise.all(dThreads);
finish();
}
function stopExtraction() {
state.isRunning = false;
updateStatus("⏹ 已停止");
toggleUI(false);
}
function finish() {
state.isRunning = false;
toggleUI(false);
updateStatus("✨ 采集完成");
showToast(`✅ 采集完成\n共 ${allReplies.length} 条数据`);
}
// --- 5. 导出逻辑 ---
function generatePrompt() {
const uid = window.location.href.match(/\/space\/(\d+)/)?.[1] || 'User';
const date = new Date().toLocaleString();
const modeText = state.deepMode ? "完整内容版" : "API摘要版";
let md = `> ⚠️ **本内容为AI生成** \n\n`;
md += `# NodeSeek 用户画像分析任务 (${modeText})\n\n`;
md += `## 📋 任务说明\n你是一位专业的用户行为分析师,精通 **NodeSeek (一个以VPS、服务器、网络技术和羊毛信息为主的垂直社区)** 的文化与黑话。请根据下方提供的用户回复数据,深入分析该用户的完整人物画像。\n\n`;
// 🟢 核心修改:根据模式动态切换“注意事项”
if (state.deepMode) {
// 深挖模式的提示
md += `> **注意**:\n`;
md += `> 1. 内容中被标记为 \`(引用上下文: ...)\` 的部分是被回复对象的原话,仅供参考语境,**不代表用户本人的观点**。\n`;
md += `> 2. 所有回复均已通过爬虫抓取完整内容,无截断。\n\n`;
} else {
// API模式的提示
md += `> **注意**:\n`;
md += `> 部分长回复可能因为 NodeSeek API 列表限制而显示为**截断状态**(通常以 ... 结尾)。请严格基于现有的内容片段进行分析,**无需臆测缺失部分**。\n\n`;
}
md += `## 👤 分析对象\n- **用户ID**: ${uid}\n- **来源**: NodeSeek\n- **回复总数**: ${allReplies.length}\n- **数据提取时间**: ${date}\n\n`;
md += `## 💬 完整回复记录\n\n`;
const groupedMap = new Map();
allReplies.forEach(item => {
if (!groupedMap.has(item.title)) {
groupedMap.set(item.title, { page: item.page, replies: [] });
}
if (!groupedMap.get(item.title).replies.includes(item.content)) {
groupedMap.get(item.title).replies.push(item.content);
}
});
let index = 1;
for (const [title, data] of groupedMap) {
md += `### 主题 #${index}\n**所在页码**: ${data.page}\n**帖子标题**: ${title}\n**回复内容**:\n`;
data.replies.forEach(content => md += `> ${content.replace(/\n/g, '\n> ')}\n\n`);
md += `---\n`;
index++;
}
md += `
---
## 🎯 分析任务要求
请基于以上所有回复数据,从以下维度深入分析该用户,并生成一份详细的**量化用户画像报告**。
**重要**: 每个维度必须按照给定的评分标准打分,不能凭主观感觉!必须深度结合 NodeSeek 社区特色(MJJ文化、VPS折腾、羊毛党等)。
---
## 📊 评分标准与分析维度
### 1. 技术能力评估 💻 (1-10分)
**评分标准**:
- **1-3分 (小白/伸手党)**: 不懂Linux,常问基础问题(如"怎么SSH" "怎么搭梯子"),找一键脚本,对网络线路(CN2/9929)无概念,只会用面板(宝塔/1Panel)。
- **4-6分 (进阶玩家/MJJ)**: 会玩Docker,懂科学上网原理,能自行搭建简单服务(图床/探针),了解线路差异,会基本的Linux命令。
- **7-8分 (运维/折腾党)**: 熟悉Linux底层,懂网络架构(BGP/ASN),能手写脚本,玩软路由/虚拟化(PVE/ESXi),会优化线路,折腾内网穿透/IPv6。
- **9-10分 (硬核大佬/开发者)**: 开发过知名开源项目,IDC从业者,能进行逆向工程,对核心网/路由表有深刻理解,发布原创技术教程。
**量化指标**:
- 技术关键词: (Docker, Python, Go, BGP, ASN, K8s, 软路由, 编译, 逆向, Shell)
- 是否发布过原创教程/脚本: 是/否
### 2. 消费能力评估 💰 (1-10分)
**评分标准**:
- **1-3分 (白嫖/挂逼/丐帮)**: 只关注免费鸡(Free Tier)、0元购、Bug价,极其价格敏感,为了几块钱纠结,常参与抽奖。
- **4-6分 (性价比党)**: 关注高性价比年付机(如10-30刀/年),偶尔收二手传家宝,预算适中,追求极致性价比。
- **7-8分 (氪金玩家/抚摸党)**: 常买一线大厂(DMIT, 搬瓦工GIA, 斯巴达, 瓦工),不屑于灵车,拥有多台高配独服,设备"吃灰"也买,追求线路质量。
- **9-10分 (富哥/老板)**: 拥有自己的ASN,托管大量设备,甚至自己开IDC,交易金额巨大,对价格不敏感。
**分析要点**:
- 关注的价格区间 (1元鸡 vs 杜甫)
- 交易行为 (收/出/溢价收)
- 对"灵车"(跑路风险高的商家)的态度
### 3. 专业深度评估 🎓 (1-10分)
**评分标准**:
- **1-3分**: 泛泛而谈,缺乏专业见解,只有情绪化表达。
- **4-6分**: 能列出简单的参数,知道基本的测试工具(YABS/融合怪),但不够深入。
- **7-8分**: 能深入分析线路质量(丢包率/抖动/路由跳数),了解硬件性能瓶颈,能给出专业的选购建议。
- **9-10分**: 行业专家,对IDC市场格局、网络协议、硬件架构有深刻见解,能预判商家跑路风险。
**分析要点**:
- 发言是否带有测试数据/截图
- 是否能纠正他人的错误观点
### 4. 社交活跃度 👥 (1-10分)
**评分标准**:
- **1-3分 (潜水党)**: 几乎不发帖,只看不回,或者只回"分母"抽奖。
- **4-6分 (普通用户)**: 偶尔回复感兴趣的话题,参与度一般。
- **7-8分 (活跃分子)**: 经常出没于各个板块,热衷于"吃瓜"、讨论,回复速度快。
- **9-10分 (水王/KOL)**: 社区熟脸,发帖量巨大,无处不在,也是社区熟脸,发帖量巨大。
**量化指标**:
- 平均回复长度
- 是否热衷于"抢楼"或"前排"
---
### 5. 兴趣广度评估 🎮 (1-10分)
**评分标准**:
- **1-3分**: 仅关注VPS/服务器单一领域。
- **4-6分**: 关注VPS以及周边的(域名/SSL/面板)话题。
- **7-8分**: 涉猎广泛,包括加密货币、数码产品、羊毛福利、甚至生活情感。
- **9-10分**: 百科全书,从服务器到修电脑,从炒币到炒股,无所不知。
**量化指标**:
- 跨板块回复的比例
---
### 6. 情绪稳定性 🧩 (1-10分)
**评分标准**:
- **1-3分 (暴躁老哥)**: 容易破防,喜欢对线,攻击性强,经常使用侮辱性词汇。
- **4-6分 (普通)**: 偶尔会有情绪化表达,大部分时间正常。
- **7-8分 (理性)**: 就事论事,不卑不亢,即使面对争论也能保持冷静。
- **9-10分 (圣人)**: 极其友善,乐于助人,面对小白问题也不厌其烦,从不引战。
---
### 7. 生活品质指数 🌟 (1-10分)
**评分标准**:
- **1-3分**: 经常抱怨生活,为了极小的羊毛花费大量时间,生活焦虑。
- **4-6分**: 普通打工人状态,偶尔分享生活琐事。
- **7-8分**: 偶尔晒出高价值物品(NAS/MacBook/软路由),生活富足。
- **9-10分**: 财富自由,讨论移民、海外置业、高端生活方式。
---
### 8. 影响力指数 🏆 (1-10分)
**评分标准**:
- **1-3分**: 透明人,无人认识。
- **4-6分**: 熟脸,ID有一定辨识度。
- **7-8分**: 在某个领域(如脚本开发/线路分析)有话语权,被他人@请教。
- **9-10分**: 社区大佬,一呼百应,发布的帖子通常是热门。
---
### 9. 学习成长力 📈 (1-10分)
**评分标准**:
- **1-3分**: 固步自封,只做伸手党,不愿意学习新知识。
- **4-6分**: 遇到问题会尝试搜索,能照着教程做。
- **7-8分**: 经常分享新的技术发现,热衷于尝试新软件/新架构。
- **9-10分**: 技术引领者,将外部的新技术引入社区,编写文档。
---
### 10. 真实度/可信度 🎭 (1-10分)
**评分标准**:
- **1-3分 (骗子/小号)**: 注册时间短,专门发广告/诈骗信息,或者只在交易区活跃且无信用背书。
- **4-6分 (普通)**: 正常用户,无不良记录。
- **7-8分 (信用良好)**: 交易记录良好,发言真实可信。
- **9-10分 (权威认证)**: 论坛元老,知名开发者,或经过验证的商家代表。
---
### 11. 社区角色定位 🏷️ (关键)
请判断该用户在 NodeSeek 生态中的角色:
- **普通 MJJ**: 大多数用户的状态,折腾VPS,偶尔灌水,寻找性价比。
- **技术大牛**: 社区的技术支柱,发布脚本/教程。
- **商家/客服**: IDC 代表,发布促销信息,处理工单。
- **Affman (推广员)**: 发言主要目的是为了发带有返利链接(Aff)的推广内容,极力吹捧某些商家。
- **黄牛 (倒狗)**: 活跃于交易区,低价收传家宝,高价卖出,以赚差价为生。
- **羊毛党**: 哪里有免费/便宜去哪里,热衷于抽奖、领币。
- **乐子人**: 喜欢看热闹,发表情包,阴阳怪气,不嫌事大。
---
### 12. 交易信誉与风险 🛡️
**分析要点**:
- **交易风格**: 爽快/磨叽/斤斤计较/先款/中介。
- **历史记录**: 是否有被挂人(争议)记录?
- **潜在风险**: 是否频繁更换账号?是否只在特定时间段活跃?
- **特殊身份**: 是否为 **Affman** (推广员) 或 **黄牛** (倒狗)?
---
### 13. 生活地域推断 🏠
**不评分,仅推断**
**分析要点**:
- **居住城市**: _____ (根据讨论的宽带运营商、提及的地点、时区推断)
- **证据强度**: 强/中/弱
- **可能的活动范围**: _____
---
### 14. 欺诈风险指数 🚩
**评分标准**:
- **1-3分 (安全)**: 信用极高,长期活跃的大佬/商家,有大量历史交易记录且无争议。
- **4-6分 (普通)**: 普通用户,无不良记录,交易需谨慎但基本安全。
- **7-8分 (高危预警)**: 风险较高,可能是买号/新号,或者有过激言论,建议走中介。
- **9-10分 (极高风险)**: 骗子特征明显(如:只出不收、价格离谱、催促交易、私聊交易),建议立即拉黑。
**分析要点**:
- 账号注册时间与活跃度是否匹配
- 是否有“急出”、“先款”等高风险关键词
- 历史回复中是否有被挂(争议)记录
---
## 📋 综合评价
### 综合画像卡片
| 维度 | 评分 | 等级 | 关键特征 |
|------|------|------|---------|
| 技术能力 | __/10 | 专家/进阶/小白 | _____ |
| 消费能力 | __/10 | 富哥/中产/挂逼 | _____ |
| 活跃度 | __/10 | 水王/活跃/潜水 | _____ |
| 交易风险 | __/10 | 高/中/低 | _____ |
| 真实度 | __/10 | 真实/存疑/小号 | _____ |
| 欺诈指数 | __/10 | 高危/中/低/安全 | _____ |
### 用户画像总结 (300字以内)
[用简练的语言描述该用户的整体特征,例如:"一位典型的挂逼MJJ,热衷于收集各种免费资源和灵车VPS,对技术一知半解但热衷于凑热闹..." 或 "一位潜伏在论坛的Linux运维大佬,偶尔分享高质量脚本,对Affman深恶痛绝..."]
### 核心标签 🏷️
\`#标签1\` \`#标签2\` \`#标签3\` \`#标签4\` \`#标签5\`
### 核心洞察 💡 (原版复刻)
**优势特征**(最突出的3个方面):
1. _____
2. _____
3. _____
**潜在需求**(可能感兴趣的3个方向):
1. _____
2. _____
3. _____
**性格特质**(MBTI参考):
- 可能的性格类型: _____
- 主要性格特征: _____
---
## 📋 输出格式要求
1. **严格按照评分标准打分**,不得凭感觉评分
2. **必须列出量化指标的具体数值**
3. **每个评分必须有具体的证据支撑**(需引用具体回复内容或楼层,例如:"如回复#3所示...")
4. **填写综合评价表格**
5. **生成200-300字的用户画像总结**
6. **给出3-5个标签**
7. **不用重新输出评分标准,只给出要求的结果**
---
## ⚡ 开始分析
请开始你的专业量化分析,注意:
✅ **量化优先**: 先统计量化指标,再基于数据打分
✅ **证据支撑**: 每个结论都要引用具体回复作为证据
✅ **客观准确**: 基于实际数据,不要过度臆测
✅ **标准一致**: 严格按照评分标准,不得凭主观感觉
---
*本文档由 NodeSeek 用户回复提取器自动生成* *提取时间: ${date}* *数据量: ${allReplies.length} 条回复*
`;
return md;
}
function exportToMarkdown() {
if (allReplies.length === 0) return showToast('没有数据可导出');
const md = generatePrompt();
const uid = window.location.href.match(/\/space\/(\d+)/)?.[1] || 'User';
download(md, `nodeseek_${uid}_analysis.md`, 'text/markdown');
showToast(`✅ 成功导出 MD\n文件名: nodeseek_${uid}_analysis.md\n回复数: ${allReplies.length} 条`);
}
function exportToCSV() {
if (allReplies.length === 0) return showToast('没有数据可导出');
const headers = ['页码', '帖子标题', '回复内容'];
let csv = '\uFEFF' + headers.join(',') + '\n';
allReplies.forEach(r => {
csv += `${r.page},"${(r.title||'').replace(/"/g,'""')}","${(r.content||'').replace(/"/g,'""')}"\n`;
});
download(csv, 'nodeseek_replies.csv', 'text/csv');
showToast(`✅ 成功导出 CSV\n共 ${allReplies.length} 条`);
}
async function copyToClipboard() {
if (allReplies.length === 0) return showToast('没有数据可复制');
try {
const md = generatePrompt();
await navigator.clipboard.writeText(md);
showToast(`✅ 复制成功!\n${allReplies.length} 条回复已存入剪贴板`);
} catch(e) {
alert('复制失败,请手动导出');
}
}
function clearData() {
if(confirm('确定清空所有数据吗?')) {
allReplies = [];
state.processedPages = 0;
state.deepProgress = 0;
updateUI();
showToast('🗑️ 数据已清空');
}
}
// --- 辅助函数 ---
function updateUI() {
const elCount = document.getElementById('ns-count');
const elPage = document.getElementById('ns-page-txt');
const elBar = document.getElementById('ns-bar');
if (elCount) elCount.innerText = allReplies.length;
if (elPage) {
// 🟢 如果当前是深挖阶段 (Phase 2)
if (state.deepMode && state.phase === 2) {
// 显示为:深挖(P5) 20 / 100
elPage.innerText = `深挖(P${state.currentPage || '-'}) ${state.deepProgress} / ${state.deepTotal}`; // 🟢 修复显示:分母为实际需挖数
elPage.style.color = '#AF52DE'; // 紫色
if (elBar) {
const pct = Math.min(100, (state.deepProgress / state.deepTotal) * 100); // 🟢 修复进度条
elBar.style.width = `${pct}%`;
elBar.style.background = 'linear-gradient(135deg, #AF52DE, #BF5AF2)';
}
} else {
// 🟢 否则显示 API 进度
elPage.innerText = `API ${state.processedPages} / ${state.maxPage}`;
elPage.style.color = '#007AFF'; // 蓝色
if (elBar && state.maxPage > 0) {
const pct = Math.min(100, (state.processedPages / state.maxPage) * 100);
elBar.style.width = `${pct}%`;
elBar.style.background = 'var(--ns-primary)';
}
}
}
}
function toggleUI(running) {
const startArea = document.getElementById('btn-start-area');
const stopArea = document.getElementById('btn-stop-area');
const config = document.getElementById('ns-config');
if(startArea) startArea.style.display = running ? 'none' : 'block';
if(stopArea) stopArea.style.display = running ? 'block' : 'none';
if(config) {
document.getElementById('ns-pages').disabled = running;
document.getElementById('ns-deep-mode').disabled = running;
}
}
function updateStatus(text) {
const el = document.getElementById('ns-status-txt');
if(el) el.innerText = text;
}
function showToast(msg, duration = 2500) {
const t = document.createElement('div');
t.className = 'ns-toast';
t.innerText = msg;
document.body.appendChild(t);
setTimeout(() => t.remove(), duration);
}
function download(content, filename, type) {
const blob = new Blob([content], {type});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
})();