NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name 知乎回答评论导出
// @namespace https://github.com/zhihu-comment-exporter
// @version 1.0.0
// @description 导出知乎回答内容、评论及子评论为 JSON/Markdown
// @copyright 2026, mikecoding (https://openuserjs.org/users/mikecoding)
// @match https://www.zhihu.com/question/*
// @match https://zhihu.com/question/*
// @grant GM_addStyle
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ======================= 配置 =======================
const CONFIG = {
commentPageSize: 20, // 根评论每页数量
maxRootComments: 0, // 最大根评论数(0 = 不限)
maxChildComments: 0, // 最大子评论数(0 = 不限)
requestDelay: 300, // API 请求间隔 (ms)
expandRetryCount: 10, // 展开内容后重试次数
expandRetryDelay: 500, // 展开内容后重试间隔 (ms)
};
// ======================= 样式 =======================
GM_addStyle(`
.zh-ce-panel {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 99999;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 24px rgba(0,0,0,0.15);
overflow: hidden;
min-width: 220px;
transition: box-shadow 0.2s;
}
.zh-ce-panel:hover {
box-shadow: 0 6px 32px rgba(0,0,0,0.22);
}
.zh-ce-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: linear-gradient(135deg, #0066ff, #0052cc);
color: #fff;
cursor: pointer;
user-select: none;
font-size: 13px;
font-weight: 600;
gap: 8px;
}
.zh-ce-header:hover { background: linear-gradient(135deg, #005ce6, #0047b3); }
.zh-ce-arrow {
display: inline-block;
transition: transform 0.25s;
font-size: 11px;
flex-shrink: 0;
}
.zh-ce-panel.collapsed .zh-ce-arrow { transform: rotate(-90deg); }
.zh-ce-body {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 14px;
max-height: 200px;
overflow: hidden;
transition: max-height 0.3s, padding 0.3s, opacity 0.3s;
}
.zh-ce-panel.collapsed .zh-ce-body {
max-height: 0;
padding-top: 0;
padding-bottom: 0;
opacity: 0;
pointer-events: none;
}
.zh-ce-body button {
width: 100%;
padding: 9px 0;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
color: #fff;
transition: all 0.15s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
white-space: nowrap;
}
.zh-ce-body button:hover { filter: brightness(1.1); transform: scale(1.02); }
.zh-ce-body button:active { transform: scale(0.98); }
.zh-ce-body button:disabled { pointer-events: none; opacity: 0.5; transform: none; }
.zh-ce-btn-json { background: linear-gradient(135deg, #0066ff, #0052cc); }
.zh-ce-btn-md { background: linear-gradient(135deg, #52c41a, #389e0d); }
@keyframes zh-ce-spin {
to { transform: rotate(360deg); }
}
.zh-ce-spinner {
display: none;
width: 12px; height: 12px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: #fff;
border-radius: 50%;
animation: zh-ce-spin 0.6s linear infinite;
}
button.zh-ce-loading .zh-ce-spinner { display: inline-block; }
button.zh-ce-loading .zh-ce-label { display: none; }
.zh-ce-toast {
position: fixed; top: 20px; left: 50%;
transform: translateX(-50%);
z-index: 199999;
padding: 10px 24px;
border-radius: 6px;
color: #fff;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
max-width: 80vw;
text-align: center;
}
.zh-ce-toast.show { opacity: 1; }
.zh-ce-toast.success { background: #52c41a; }
.zh-ce-toast.error { background: #ff4d4f; }
.zh-ce-toast.info { background: #1890ff; }
`);
// ======================= 工具 =======================
function toast(msg, type) {
type = type || 'info';
var existing = document.querySelector('.zh-ce-toast');
if (existing) existing.remove();
var el = document.createElement('div');
el.className = 'zh-ce-toast ' + type;
el.textContent = msg;
document.body.appendChild(el);
requestAnimationFrame(function () { el.classList.add('show'); });
setTimeout(function () {
el.classList.remove('show');
setTimeout(function () { el.remove(); }, 300);
}, 4000);
}
function sleep(ms) {
return new Promise(function (r) { setTimeout(r, ms); });
}
function parseUrl() {
var m = location.pathname.match(/\/question\/(\d+)(?:\/answer\/(\d+))?/);
return m ? { questionId: m[1], answerId: m[2] || null } : null;
}
function stripHtml(html) {
if (!html) return '';
var div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
}
// ======================= 答案提取 =======================
/**
* 展开所有折叠内容,并轮询等待 DOM 更新
* 返回是否展开了任何内容
*/
function autoExpandAndWait() {
return new Promise(function (resolve) {
var expandBtns = document.querySelectorAll(
'.ContentItem-expandButton, .QuestionRichText-more'
);
var clicked = 0;
expandBtns.forEach(function (btn) {
try {
// 只点 "阅读全文" 类的按钮,不要点 "收起"
if (/收起|折叠|收起评论/.test(btn.textContent)) return;
btn.click();
clicked++;
} catch (e) { /* ignore */ }
});
if (clicked === 0) {
resolve(0);
return;
}
console.log('[知乎导出] 展开了 ' + clicked + ' 个折叠内容,等待 DOM 更新...');
// 轮询检查:等 DOM 内容长度不再增长了再继续
var retries = 0;
var lastLength = 0;
function check() {
var contentEls = document.querySelectorAll('.RichContent-inner, .RichContent');
var currentLength = 0;
contentEls.forEach(function (el) { currentLength += el.textContent.length; });
retries++;
if (currentLength === lastLength && currentLength > 0 && retries >= 3) {
// 内容不再变化,认为加载完成
console.log('[知乎导出] 内容稳定,总长度: ' + currentLength);
resolve(clicked);
} else if (retries >= CONFIG.expandRetryCount) {
// 超时
console.log('[知乎导出] 等待超时,总长度: ' + currentLength);
resolve(clicked);
} else {
lastLength = currentLength;
setTimeout(check, CONFIG.expandRetryDelay);
}
}
// 首次等待后开始检查
setTimeout(check, 600);
});
}
/**
* 从 SSR + DOM 提取所有答案
*/
function extractAllAnswers() {
var ssrScript = document.getElementById('js-initialData');
var entities = {};
if (ssrScript) {
try {
var ssrData = JSON.parse(ssrScript.textContent);
entities = ssrData.initialState && ssrData.initialState.entities || {};
} catch (e) { /* ignore */ }
}
var answerMap = {};
// ---- 步骤 1: 从 SSR entities.answers 获取 ----
if (entities.answers) {
Object.keys(entities.answers).forEach(function (id) {
var answer = entities.answers[id];
if (!answer || answer.type !== 'answer') return;
answerMap[id] = {
id: id,
author: {
name: (answer.author && answer.author.name) || '未知',
urlToken: (answer.author && answer.author.url_token) || '',
headline: (answer.author && answer.author.headline) || '',
avatarUrl: (answer.author && answer.author.avatar_url) || '',
},
content: answer.content || '',
contentText: stripHtml(answer.content) || answer.excerpt || '',
voteupCount: answer.voteupCount || 0,
commentCount: answer.commentCount || 0,
createdTime: answer.createdTime || 0,
updatedTime: answer.updatedTime || 0,
questionTitle: entities.questions && entities.questions[answer.question && answer.question.id] ? entities.questions[answer.question.id].title : '',
url: answer.url || '',
_source: 'ssr',
};
});
}
// ---- 步骤 2: 从 DOM .AnswerItem 获取(覆盖内容 + 补充滚动加载的答案) ----
var cards = document.querySelectorAll('.AnswerItem');
cards.forEach(function (card) {
// 提取 answerId
var dataZopEl = card.querySelector('[data-zop]');
var dataZop = dataZopEl ? dataZopEl.getAttribute('data-zop') || '' : '';
var answerLink = card.querySelector('a[href*="/answer/"]');
var answerLinkHref = answerLink ? answerLink.getAttribute('href') || '' : '';
var answerId =
(dataZop.match(/answer-(\d+)/) || [])[1] ||
(answerLinkHref.match(/\/answer\/(\d+)/) || [])[1] ||
'';
// 提取 DOM 内容
var contentEl =
card.querySelector('.RichContent-inner') ||
card.querySelector('.RichContent');
var domContent = contentEl ? contentEl.innerHTML : '';
var domText = stripHtml(domContent);
if (answerId && answerMap[answerId]) {
// SSR 已有,用 DOM 内容覆盖(优先用展开后的完整内容)
var existing = answerMap[answerId];
if (domContent && domContent.length > existing.content.length) {
existing.content = domContent;
existing.contentText = domText;
}
// 用 DOM 修正 commentCount
var btns = card.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
if (/条评论/.test(btns[i].textContent)) {
var domCount = parseInt(btns[i].textContent.replace(/[^\d]/g, '') || '0');
if (domCount > 0) existing.commentCount = domCount;
break;
}
}
existing._source = 'ssr+dom';
} else if (answerId) {
// 只在 DOM 中(通过滚动加载的答案)
var authorEl = card.querySelector(
'.AuthorInfo-name a, .UserLink-link, .AnswerAuthor-user-name a'
);
var headlineEl = card.querySelector(
'.AuthorInfo-headline, .AnswerAuthor-user-headline span'
);
var voteupBtn = card.querySelector('button[aria-label*="赞同"], .VoteButton');
var commentBtn = null;
var allBtns = card.querySelectorAll('button');
for (var j = 0; j < allBtns.length; j++) {
if (/条评论/.test(allBtns[j].textContent)) {
commentBtn = allBtns[j];
break;
}
}
answerMap[answerId] = {
id: answerId,
author: {
name: (authorEl && authorEl.textContent && authorEl.textContent.trim()) || '未知',
urlToken: '',
headline: (headlineEl && headlineEl.textContent && headlineEl.textContent.trim()) || '',
avatarUrl: '',
},
content: domContent,
contentText: domText,
voteupCount: parseInt((voteupBtn && voteupBtn.textContent || '').replace(/[^\d]/g, '') || '0'),
commentCount: parseInt((commentBtn && commentBtn.textContent || '').replace(/[^\d]/g, '') || '0'),
createdTime: 0,
updatedTime: 0,
questionTitle:
(entities.questions && Object.keys(entities.questions || {})[0] && entities.questions[Object.keys(entities.questions)[0]].title) ||
(function () { var t = document.querySelector('.QuestionHeader-title'); return t ? t.textContent.trim() : ''; })() ||
'',
url: 'https://www.zhihu.com/question/' + (parseUrl() || {}).questionId + '/answer/' + answerId,
_source: 'dom-only',
};
}
});
var result = [];
Object.keys(answerMap).forEach(function (k) { result.push(answerMap[k]); });
return result;
}
// ======================= 评论 API =======================
function normalizeComment(c) {
if (!c) return null;
return {
id: c.id || '',
content: stripHtml(c.content || ''),
contentHtml: c.content || '',
author: {
name: (c.author && c.author.name) || '匿名用户',
urlToken: (c.author && c.author.url_token) || '',
headline: (c.author && c.author.headline) || '',
avatarUrl: (c.author && c.author.avatar_url) || '',
isAuthor: !!(c.is_author || (c.author_tag || []).some(function (t) { return t.type === 'content_author'; })),
},
createdTime: c.created_time || 0,
createdTimeStr: c.created_time
? new Date(c.created_time * 1000).toLocaleString('zh-CN')
: '',
likeCount: c.like_count || 0,
dislikeCount: c.dislike_count || 0,
isCollapsed: c.collapsed || false,
isAuthorTop: c.is_author_top || false,
childCommentCount: c.child_comment_count || 0,
childComments: (c.child_comments || []).map(function (cc) { return normalizeComment(cc); }).filter(Boolean),
childCommentNextOffset: c.child_comment_next_offset || null,
replyToCommentId: c.reply_comment_id || '0',
replyToRootCommentId: c.reply_root_comment_id || '',
location: ((c.comment_tag || []).find(function (t) { return t.type === 'ip_info'; }) || {}).text || '',
tags: (c.author_tag || []).map(function (t) { return t.text; }),
};
}
function fetchRootComments(answerId) {
return new Promise(function (resolve) {
var all = [];
var nextUrl = 'https://www.zhihu.com/api/v4/comment_v5/answers/' + answerId +
'/root_comment?order_by=score&limit=' + CONFIG.commentPageSize;
function fetchNext() {
if (!nextUrl || all.length >= (CONFIG.maxRootComments || Infinity)) {
if (CONFIG.maxRootComments > 0) all.length = Math.min(all.length, CONFIG.maxRootComments);
resolve(all);
return;
}
fetch(nextUrl)
.then(function (resp) {
if (!resp.ok) { resolve(all); return; }
return resp.json();
})
.then(function (data) {
if (!data || !data.data) { resolve(all); return; }
var comments = data.data.map(normalizeComment);
all = all.concat(comments);
console.log('[知乎导出] 根评论: ' + all.length + ' / ' + (data.counts ? data.counts.total_counts : '?'));
if (data.paging && !data.paging.is_end && data.paging.next) {
nextUrl = data.paging.next;
setTimeout(fetchNext, CONFIG.requestDelay);
} else {
if (CONFIG.maxRootComments > 0) all.length = Math.min(all.length, CONFIG.maxRootComments);
resolve(all);
}
})
.catch(function (e) {
console.error('[知乎导出] 根评论网络错误:', e);
resolve(all);
});
}
fetchNext();
});
}
function fetchChildComments(answerId, parentComment) {
return new Promise(function (resolve) {
if (parentComment.childCommentCount === 0) { resolve([]); return; }
var children = (parentComment.childComments || []).slice();
if (children.length >= parentComment.childCommentCount) {
resolve(children.map(function (c) { return normalizeComment(c); }).filter(Boolean));
return;
}
var nextOffset = parentComment.childCommentNextOffset;
function fetchNextOffset() {
if (!nextOffset || children.length >= parentComment.childCommentCount) {
if (CONFIG.maxChildComments > 0) children.length = Math.min(children.length, CONFIG.maxChildComments);
resolve(children);
return;
}
var url = 'https://www.zhihu.com/api/v4/comment_v5/answers/' + answerId +
'/root_comment?order_by=score&limit=1&child_comment_offset=' + encodeURIComponent(nextOffset);
fetch(url)
.then(function (resp) {
if (!resp.ok) { resolve(children); return; }
return resp.json();
})
.then(function (data) {
if (!data || !data.data || !data.data[0] || !data.data[0].child_comments) {
resolve(children);
return;
}
var more = data.data[0].child_comments.map(normalizeComment).filter(Boolean);
children = children.concat(more);
nextOffset = data.data[0].child_comment_next_offset;
if (CONFIG.maxChildComments > 0 && children.length >= CONFIG.maxChildComments) {
children.length = CONFIG.maxChildComments;
resolve(children);
return;
}
setTimeout(fetchNextOffset, CONFIG.requestDelay);
})
.catch(function (e) {
console.error('[知乎导出] 子评论网络错误:', e);
resolve(children);
});
}
fetchNextOffset();
});
}
// ======================= 导出主流程 =======================
function exportData(format) {
format = format || 'json';
var parsed = parseUrl();
if (!parsed) {
toast('无法解析 URL,请确保在知乎问题/回答页面', 'error');
return;
}
var questionId = parsed.questionId;
var answerId = parsed.answerId;
// 设置按钮加载状态
var btns = document.querySelectorAll('.zh-ce-panel .zh-ce-body button');
btns.forEach(function (b) {
b.disabled = true;
b.classList.add('zh-ce-loading');
});
// 异步主流程
var run = async function () {
try {
// 0. 展开折叠内容并等待 DOM 更新
toast('展开折叠内容...', 'info');
var expandedCount = await autoExpandAndWait();
if (expandedCount > 0) {
console.log('[知乎导出] 折叠内容已展开');
}
// 1. 提取答案
toast('提取答案信息...', 'info');
var answers = extractAllAnswers();
// 去重(按 id)
var seen = {};
answers = answers.filter(function (a) {
if (seen[a.id]) return false;
seen[a.id] = true;
return true;
});
// 筛选目标答案
var targetAnswers = answers;
if (answerId) {
targetAnswers = answers.filter(function (a) { return a.id === answerId; });
if (targetAnswers.length === 0 && answers.length > 0) {
targetAnswers = [answers[0]];
}
}
console.log('[知乎导出] 共 ' + answers.length + ' 个答案,目标 ' + targetAnswers.length + ' 个');
if (targetAnswers.length === 0) {
toast('未找到任何回答,请刷新页面后重试', 'error');
return;
}
// 2. 逐个答案获取评论
var results = [];
for (var i = 0; i < targetAnswers.length; i++) {
var answer = targetAnswers[i];
var aid = answer.id;
if (!aid) {
results.push({
id: answer.id, author: answer.author, content: answer.content,
contentText: answer.contentText, voteupCount: answer.voteupCount,
commentCount: answer.commentCount, createdTime: answer.createdTime,
updatedTime: answer.updatedTime, questionTitle: answer.questionTitle,
url: answer.url, comments: [], totalRootComments: 0, totalChildComments: 0,
});
continue;
}
toast('获取评论 ' + (i + 1) + '/' + targetAnswers.length + ' (' + answer.author.name + ')...', 'info');
var rootComments = await fetchRootComments(aid);
var totalChildren = 0;
for (var j = 0; j < rootComments.length; j++) {
var comment = rootComments[j];
if (comment.childCommentCount === 0) continue;
var children = await fetchChildComments(aid, comment);
comment.childComments = children;
totalChildren += children.length;
}
console.log('[知乎导出] ' + answer.author.name + ': ' + rootComments.length + ' 评论 + ' + totalChildren + ' 子评论');
results.push({
id: answer.id,
author: answer.author,
content: answer.content,
contentText: answer.contentText,
voteupCount: answer.voteupCount,
commentCount: answer.commentCount,
createdTime: answer.createdTime,
updatedTime: answer.updatedTime,
questionTitle: answer.questionTitle,
url: answer.url,
comments: rootComments,
totalRootComments: rootComments.length,
totalChildComments: totalChildren,
});
}
// 3. 构建导出对象
var exportObj = {
exportedAt: new Date().toISOString(),
url: location.href,
questionId: questionId,
questionTitle: results[0].questionTitle || (function () {
var t = document.querySelector('.QuestionHeader-title');
return t ? t.textContent.trim() : '';
})() || '',
totalAnswers: results.length,
answers: results,
};
// 4. 生成文件
var filename, content, mimeType;
var titleSlug = (exportObj.questionTitle || 'question-' + questionId)
.replace(/[\\/:*?"<>|]/g, '')
.substring(0, 50);
if (format === 'markdown') {
filename = titleSlug + (answerId ? '-' + answerId : '') + '.md';
content = generateMarkdown(exportObj);
mimeType = 'text/markdown;charset=utf-8';
} else {
filename = titleSlug + (answerId ? '-' + answerId : '') + '.json';
content = JSON.stringify(exportObj, null, 2);
mimeType = 'application/json;charset=utf-8';
}
// 5. 下载 - 直接触发,不使用 GM_download(blob URL 在沙箱中不可靠)
downloadFile(filename, content, mimeType);
// 6. 显示统计
var stats = results.map(function (r) {
return '「' + (r.author.name || '未知') + '」: ' + r.totalRootComments + '评+' + r.totalChildComments + '子评';
});
toast('✅ 导出完成! ' + stats.join(' | '), 'success');
console.log('[知乎导出] 完成:', exportObj.questionTitle, results.length + ' 个答案');
} catch (e) {
console.error('[知乎导出] 错误:', e);
toast('导出失败: ' + e.message, 'error');
} finally {
btns.forEach(function (b) {
b.disabled = false;
b.classList.remove('zh-ce-loading');
});
}
};
run();
}
// ======================= Markdown 生成 =======================
function generateMarkdown(data) {
var lines = [];
lines.push('# ' + data.questionTitle);
lines.push('');
lines.push('> 📅 导出时间: ' + data.exportedAt);
lines.push('> 🔗 原链接: ' + data.url);
lines.push('');
data.answers.forEach(function (answer) {
lines.push('---');
lines.push('');
lines.push('## ' + answer.author.name);
if (answer.author.headline) {
lines.push('*' + answer.author.headline + '*');
}
lines.push('');
lines.push('👍 ' + answer.voteupCount + ' 赞同 | 💬 ' + answer.totalRootComments + ' 评论 | ' + answer.totalChildComments + ' 子评论');
lines.push('');
// === 回答正文 ===
lines.push('### 📝 回答内容');
lines.push('');
var contentText = answer.contentText || stripHtml(answer.content);
if (contentText) {
contentText.split('\n').forEach(function (line) {
lines.push(line.trim() || '');
});
} else {
lines.push('*(未获取到回答内容)*');
}
lines.push('');
// === 评论 ===
if (answer.comments.length > 0) {
lines.push('### 💬 评论 (' + answer.totalRootComments + ' 条)');
lines.push('');
answer.comments.forEach(function (comment) {
var time = comment.createdTimeStr || '';
var badge = comment.author.isAuthor ? ' 🅰️' : '';
var loc = comment.location ? ' 📍' + comment.location : '';
var topBadge = comment.isAuthorTop ? ' 📌置顶' : '';
lines.push('**' + comment.author.name + '**' + badge + topBadge);
lines.push('> ' + comment.content.replace(/\n/g, '\n> '));
lines.push('> 👍' + comment.likeCount + loc + ' · ' + time);
lines.push('');
// 子评论
if (comment.childComments && comment.childComments.length > 0) {
comment.childComments.forEach(function (child) {
var childTime = child.createdTimeStr || '';
var childBadge = child.author.isAuthor ? ' 🅰️' : '';
var childLoc = child.location ? ' 📍' + child.location : '';
lines.push(' **' + child.author.name + '**' + childBadge);
lines.push(' > ' + (child.content || '').replace(/\n/g, '\n > '));
lines.push(' > 👍' + child.likeCount + childLoc + ' · ' + childTime);
lines.push('');
});
}
});
} else {
lines.push('*暂无评论*');
lines.push('');
}
});
return lines.join('\n');
}
// ======================= 下载(可靠方案) =======================
function downloadFile(filename, content, mimeType) {
// 直接用 a.click() 触发下载。CSP 不拦截 <a> 点击触发的浏览器下载行为。
// 不用 script 注入(知乎 CSP 禁止内联脚本),不用 GM_download(blob URL 可能被沙箱拦截)。
var blob = new Blob([content], { type: mimeType });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
// 延迟清理,确保浏览器有足够时间开始下载
setTimeout(function () {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 60000);
console.log('[知乎导出] 下载已触发: ' + filename + ' (' + (content.length / 1024).toFixed(1) + ' KB)');
}
// ======================= UI =======================
function createUI() {
// 避免重复创建
if (document.querySelector('.zh-ce-panel')) return;
var panel = document.createElement('div');
panel.className = 'zh-ce-panel';
// ---- 标题栏 ----
var header = document.createElement('div');
header.className = 'zh-ce-header';
header.innerHTML = '<span>📋 知乎回答·评论导出</span><span class="zh-ce-arrow">▼</span>';
header.title = '点击展开/收起';
header.addEventListener('click', function () {
panel.classList.toggle('collapsed');
});
// ---- 按钮区 ----
var body = document.createElement('div');
body.className = 'zh-ce-body';
var jsonBtn = document.createElement('button');
jsonBtn.className = 'zh-ce-btn-json';
jsonBtn.innerHTML = '<span class="zh-ce-spinner"></span><span class="zh-ce-label">📥 导出 JSON</span>';
jsonBtn.title = '导出 JSON(完整结构化数据)';
jsonBtn.addEventListener('click', function (e) {
e.stopPropagation();
exportData('json');
});
var mdBtn = document.createElement('button');
mdBtn.className = 'zh-ce-btn-md';
mdBtn.innerHTML = '<span class="zh-ce-spinner"></span><span class="zh-ce-label">📝 导出 Markdown</span>';
mdBtn.title = '导出 Markdown(方便阅读)';
mdBtn.addEventListener('click', function (e) {
e.stopPropagation();
exportData('markdown');
});
body.appendChild(jsonBtn);
body.appendChild(mdBtn);
panel.appendChild(header);
panel.appendChild(body);
document.body.appendChild(panel);
console.log('[知乎导出] 面板已加载 ✓');
}
// ======================= 入口 =======================
function init() {
if (!/\/question\/\d+/.test(location.pathname)) return;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
setTimeout(createUI, 1500);
});
} else {
setTimeout(createUI, 1500);
}
}
init();
// SPA 导航监听(知乎是 React SPA)
var lastPath = location.pathname;
var observer = new MutationObserver(function () {
if (location.pathname !== lastPath) {
lastPath = location.pathname;
if (/\/question\/\d+/.test(location.pathname)) {
setTimeout(createUI, 1500);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();