在小红书笔记页面添加两个悬浮按钮:1. 自动展开所有评论;2. 导出所有评论(包含帖子链接和完整信息)为TXT文件。
// ==UserScript==
// @name 小红书评论区助手
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 在小红书笔记页面添加两个悬浮按钮:1. 自动展开所有评论;2. 导出所有评论(包含帖子链接和完整信息)为TXT文件。
// @author Gao + Claude
// @match https://www.xiaohongshu.com/explore/*
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- 样式定义 (与之前相同) ---
GM_addStyle(`
.xhs-helper-btn-container {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
}
.xhs-helper-btn {
padding: 10px 15px;
background-color: #ff2442;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transition: background-color 0.3s, transform 0.2s;
width: 150px;
text-align: center;
}
.xhs-helper-btn:hover {
background-color: #e01f38;
}
.xhs-helper-btn:disabled {
background-color: #cccccc;
cursor: not-allowed;
transform: none;
}
.xhs-helper-btn:active:not(:disabled) {
transform: scale(0.98);
}
`);
// --- 创建按钮 (与之前相同) ---
const container = document.createElement('div');
container.className = 'xhs-helper-btn-container';
const expandButton = document.createElement('button');
expandButton.id = 'expand-comments-btn';
expandButton.className = 'xhs-helper-btn';
expandButton.innerText = '自动展开评论';
const exportButton = document.createElement('button');
exportButton.id = 'export-comments-btn';
exportButton.className = 'xhs-helper-btn';
exportButton.innerText = '导出全部评论';
container.appendChild(expandButton);
container.appendChild(exportButton);
document.body.appendChild(container);
// --- 核心功能实现 (大部分与之前相同) ---
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function handleExpandComments() {
let clickCount = 0;
expandButton.disabled = true;
expandButton.innerText = '展开中...';
try {
while (true) {
const moreButtons = document.querySelectorAll('.show-more, .bottom-bar .loading.active');
if (moreButtons.length === 0) {
console.log('未找到可展开的按钮,任务完成。');
expandButton.innerText = '全部已展开';
break;
}
console.log(`找到 ${moreButtons.length} 个展开按钮,正在点击...`);
moreButtons.forEach(btn => {
btn.click();
clickCount++;
expandButton.innerText = `展开中... (${clickCount})`;
});
await sleep(1500);
}
} catch (error) {
console.error('展开评论时发生错误:', error);
expandButton.innerText = '出现错误';
}
}
function handleExportComments() {
exportButton.disabled = true;
exportButton.innerText = '正在导出...';
console.log('开始提取评论内容...');
let exportText = '';
// --- 新增功能:获取并添加帖子链接 ---
const postUrl = window.location.href;
exportText += `帖子链接: ${postUrl}\n\n`;
const noteTitle = document.querySelector('#detail-title')?.textContent.trim() || '未知标题';
const authorName = document.querySelector('.author-wrapper .name .username')?.textContent.trim() || '未知作者';
const noteDesc = document.querySelector('#detail-desc')?.textContent.trim() || '无';
exportText += `笔记标题: ${noteTitle}\n`;
exportText += `作者: ${authorName}\n\n`;
exportText += `笔记内容:\n${noteDesc}\n\n`;
exportText += "==================== 评论区 ====================\n\n";
const comments = document.querySelectorAll('.parent-comment');
if (comments.length === 0) {
exportText += "当前页面没有评论。";
}
comments.forEach((comment, index) => {
const mainCommentInfo = extractCommentInfo(comment);
exportText += `【${index + 1}楼】 ${mainCommentInfo.userName} (点赞: ${mainCommentInfo.likes})\n`;
exportText += `时间: ${mainCommentInfo.time} ${mainCommentInfo.location}\n`;
exportText += `内容: ${mainCommentInfo.content}\n`;
const replies = comment.querySelectorAll('.reply-container .comment-item');
if (replies.length > 0) {
exportText += "--- 回复 ---\n";
replies.forEach(reply => {
const replyInfo = extractCommentInfo(reply);
exportText += ` -> ${replyInfo.userName} (点赞: ${replyInfo.likes}): ${replyInfo.content}\n`;
if(replyInfo.time || replyInfo.location){
exportText += ` 时间: ${replyInfo.time} ${replyInfo.location}\n`;
}
});
}
exportText += '----------------------------------------\n\n';
});
console.log('评论提取完成,准备下载...');
downloadTextFile(exportText, `${noteTitle}_评论区.txt`);
exportButton.innerText = '导出完成!';
setTimeout(() => {
exportButton.disabled = false;
exportButton.innerText = '导出全部评论';
}, 3000);
}
function extractCommentInfo(element) {
const userElement = element.querySelector('.name');
const contentElement = element.querySelector('.content');
const timeElement = element.querySelector('.info .date');
const locationElement = element.querySelector('.info .location');
const likeCountElement = element.querySelector('.like-wrapper .count');
const userName = userElement?.textContent.trim().replace(/\s*回复\s*.*/, '') || '未知用户';
const content = contentElement?.textContent.trim() || '(无文本内容)';
const time = timeElement?.textContent.trim() || '';
const location = locationElement ? `IP属地: ${locationElement.textContent.trim()}` : '';
let likes = '0';
if (likeCountElement) {
const likeText = likeCountElement.textContent.trim();
if (likeText && !isNaN(parseInt(likeText, 10))) {
likes = likeText;
}
}
return { userName, content, time, location, likes };
}
function downloadTextFile(text, filename) {
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
// --- 绑定事件监听 ---
expandButton.addEventListener('click', handleExpandComments);
exportButton.addEventListener('click', handleExportComments);
})();