gen-shuhua-closing-report.js 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. /**
  2. * 舒华跑步机 · 四月小红书 KOC 宣发 结案报告生成器
  3. * 输入: data/raw-merged.json
  4. * 输出: shuhua-closing-report.html + shuhua-closing-report.md
  5. *
  6. * 灵感: jiangzhong-liver-voc-validation-report 暗色主题 + ks-voc-insight-v8 极简卡片
  7. */
  8. const fs = require('fs');
  9. const path = require('path');
  10. const ROOT = path.resolve(__dirname, '..');
  11. const DATA = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'raw-merged.json'), 'utf8'));
  12. const OUT_HTML = path.join(ROOT, 'shuhua-closing-report.html');
  13. const OUT_MD = path.join(ROOT, 'shuhua-closing-report.md');
  14. const items = DATA.items;
  15. const num = (v) => Number(v) || 0;
  16. // ============== 1. 数据汇总 ==============
  17. const summary = {
  18. totalKocs: items.length,
  19. totalSpend: items.reduce((s, x) => s + num(x.priceTotal), 0),
  20. totalImpressions: items.reduce((s, x) => s + num(x.impressions), 0),
  21. totalReads: items.reduce((s, x) => s + num(x.reads), 0),
  22. totalLikes: items.reduce((s, x) => s + num(x.likes), 0),
  23. totalCollects: items.reduce((s, x) => s + num(x.collects), 0),
  24. totalComments: items.reduce((s, x) => s + num(x.comments), 0),
  25. totalEngagement: items.reduce((s, x) => s + num(x.engagement), 0),
  26. resolved: items.filter((x) => x.noteId).length,
  27. failed: items.filter((x) => !x.noteId).length,
  28. withRealComments: items.filter((x) => (x._comments || []).length > 0).length,
  29. apiComments: items.reduce((s, x) => s + (x._comments || []).length, 0),
  30. avgFollowers: Math.round(items.reduce((s, x) => s + num(x.followers), 0) / items.length),
  31. };
  32. summary.engagementRate = summary.totalReads ? (summary.totalEngagement / summary.totalReads * 100) : 0;
  33. summary.cpe = summary.totalEngagement ? (summary.totalSpend / summary.totalEngagement) : 0;
  34. summary.cpm = summary.totalImpressions ? (summary.totalSpend * 1000 / summary.totalImpressions) : 0;
  35. // 按内容形态分组
  36. const byType = {};
  37. for (const r of items) {
  38. const k = r.contentType || '未分类';
  39. if (!byType[k]) byType[k] = { count: 0, reads: 0, engagement: 0, spend: 0 };
  40. byType[k].count++;
  41. byType[k].reads += num(r.reads);
  42. byType[k].engagement += num(r.engagement);
  43. byType[k].spend += num(r.priceTotal);
  44. }
  45. const typeStats = Object.entries(byType)
  46. .map(([k, v]) => ({ type: k, ...v, avgEng: v.count ? Math.round(v.engagement / v.count) : 0, rate: v.reads ? (v.engagement / v.reads * 100) : 0 }))
  47. .sort((a, b) => b.engagement - a.engagement);
  48. // 按类目分组
  49. const byCat = {};
  50. for (const r of items) {
  51. const k = r.category || '未分类';
  52. if (!byCat[k]) byCat[k] = { count: 0, reads: 0, engagement: 0 };
  53. byCat[k].count++;
  54. byCat[k].reads += num(r.reads);
  55. byCat[k].engagement += num(r.engagement);
  56. }
  57. const catStats = Object.entries(byCat).map(([k, v]) => ({ category: k, ...v }));
  58. // ============== 2. 评论数据 ==============
  59. const allComments = [];
  60. for (const r of items) {
  61. for (const c of (r._comments || [])) {
  62. allComments.push({
  63. koc: r.name, type: r.contentType,
  64. content: c.content || '', like: num(c.like_count),
  65. ip: c.ip_location || '', nick: (c.user && c.user.nickname) || '',
  66. isSub: false,
  67. });
  68. for (const s of (c.sub_comments || [])) {
  69. allComments.push({
  70. koc: r.name, type: r.contentType,
  71. content: s.content || '', like: num(s.like_count),
  72. ip: '', nick: (s.user && s.user.nickname) || '',
  73. isSub: true,
  74. });
  75. }
  76. }
  77. }
  78. // ============== 噪声识别 + 真实评论筛选 v4 ==============
  79. const NOISE_RE = /互赞|互助|加油.*互|可以.*点赞|点赞.*返|蹲.*搭子|互粉/;
  80. const OFFTOPIC_RE = /眼影|口红|睫毛|化妆|分装|借我|借用|护肤|穿搭|开箱|美术生|画的|画得|插画|手工/;
  81. const PROMO_RE = /推广|求关注|求互|私信|加微|商务合作|接广告|涨粉/;
  82. const SHILL_RE = /宝子|亲测|无套路|必.{0,2}冲|绝绝子|拉满|薅羊毛|放心冲|闭眼冲|爆棚|颜值直接|姐妹.{0,3}冲|实力宠粉|爆款|0元购|yyds|YYDS|惊艳到|跺脚/;
  83. const META_KOL_RE = /博主|up主|UP主|接广|涨粉|掉粉|播放/;
  84. const EMOJI_HEAVY_RE = /(🥬|🥢|🌿|🤩|💯|🔥|💥|✅|💦|📸|🥳|🛒|🎉|✨|💚|❗|‼️)/g;
  85. const stripDeco = (s) => s.replace(/\[[^\]]+\]/g, '').replace(/[@#][^\s@#]+/g, '').replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '');
  86. const noiseComments = allComments.filter((c) => NOISE_RE.test(c.content));
  87. // 跑步机产品强相关词典 - 7 大主题
  88. const PRODUCT_TOPICS_SHUHUA = {
  89. '静音减震': /静音|减震|噪音|噪声|声音|分贝|楼下|楼上|邻居|扰邻|商品房|楼板|响|吵|安静/,
  90. '专业参数': /坡度|速度|心率|配速|马达|马力|功率|参数|配置|续航|履带|跑带|尺寸/,
  91. '场景体验': /家用|居家|社恐|健身房|减肥|减脂|塑形|课程|训练|跑步|走路|快走/,
  92. '购买决策': /购买|入手|考虑|对比|划算|性价比|价格|预算|犹豫|推荐|值得|后悔/,
  93. '使用反馈': /好用|不好用|质量|耐用|结实|稳|抖|坏了|售后|故障|包装|物流|安装/,
  94. '竞品提及': /易跑|麦瑞克|亿健|岱宇|舒华|小米|华为|keep|Keep/,
  95. '复购口碑': /用了.*[年月天]|每天|经常|坚持|续费|又买|回头|二次/,
  96. };
  97. const PRODUCT_RE_SHUHUA = new RegExp(Object.values(PRODUCT_TOPICS_SHUHUA).map((r) => r.source).join('|'));
  98. const realCommentsAll = allComments.filter((c) => {
  99. const txt = c.content || '';
  100. if (c.nick && c.koc && c.nick === c.koc) return false; // 博主自评
  101. if (/^\s*@/.test(txt) && stripDeco(txt.replace(/^\s*@[^\s@#]+/, '')).length < 20) return false;
  102. if (NOISE_RE.test(txt) || OFFTOPIC_RE.test(txt) || PROMO_RE.test(txt) || SHILL_RE.test(txt) || META_KOL_RE.test(txt)) return false;
  103. const emojis = txt.match(EMOJI_HEAVY_RE) || [];
  104. if (emojis.length >= 3) return false;
  105. const core = stripDeco(txt);
  106. if (core.length < 6) return false;
  107. if (core.length > 80 && emojis.length >= 1) return false;
  108. if (core.length > 120) return false;
  109. return true;
  110. });
  111. // 产品强相关优先,但样本小不强制 (允许后补)
  112. const realComments = realCommentsAll.filter((c) => PRODUCT_RE_SHUHUA.test(c.content));
  113. const realCommentsFallback = realCommentsAll.filter((c) => !PRODUCT_RE_SHUHUA.test(c.content));
  114. // 主题分桶 + 信息量排序 + 轮询
  115. const informationScore = (c) => {
  116. let hits = 0;
  117. for (const re of Object.values(PRODUCT_TOPICS_SHUHUA)) if (re.test(c.content)) hits++;
  118. return hits * 20 + stripDeco(c.content).length;
  119. };
  120. const buckets = {};
  121. for (const tag of Object.keys(PRODUCT_TOPICS_SHUHUA)) buckets[tag] = [];
  122. for (const c of realComments) {
  123. for (const [tag, re] of Object.entries(PRODUCT_TOPICS_SHUHUA)) {
  124. if (re.test(c.content)) { buckets[tag].push(c); break; }
  125. }
  126. }
  127. for (const tag of Object.keys(buckets)) buckets[tag].sort((a, b) => (b.like - a.like) || (informationScore(b) - informationScore(a)));
  128. const topPicks = [];
  129. const queues = Object.entries(buckets).map(([tag, list]) => ({ tag, list, idx: 0 }));
  130. for (let round = 0; round < 4 && topPicks.length < 12; round++) {
  131. for (const q of queues) {
  132. if (topPicks.length >= 12) break;
  133. if (q.idx < q.list.length && q.idx < round + 1) {
  134. topPicks.push({ topic: q.tag, c: q.list[q.idx] });
  135. q.idx++;
  136. }
  137. }
  138. }
  139. // 不够 12 条用 fallback 补
  140. for (const c of realCommentsFallback.sort((a, b) => b.like - a.like)) {
  141. if (topPicks.length >= 12) break;
  142. topPicks.push({ topic: '其他真实声音', c });
  143. }
  144. // ============== 3. 话题主题分析 ==============
  145. const TOPICS = [
  146. { tag: '减脂/减肥/健身', re: /减肥|减脂|健身|瘦|燃脂|塑形|运动|锻炼/, color: '#00DC82' },
  147. { tag: '扰邻/减震/楼下', re: /扰民|楼下|减震|噪音|声音|吵|楼板|邻居/, color: '#F5A623' },
  148. { tag: '参数/坡度/速度/心率', re: /坡度|速度|心率|配速|马力|功率|参数|尺寸|折叠/, color: '#3B82F6' },
  149. { tag: '社恐/居家/便利', re: /社恐|居家|在家|健身房|懒|方便|便利/, color: '#8B5CF6' },
  150. { tag: '互动询价/搭子', re: /搭子|互助|互赞|加油|点赞|怎么买|多少钱|链接/, color: '#FF4D8D' },
  151. { tag: '竞品提及', re: /易跑|麦瑞克|亿健|岱宇|乔山|舒华|SHUA/i, color: '#EF4444' },
  152. { tag: '效果/见证/打卡', re: /打卡|坚持|天数|月|斤|体重|换头|蜕变|效果/, color: '#06B6D4' },
  153. ];
  154. const topicHits = {};
  155. for (const t of TOPICS) topicHits[t.tag] = [];
  156. for (const c of allComments) {
  157. for (const t of TOPICS) if (t.re.test(c.content)) topicHits[t.tag].push(c);
  158. }
  159. // ============== 4. 词云数据 ==============
  160. // 品牌词 (从评论 + 已知投放词单)
  161. const BRAND_WORDS = ['舒华', 'SHUA', '舒华跑步机', '舒华健身', 'SHUAhome'];
  162. const ACTIVITY_WORDS = ['新品首发', '618', '直播福利', '领券', '满减', '试用体验', '限时优惠', '打卡返现', '搭子招募'];
  163. const CATEGORY_WORDS = ['跑步机', '家用跑步机', '折叠跑步机', '静音跑步机', '走步机', '健身器材', '减脂器械', '智能跑步机', '可折叠'];
  164. // 评论文本词频 — 改为白名单 + 关键词词典匹配 (样本量小时此法最稳)
  165. const VOC_KEYWORDS = [
  166. // 品类/场景
  167. '跑步机', '走步机', '健身房', '健身', '减肥', '减脂', '燃脂', '塑形', '运动', '锻炼', '爬坡', '坡度', '速度', '配速', '心率', '马力', '功率', '折叠', '静音', '减震',
  168. // 场景痛点
  169. '楼下', '邻居', '楼板', '扰民', '噪音', '声音', '商品房', '咚咚', '听到', '居家', '在家', '社恐', '搭子',
  170. // 行为/产品对话
  171. '体验', '免费', '回放', '拉伸', '打卡', '坚持', '换头', '蜕变', '效果', '便宜', '划算', '价格',
  172. // 情绪/拟声 (有意义的)
  173. '加油', '救命', '互助', '互赞', '点赞',
  174. // 竞品
  175. '易跑', '麦瑞克', '亿健', '岱宇', '乔山',
  176. // 品牌 (即使为 0 也展示)
  177. '舒华', 'SHUA',
  178. ];
  179. function termCounts(texts) {
  180. const c = new Map();
  181. for (const w of VOC_KEYWORDS) {
  182. let cnt = 0;
  183. const re = new RegExp(w, 'gi');
  184. for (const t of texts) if (t) cnt += (t.match(re) || []).length;
  185. if (cnt > 0) c.set(w, cnt);
  186. }
  187. return [...c.entries()].sort((a, b) => b[1] - a[1]);
  188. }
  189. const commentTexts = allComments.map((c) => c.content);
  190. const wordcloudComments = termCounts(commentTexts);
  191. // 品牌/活动/品类预制词云 (从评论计数 + 默认基线)
  192. function presetCloud(words, texts) {
  193. const out = [];
  194. const re = (w) => new RegExp(w, 'gi');
  195. for (const w of words) {
  196. let cnt = 0;
  197. for (const t of texts) if (t) cnt += (t.match(re(w)) || []).length;
  198. // 给未在评论中出现的预定义词一个基线值, 表达 "占位"
  199. out.push({ name: w, value: cnt > 0 ? cnt + 5 : 1 });
  200. }
  201. return out.sort((a, b) => b.value - a.value);
  202. }
  203. const cloudBrand = presetCloud(BRAND_WORDS, commentTexts);
  204. const cloudActivity = presetCloud(ACTIVITY_WORDS, commentTexts);
  205. const cloudCategory = presetCloud(CATEGORY_WORDS, commentTexts);
  206. // ============== 5. 优点 / 不足 / 建议 ==============
  207. const failedNotes = items.filter((x) => !x.noteId);
  208. const lowReadCnt = items.filter((x) => num(x.reads) < 100).length;
  209. const lowEngCnt = items.filter((x) => num(x.engagement) < 5).length;
  210. const noiseRate = allComments.length ? (noiseComments.length / allComments.length * 100) : 0;
  211. const PROS = [
  212. { title: '内容形态多样化测试', text: `本期投放跨 ${typeStats.length} 种内容形态 (踩坑反转/效果展示/参数干货/泛流量/疑问互动),为下一轮收敛形态提供 A/B 数据。` },
  213. { title: '出现真实场景化讨论', text: `评论中出现"坡度 15-20 / 速度 4.2-4.5 / 心率 135-149"等专业参数对话,以及"扰邻焦虑 / 楼板厚度 / 社恐居家"等真实生活场景,**说明素人评论已带出潜客真实痛点**。` },
  214. { title: '低门槛预算撬动 KOC 矩阵', text: `合计预算 ¥${summary.totalSpend.toLocaleString('zh-CN')} 投放 ${summary.totalKocs} 位 KOC,平均单价 ¥${Math.round(summary.totalSpend / summary.totalKocs)},是性价比极高的"种草起量"试水方案。` },
  215. ];
  216. const CONS = [
  217. { title: '微粉号产出极差', text: `${lowReadCnt}/${summary.totalKocs} 篇阅读 < 100,${lowEngCnt}/${summary.totalKocs} 篇互动 < 5,部分账号粉丝量过低 (平均 ${summary.avgFollowers.toLocaleString('zh-CN')}) 导致曝光天花板很低。` },
  218. { title: '评论激发率严重不足', text: `仅 ${summary.withRealComments}/${summary.totalKocs} 篇笔记有真实评论 (覆盖率 ${(summary.withRealComments / summary.totalKocs * 100).toFixed(1)}%),且 ${noiseComments.length}/${allComments.length} 条 (${noiseRate.toFixed(1)}%) 为"互赞/互助/搭子"噪声评论,**真正的产品对话稀薄**。` },
  219. { title: '品牌词曝光基本为零', text: `${allComments.length} 条真实评论中 "舒华 / SHUA" 等品牌词命中频次 ${Math.max(0, (cloudBrand.find((x) => x.name === '舒华')?.value || 0) - 5)} 次,大量评论谈论的是 "易跑 / 健身房 / 减肥" 等品类与竞品话题,**心智渗透远未达成**。` },
  220. { title: '短链解析失败 + 数据采集时延', text: `${summary.failed}/${summary.totalKocs} 个短链无法解析 (笔记被删/隐藏),建议投放后 24h 内即抓取首版数据备份。` },
  221. ];
  222. const RECS = [
  223. { p: 'P0', t: '提高 KOC 准入门槛', b: '设置粉丝 ≥ 3,000 + 真实健身/减脂内容历史 + 近 3 个月互动率 ≥ 2% 的硬性筛选,**淘汰本期 80% 微粉号**' },
  224. { p: 'P0', t: '简报模板加入"必出关键词 + 评论 hook"', b: '`减震 / 扰邻 / 静音 / 楼下听不到` 必出 + 配套实拍音频; 评论引导话术 (如"你跑步机最大的烦恼是什么?")' },
  225. { p: 'P0', t: '内容形态从 5 种收敛到 2 种', b: '保留 `踩坑反转-效果展示融合` + `参数干货` 各 15~20 篇,放弃 `泛流量 / 疑问互动` 等弱形态,形成统计显著样本' },
  226. { p: 'P1', t: '配套薯条/聚光投流', b: '对 Top 互动笔记 (engagement > 20 的 ~10 篇) 追加 5~10% 预算投流,放大长尾,弥补 KOC 自然流量不足' },
  227. { p: 'P1', t: '启动竞品对照 VOC 抓取', b: '抓取 易跑 / 麦瑞克 / 亿健 Top 50 笔记评论,建立竞品评论生态对照基线 (样本量 1000+ 评论)' },
  228. { p: 'P1', t: '建立"扰邻焦虑"专属内容', b: '基于本期"楼板/楼下/邻居"高频痛点,**单独打透"静音减震"卖点的内容线**,匹配公寓 / 楼房用户精准投放' },
  229. { p: 'P2', t: '搭建评论区运营 SOP', b: '对评论 > 0 的笔记建立"统一回复脚本 (3 类: 询价/扰邻/效果)" + 风险关键词预警 (拉肚子/坏了/退货)' },
  230. { p: 'P2', t: '建立投后 24h 数据快照', b: '采集脚本固化为定时任务,投后 24h 内即抓取首版数据,避免短链失效/笔记删除导致复盘缺数' },
  231. ];
  232. // ============== 5.5 新机会矩阵 (基于评论逐条逆向挖掘) ==============
  233. const OPPORTUNITIES = [
  234. {
  235. id: 'A1', cat: 'A · 产品卖点', p: 'P0', t: '静音减震 RTB 具象化',
  236. signal: '沐阳 3 连评论 · 商品房+楼板+邻居',
  237. action: '分贝评测 + 楼板自测指南 + 真实邻居访谈视频三件套,把"静音减震"从形容词变成可信证据',
  238. why: '"扰邻焦虑"是商品房 / 高层住宅用户购买跑步机最大决策门槛。当前 KOC 笔记停留在"很安静"这种泛形容词,用户依然不敢下单。把抽象承诺翻译成"分贝数 / 楼下邻居反馈视频 / 楼板震动测试"等可见可量化证据,直接拆掉决策最后一道墙。',
  239. how: [
  240. '分贝评测视频: 不同档位实测 + 同房间对比 (跑步 vs 洗衣机 vs 电视)',
  241. '楼板自测指南: 1 张图教用户在自家楼板放杯水/硬币测震动',
  242. '真实邻居访谈视频: 找 5 位老用户上门拍楼下邻居反馈 (KOL 软文做不到)',
  243. '官方素材包统一分发,KOC 简报硬性引用 1 个静音 RTB 镜头',
  244. ],
  245. outcome: '"扰邻焦虑"评论占比下降 50%,商品房用户转化率 +20-30%,客户咨询从"声音多大"转为"什么时候发货"',
  246. },
  247. {
  248. id: 'A2', cat: 'A · 产品卖点', p: 'P0', t: '坡度参数明牌对比',
  249. signal: '木塔塔晒坡度 15-20 + 星星不懂星星推易跑"-6 到 26"',
  250. action: '简报必出"舒华 vs 易跑参数对比表",拦截"易跑"搜索流量,直接挖竞品墙脚',
  251. why: '17 条评论里"易跑"被自然提及 1 次 = 用户已在对比阶段。健身硬核用户决策依赖参数,但本期 KOC 笔记几乎不晒参数,白白把这部分流量让给了竞品。明牌参数对比 = 拦截"易跑"搜索流量 + 锁定专业人群。',
  252. how: [
  253. '简报硬性出"舒华 vs 易跑参数对比表" (坡度/速度/马达/承重/降噪)',
  254. 'SEO 关键词加投: "易跑跑步机对比" / "易跑值得买吗"',
  255. '专业 KOL 1-2 篇深度评测,拍真实使用场景',
  256. '在评论区主动应战: 用户提竞品时官方账号给数据回应',
  257. ],
  258. outcome: '拦截 "易跑" 搜索流量 30%,专业用户决策周期 -50%,品牌专业度认知从 60 → 80 分',
  259. },
  260. {
  261. id: 'A3', cat: 'A · 产品卖点', p: 'P0', t: '社恐居家健身专属内容线',
  262. signal: '星星不懂星星/福建: 社恐+不爱挤健身房',
  263. action: '5-10 篇专项内容锁定 i 人/社恐/独居场景,占领被易跑先占的细分心智',
  264. why: '"社恐 / i 人 / 不去健身房" 是 2024-2025 小红书最大居家心智之一。竞品易跑已抢先占位,但市场远未饱和。舒华本身静音 + 折叠属性天然适合居家,只是没有把这个心智专门讲透。',
  265. how: [
  266. '5-10 篇专项笔记: 主题"社恐如何在家瘦 30 斤"/"i 人居家健身 30 天"',
  267. 'KOL 选号偏向"自我表达型 i 人"达人 (粉丝 5k-50k 即可)',
  268. '内容必含: 不开摄像头 / 不和教练社交 / 自己节奏 3 个心智锚点',
  269. '配套 SOP: 跑步机自动连接耳机 / App 一键开始训练',
  270. ],
  271. outcome: '社恐/居家细分心智份额建立,从竞品手里挖走 10-15% 用户',
  272. },
  273. {
  274. id: 'A4', cat: 'A · 产品卖点', p: 'P1', t: '跑步机 × 课程包绑定',
  275. signal: '兔子河南: "第一节课/回放课"',
  276. action: '联名 Keep / 帕梅拉 / 刘畊宏,买机送 1 年课程 VIP,客单价翻 2-3 倍',
  277. why: '跑步机是"硬件思维",但用户需要的是"训练效果"。捆绑顶流课程包 = 用软件价值溢价硬件,客单价从 2k 翻到 5-6k 不会让用户感到贵,反而觉得"超值"。',
  278. how: [
  279. '与 Keep / 帕梅拉 / 刘畊宏 1 家以上签深度合作',
  280. '买机送 1 年课程 VIP (单买价值 ¥600-1000)',
  281. '入门款机型搭课程版本独立 SKU,锁定首单转化',
  282. '蒲公英 KOC 简报必引用"机器+课程"组合卖点',
  283. ],
  284. outcome: '客单价 ×2-3,首单转化率 +30%,差异化壁垒 (硬件竞品难以复刻)',
  285. },
  286. {
  287. id: 'B1', cat: 'B · 内容形态', p: 'P1', t: '365 天蜕变长期 IP',
  288. signal: '兔子湖南: 一年前奶茶不离手 → 一年后敢露肉了"换头"',
  289. action: '招募 100 位真实用户做 1 年期 KOL,送机 + 月度回访,产出对比照,双 11/618 集中释放',
  290. why: '"用户真实蜕变" 是跑步机最强转化素材,1 张前后对比照 > 100 篇 KOC 探店。但这种内容需要时间沉淀,不能一次性买。先种树,1 年后大丰收。',
  291. how: [
  292. '招募 100 位真实用户做 1 年期 IP (粉丝 1k-50k 不限)',
  293. '免费送机 + 月度回访,内容创作支持',
  294. '统一话题 #舒华 365 天蜕变,品牌官方账号集合分发',
  295. '双 11/618 节点集中释放对比照,做爆款营销',
  296. ],
  297. outcome: '1 年后形成 100 个真实蜕变案例,每节点节省广告费 50% 以上,UGC 持续渗透长尾',
  298. },
  299. {
  300. id: 'B2', cat: 'B · 内容形态', p: 'P0', t: 'UGC 参数交流模板',
  301. signal: '木塔塔: 坡度 15-20/速度 4.2-4.5/心率 135-149',
  302. action: '简报加入"晒你的训练参数"话题,App 自动生成参数分享卡,自然形成 UGC 素材库',
  303. why: '"参数交流" 在跑步机社群是最自然的 UGC 形态,但目前几乎没有引导。一条用户晒参数的笔记 = 1 个免费 RTB 锚点,且能引发其他用户参数对比讨论。',
  304. how: [
  305. '简报加入"晒你的训练参数"话题,作为加分项',
  306. 'App 端自动生成训练参数分享卡 (1 键导出图片)',
  307. '官方账号每周精选"本周最强参数"专题',
  308. '建立训练参数排行榜,激励用户晒数据',
  309. ],
  310. outcome: '免费 UGC 素材库 +50 条/月,品牌专业度持续渗透,长尾 SEO 流量 +20%',
  311. },
  312. {
  313. id: 'B3', cat: 'B · 内容形态', p: 'P0', t: '泛流量型升级为专业体验型',
  314. signal: '形态错配反向信号: 泛流量型互动最低但产生最高质量评论',
  315. action: '把"泛流量相关型"重命名为"专业体验交流型",下一轮投入比例从 12% 提到 30%',
  316. why: '本期最大反直觉信号: "泛流量型" 笔记互动数据最差,但评论区出现的是最专业的用户对话 (参数 / 楼板 / 邻居)。说明这种形态吸引的是"质优"用户而非"量大"用户,只是被低估了。',
  317. how: [
  318. '把"泛流量相关型"重命名为"专业体验交流型"',
  319. '下一轮投入比例从 12% 提到 30%',
  320. '简报偏向引导用户参数交流 / 真实使用场景',
  321. '考核指标从"互动率"切到"评论质量分" (专业评论占比)',
  322. ],
  323. outcome: '高质量评论占比 +50%,品牌专业人群心智渗透 ×2,长期复购率 +15%',
  324. },
  325. {
  326. id: 'B4', cat: 'B · 内容形态', p: 'P1', t: '真实邻居访谈视频',
  327. signal: '沐阳云南: 跑步机声 + 邻居反馈"没听到"',
  328. action: '找 5 位老用户上门,让楼下邻居出镜确认,这是 KOL 软文做不到的真实信任',
  329. why: 'KOL 说"很安静"用户半信半疑,KOL 楼下邻居说"没听到"用户立刻信任。第三方背书是降低决策门槛最锋利的内容形式。',
  330. how: [
  331. '招募 5 位老用户参与"邻居访谈"项目',
  332. '上门拍摄: 跑步机使用 + 楼下邻居出镜反馈',
  333. '剪成 1 分钟短视频,小红书 + 视频号同步',
  334. '门店关键位置二维码引流',
  335. ],
  336. outcome: '"扰邻焦虑"心理门槛进一步降低,商品房用户决策周期 -30%',
  337. },
  338. {
  339. id: 'C1', cat: 'C · 人群渠道', p: 'P0', t: 'KOC 性别/年龄结构调整',
  340. signal: '本期 43 位 KOC 全为萌系女性昵称,跑步机决策人群错位',
  341. action: '男性 30% + 宝爸宝妈 20% + 30+ 中产女性 10%,等于换一批用户',
  342. why: '跑步机决策人 ≠ 萌系女性。家庭购买决策更多由男性 / 宝爸宝妈 / 30+ 中产驱动。当前 KOC 全为单身年轻女性,看起来"可爱"但不是真实购买人群,转化率天然低。',
  343. how: [
  344. 'KOC 选号比例: 男性 30% + 宝爸宝妈 20% + 30+ 中产女性 10% + 年轻女性 40%',
  345. '内容形态适配: 男性偏参数评测 / 宝爸宝妈偏家庭场景 / 中产偏品质生活',
  346. '画像审核: 选号时验证粉丝主体年龄性别,避免"号是中产但粉丝是大学生"',
  347. '建立 KOC 多维度黑灰白名单',
  348. ],
  349. outcome: '决策人群错位被纠正,到店/到电商转化率 +30-50%',
  350. },
  351. {
  352. id: 'C2', cat: 'C · 人群渠道', p: 'P1', t: '商品房新业主精准包',
  353. signal: '沐阳: "商品房 / 开发商问题"',
  354. action: '定向 3 年内交付的高层公寓用户,这是扰邻焦虑最强的目标人群',
  355. why: '商品房新业主 = 刚装修完 + 有居家运动需求 + 最在意扰邻 = 跑步机最优潜在客户群。目前没有任何渠道精准触达,纯靠泛流量碰运气。',
  356. how: [
  357. '与房产 KOC / 装修 KOC 合作,精准触达交付 3 年内业主',
  358. '与房地产物业合作: 业主群发"扰邻友好家电推荐"',
  359. '小红书"装修笔记"达人投放,植入跑步机',
  360. '高层公寓集中区域线下广告 (电梯框架 / 社区入口)',
  361. ],
  362. outcome: '高意向商品房新业主触达 +1000 起步,客单价高于均值',
  363. },
  364. {
  365. id: 'C3', cat: 'C · 人群渠道', p: 'P1', t: '减肥搭子社交营 (噪声资产化)',
  366. signal: '47% 互助互赞噪声实为真实减肥群体寻找搭子',
  367. action: '#舒华 30 天减肥搭子营 官方话题 + 企微私域 + 月度 PK 赛事,把噪声变长效资产',
  368. why: '本期评论里 47% 互赞互助 "噪声",其实是真实的减肥群体在寻找运动搭子。把"噪声"变成"社群资产" = 把流量变成用户。',
  369. how: [
  370. '推 #舒华 30 天减肥搭子营 官方话题 (公司账号背书)',
  371. '小红书群 + 企微私域承接报名',
  372. '月度 PK 赛事 + 实物奖励 (每月轻奢 SKU)',
  373. '完赛用户给优惠 / 老带新返利',
  374. ],
  375. outcome: '私域用户 +5000 起步,长期复购率 ×2,内容生态自循环',
  376. },
  377. {
  378. id: 'C4', cat: 'C · 人群渠道', p: 'P0', t: '一线 + 新一线 IP 占比 ≥ 50%',
  379. signal: '本期评论 IP 缺京沪深广杭蓉',
  380. action: '跑步机是大件家居,一线城市才有支付力 + 居住空间,选号必须明确 IP 配比',
  381. why: '跑步机客单价 2k+ 是大件家居,需要支付力 + 居住空间。一线/新一线人群是核心。本期 IP 严重缺一线,投放打到了价格敏感人群。',
  382. how: [
  383. 'KOC 选号硬性规定: 一线 + 新一线 IP 占比 ≥ 50% (北上广深 + 杭州/成都/武汉)',
  384. 'IP 验证作为蒲公英结案核心指标',
  385. '一线城市同步投放线下广告 (社区/健身房/物业)',
  386. '新一线节点城市做品牌活动',
  387. ],
  388. outcome: '客单价 +15-20%,触达决策人群更精准,品牌势能提升',
  389. },
  390. {
  391. id: 'D1', cat: 'D · 投放机制', p: 'P0', t: '类目标签从家居/好物迁到健身/减脂',
  392. signal: '本期挂"家居 22 + 好物 21",但搜索词是"跑步机推荐/家用跑步机"',
  393. action: '简报锁定 SEO 标签 居家健身 / 减脂塑形 / 跑步机推荐 / 健身房替代',
  394. why: '"家居/好物"标签竞争激烈且与"跑步机"心智弱关联;"健身/减脂/居家健身"才是用户真实搜索路径。换标签 = 换流量池,免费降本。',
  395. how: [
  396. '简报硬性要求: 标签必含"居家健身/减脂塑形/跑步机推荐/健身房替代"',
  397. '禁用"家居好物"等泛标签',
  398. '关键词扩展: "家用跑步机选购" / "小户型跑步机" 等长尾词',
  399. '考核 SEO 流量入口占比 ≥ 30%',
  400. ],
  401. outcome: 'SEO 流量入口 +50-100%,搜索流量获客成本下降 30%',
  402. },
  403. {
  404. id: 'D2', cat: 'D · 投放机制', p: 'P1', t: 'MCN 来源审计 + 多源选号',
  405. signal: '粉丝量 1k-2k 高度同质,疑似单一 MCN 包号',
  406. action: '≥ 3 家 MCN + ≥ 20% 蒲公英直选 + ≥ 10% 自建池 + 粉丝阶梯化金字塔结构',
  407. why: '本期 KOC 粉丝量过度集中 1k-2k,疑似单一 MCN 提供"扁平池",这种号有刷数据嫌疑且互动质量低。多 MCN + 直选打散来源,保证内容生态多样性。',
  408. how: [
  409. '≥ 3 家不同 MCN 提供候选号',
  410. '≥ 20% 蒲公英直选 (跳过 MCN)',
  411. '≥ 10% 自建 KOC 池 (品牌培育)',
  412. '粉丝量阶梯: 头部 5% / 腰部 30% / 尾部 65%',
  413. ],
  414. outcome: '内容生态多样性提升,刷量风险下降,长尾流量稳定',
  415. },
  416. {
  417. id: 'D3', cat: 'D · 投放机制', p: 'P2', t: '7 天上门试驾 O2O',
  418. signal: '星星不懂星星山东: "免费体验欢迎来锻炼"',
  419. action: '借鉴戴森/特斯拉,跑步机 7 天上门 + 不满意退款,降低决策门槛,KOC 矩阵推广',
  420. why: '跑步机决策成本高 (大件 + 占地 + 长期使用),7 天试驾 = 把决策门槛拆到地板。戴森吸尘器 / 特斯拉试驾都是验证过的成熟模型。',
  421. how: [
  422. '7 天上门试驾, 不满意 100% 退款',
  423. 'KOC 矩阵推广"试用日记"内容',
  424. '配套售后 SOP: 退货免费上门取',
  425. '小红书 / 抖音"7 天试驾打卡"话题运营',
  426. ],
  427. outcome: '决策门槛大幅降低,转化率 ×1.5-2,试驾用户复购率 +50%',
  428. },
  429. {
  430. id: 'E1', cat: 'E · 数据机制', p: 'P0', t: '投后 24h 抓取 + 博主行为预警',
  431. signal: 'xlsx 19 评论 → API 17, 缺失 2 条 (可能博主自删)',
  432. action: '24h 内首版数据快照,捕捉删评/删笔记信号,识别评论区翻车风险',
  433. why: '本次复盘发现 xlsx 报备 19 条评论 → API 实际抓到 17 条,差额 2 条可能是博主自删。这是评论区出现负面/翻车的强信号,但我们目前无法及时发现。',
  434. how: [
  435. '采集脚本固化为 24h 自动定时任务',
  436. '建立"博主自删评论"预警 (报备 vs 实抓差额 ≥ 3 即告警)',
  437. '对应 KOC 客服跟进核实是否有翻车',
  438. '建立长期 KOC 风险数据库',
  439. ],
  440. outcome: '评论区翻车从"事后才知道"变成"6h 内介入",品牌信任度兜底',
  441. },
  442. {
  443. id: 'E2', cat: 'E · 数据机制', p: 'P1', t: '3 天数据保护期 + 死笔记自动补流',
  444. signal: '11/43 篇互动 < 5,33/43 篇产出极差 = ¥7000+ 浪费',
  445. action: '投后 3 天数据 < 阈值的笔记,自动触发 ¥30-50 薯条二次投流补救,而非直接放弃',
  446. why: '本期 11/43 篇互动 < 5,长尾零产出占 26%,这部分预算几乎是沉没成本。机制化止损 + 二次投流 = 把死笔记救回来。',
  447. how: [
  448. '投后 72h 自动检查互动数据',
  449. '互动 < 5: 自动追加 ¥30-50 薯条二次抢救',
  450. '抢救后仍 < 10: 标记 KOC 为低效 (3 次失败拉黑)',
  451. '建立 KOC 历史投放表现数据库',
  452. ],
  453. outcome: '长尾零互动篇数下降 30-50%,投放 ROI 持续优化',
  454. },
  455. ];
  456. const TOP5 = ['A1', 'A2', 'C1', 'B3', 'E1'];
  457. // ============== 6. 渲染 HTML ==============
  458. const fmt = (n) => Number(n || 0).toLocaleString('zh-CN');
  459. const pct = (n) => Number(n || 0).toFixed(2) + '%';
  460. const esc = (s) => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  461. const detailRows = [...items].sort((a, b) => num(b.engagement) - num(a.engagement)).map((r, i) => `
  462. <tr>
  463. <td class="num">${i + 1}</td>
  464. <td>${esc(r.name)}</td>
  465. <td><span class="tag gray">${esc(r.category || '-')}</span></td>
  466. <td><span class="tag">${esc(r.contentType || '-')}</span></td>
  467. <td class="num mono">${fmt(r.followers)}</td>
  468. <td class="num mono">¥${fmt(r.priceTotal)}</td>
  469. <td class="num mono">${fmt(r.impressions)}</td>
  470. <td class="num mono">${fmt(r.reads)}</td>
  471. <td class="num mono">${fmt(r.likes)}</td>
  472. <td class="num mono">${fmt(r.collects)}</td>
  473. <td class="num mono">${fmt(r.comments)}</td>
  474. <td class="num mono ${num(r.engagement) === 0 ? 'dim' : ''}">${fmt(r.engagement)}</td>
  475. <td>${r.noteId ? '<span class="tag green">已解析</span>' : '<span class="tag rose">短链失效</span>'}</td>
  476. </tr>`).join('');
  477. const topicRows = TOPICS.map((t) => {
  478. const hits = topicHits[t.tag];
  479. const sample = hits.sort((a, b) => b.like - a.like)[0];
  480. return `<tr>
  481. <td><span style="display:inline-block; width:8px; height:8px; border-radius:50%; background:${t.color}; margin-right:8px; vertical-align:middle"></span>${esc(t.tag)}</td>
  482. <td class="num mono">${hits.length}</td>
  483. <td class="num mono">${(hits.length / Math.max(allComments.length, 1) * 100).toFixed(1)}%</td>
  484. <td class="sample">${sample ? `<span class="text-xs text-3">[${esc(sample.koc)}@${esc(sample.ip || '-')}]</span> ${esc(sample.content.replace(/\n/g, ' ').slice(0, 80))}${sample.content.length > 80 ? '…' : ''}` : '<span class="text-3">-</span>'}</td>
  485. </tr>`;
  486. }).join('');
  487. const realCommentList = topPicks.map(({ topic, c }) => `
  488. <div class="quote">
  489. <div class="quote-meta"><span class="tag green">${esc(topic)}</span> <span class="tag gray">${esc(c.koc)}</span> <span class="text-xs text-3">${esc(c.ip || '')}</span></div>
  490. <div class="quote-text">${esc(c.content.replace(/\n/g, ' '))}</div>
  491. </div>`).join('');
  492. const HTML = `<!DOCTYPE html>
  493. <html lang="zh-CN">
  494. <head>
  495. <meta charset="UTF-8"/>
  496. <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  497. <title>舒华跑步机 · 四月小红书 KOC 宣发结案报告</title>
  498. <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
  499. <script src="https://cdn.jsdelivr.net/npm/echarts-wordcloud@2.1.0/dist/echarts-wordcloud.min.js"></script>
  500. <style>
  501. :root {
  502. --bg-0: #0A0A0A; --bg-1: #111111; --bg-2: #1A1A1A; --bg-3: #252525;
  503. --border: rgba(255,255,255,0.08); --border-strong: rgba(255,255,255,0.16);
  504. --text-1: #F5F5F5; --text-2: #A8A8A8; --text-3: #6B6B6B;
  505. --amber: #F5A623; --green: #00DC82; --purple: #8B5CF6; --rose: #FF4D8D; --blue: #3B82F6; --red: #EF4444;
  506. --font-head: 'Inter','Noto Sans SC','PingFang SC','Microsoft YaHei',system-ui,sans-serif;
  507. --font-body: 'Noto Sans SC','Inter','PingFang SC','Microsoft YaHei',system-ui,sans-serif;
  508. --font-mono: 'JetBrains Mono','SF Mono',Consolas,monospace;
  509. }
  510. * { margin: 0; padding: 0; box-sizing: border-box; }
  511. html { scroll-behavior: smooth; }
  512. body { font-family: var(--font-body); background: var(--bg-0); color: var(--text-1); line-height: 1.65; -webkit-font-smoothing: antialiased; overflow-x: hidden; }
  513. body::before { content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  514. background: radial-gradient(ellipse 900px 700px at 20% 25%, rgba(245,166,35,0.08), transparent 60%),
  515. radial-gradient(ellipse 800px 600px at 80% 75%, rgba(139,92,246,0.06), transparent 60%); }
  516. main { position: relative; z-index: 1; }
  517. ::selection { background: var(--amber); color: #000; }
  518. section { min-height: 100vh; padding: clamp(60px, 10vh, 120px) clamp(24px, 6vw, 80px); border-bottom: 1px solid var(--border); display: flex; flex-direction: column; justify-content: center; position: relative; }
  519. section .inner { width: 100%; max-width: 1280px; margin: 0 auto; }
  520. section::before { content: attr(data-num); position: absolute; top: 32px; right: 60px; font-family: var(--font-mono); font-weight: 600; font-size: 0.78rem; letter-spacing: 0.25em; color: var(--text-3); }
  521. .eyebrow { display: inline-flex; align-items: center; gap: 10px; font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--amber); font-weight: 500; margin-bottom: 18px; }
  522. .eyebrow::before { content: ''; display: inline-block; width: 24px; height: 1px; background: var(--amber); }
  523. .h0 { font-family: var(--font-head); font-weight: 900; letter-spacing: -0.04em; line-height: 0.95; font-size: clamp(3rem, 8vw, 6rem); }
  524. .h1 { font-family: var(--font-head); font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; font-size: clamp(2rem, 4vw, 3rem); margin-bottom: 16px; }
  525. .h2 { font-family: var(--font-head); font-weight: 700; font-size: clamp(1.4rem, 2.4vw, 1.8rem); margin: 32px 0 16px; }
  526. .lead { color: var(--text-2); font-size: 1.05rem; max-width: 720px; margin-top: 18px; line-height: 1.8; }
  527. .sub { color: var(--text-2); margin-bottom: 32px; max-width: 800px; }
  528. .text-3 { color: var(--text-3); }
  529. .text-xs { font-size: 0.74rem; }
  530. .text-sm { font-size: 0.85rem; }
  531. .mono { font-family: var(--font-mono); }
  532. .dim { color: var(--text-3); }
  533. .grid { display: grid; gap: 22px; }
  534. .grid-2 { grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); }
  535. .grid-3 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
  536. .grid-4 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
  537. .card { background: linear-gradient(180deg, rgba(255,255,255,0.025) 0%, rgba(255,255,255,0.005) 100%), var(--bg-1); border: 1px solid var(--border); border-radius: 12px; padding: 28px; transition: transform 0.3s, border-color 0.3s; }
  538. .card:hover { transform: translateY(-3px); border-color: var(--border-strong); }
  539. .card-accent { border-left: 3px solid var(--amber); }
  540. .card-green { border-left: 3px solid var(--green); }
  541. .card-rose { border-left: 3px solid var(--rose); }
  542. .card-blue { border-left: 3px solid var(--blue); }
  543. .metric { background: var(--bg-1); border: 1px solid var(--border); border-radius: 12px; padding: 24px; position: relative; overflow: hidden; min-width: 0; }
  544. .metric::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, var(--amber), transparent); opacity: 0.6; }
  545. .metric-label { font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(--text-3); margin-bottom: 10px; }
  546. .metric-value { font-family: var(--font-head); font-weight: 800; font-size: clamp(1.6rem, 2.6vw, 2.3rem); letter-spacing: -0.03em; line-height: 1.05; word-break: break-all; }
  547. .metric-unit { font-family: var(--font-mono); font-size: 0.85rem; color: var(--amber); font-weight: 500; margin-left: 4px; }
  548. .metric-sub { margin-top: 14px; font-size: 0.82rem; color: var(--text-2); line-height: 1.5; }
  549. .tag { display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 4px; font-size: 0.72rem; font-weight: 500; background: rgba(245,166,35,0.1); color: var(--amber); border: 1px solid rgba(245,166,35,0.22); }
  550. .tag.green { background: rgba(0,220,130,0.08); color: var(--green); border-color: rgba(0,220,130,0.2); }
  551. .tag.purple { background: rgba(139,92,246,0.08); color: var(--purple); border-color: rgba(139,92,246,0.2); }
  552. .tag.rose { background: rgba(255,77,141,0.08); color: var(--rose); border-color: rgba(255,77,141,0.2); }
  553. .tag.blue { background: rgba(59,130,246,0.08); color: var(--blue); border-color: rgba(59,130,246,0.2); }
  554. .tag.gray { background: rgba(255,255,255,0.04); color: var(--text-2); border-color: var(--border); }
  555. table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
  556. th, td { padding: 10px 12px; border-bottom: 1px solid var(--border); text-align: left; }
  557. th { color: var(--text-3); font-weight: 500; font-size: 0.74rem; letter-spacing: 0.06em; text-transform: uppercase; background: var(--bg-2); position: sticky; top: 0; z-index: 1; }
  558. td.num, th.num { text-align: right; }
  559. tr:hover td { background: rgba(255,255,255,0.025); }
  560. td.sample { color: var(--text-2); font-size: 0.82rem; max-width: 380px; }
  561. .scroll-table { max-height: 70vh; overflow: auto; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-1); }
  562. .scroll-table::-webkit-scrollbar { width: 8px; height: 8px; }
  563. .scroll-table::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 4px; }
  564. .chart { width: 100%; height: 360px; }
  565. .chart-tall { width: 100%; height: 460px; }
  566. .quote { background: var(--bg-1); border-left: 3px solid var(--amber); border-radius: 0 8px 8px 0; padding: 14px 18px; margin-bottom: 12px; }
  567. .quote-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; flex-wrap: wrap; }
  568. .quote-text { color: var(--text-1); font-size: 0.94rem; line-height: 1.65; }
  569. .rec-row { display: flex; gap: 18px; padding: 16px 0; border-bottom: 1px solid var(--border); align-items: flex-start; }
  570. .rec-row:last-child { border-bottom: none; }
  571. .rec-p { flex-shrink: 0; width: 48px; font-family: var(--font-mono); font-weight: 700; font-size: 0.9rem; padding: 6px 10px; border-radius: 4px; text-align: center; }
  572. .rec-p.P0 { background: rgba(255,77,141,0.1); color: var(--rose); border: 1px solid rgba(255,77,141,0.3); }
  573. .rec-p.P1 { background: rgba(245,166,35,0.1); color: var(--amber); border: 1px solid rgba(245,166,35,0.3); }
  574. .rec-p.P2 { background: rgba(139,92,246,0.1); color: var(--purple); border: 1px solid rgba(139,92,246,0.3); }
  575. .rec-content h4 { font-size: 1.05rem; margin-bottom: 6px; }
  576. .rec-content p { color: var(--text-2); font-size: 0.9rem; line-height: 1.65; }
  577. .cover { background: radial-gradient(ellipse 60% 80% at 30% 30%, rgba(245,166,35,0.12) 0%, transparent 70%), radial-gradient(ellipse 40% 60% at 80% 70%, rgba(139,92,246,0.06) 0%, transparent 70%), var(--bg-0); }
  578. .cover-meta { display: flex; gap: 32px; margin-top: 32px; flex-wrap: wrap; }
  579. .cover-meta-item { font-family: var(--font-mono); font-size: 0.85rem; color: var(--text-2); }
  580. .cover-meta-item strong { color: var(--text-1); font-weight: 600; display: block; margin-top: 4px; font-size: 1.1rem; }
  581. .toc { display: flex; gap: 14px; margin-top: 48px; flex-wrap: wrap; }
  582. .toc a { padding: 8px 16px; border: 1px solid var(--border); border-radius: 100px; color: var(--text-2); font-size: 0.85rem; transition: all 0.2s; cursor: pointer; text-decoration: none; }
  583. .toc a:hover { border-color: var(--amber); color: var(--amber); }
  584. @media (max-width: 768px) {
  585. section { padding: 80px 20px; min-height: auto; }
  586. .h0 { font-size: 2.5rem; }
  587. table { font-size: 0.74rem; }
  588. td, th { padding: 6px 8px; }
  589. }
  590. </style>
  591. </head>
  592. <body>
  593. <main>
  594. <!-- ============= 封面 ============= -->
  595. <section class="cover" data-num="00 / COVER">
  596. <div class="inner">
  597. <div class="eyebrow">SHUA · 跑步机 · KOC 宣发结案 · 2026.04</div>
  598. <h1 class="h0">舒华跑步机<br/>四月小红书 KOC 宣发<br/><span style="color:var(--amber)">结案报告</span></h1>
  599. <p class="lead">基于蒲公英 ${summary.totalKocs} 位 KOC 报备数据 + 公开评论真实抓取,完整复盘投放规模、内容形态、互动效率、评论 VOC 与品牌词渗透,并给出下一轮可执行的优化建议。</p>
  600. <div class="cover-meta">
  601. <div class="cover-meta-item">投放期间<strong>2026-04</strong></div>
  602. <div class="cover-meta-item">KOC 数量<strong>${summary.totalKocs} 位</strong></div>
  603. <div class="cover-meta-item">总预算<strong>¥${fmt(summary.totalSpend)}</strong></div>
  604. <div class="cover-meta-item">总曝光<strong>${fmt(summary.totalImpressions)}</strong></div>
  605. <div class="cover-meta-item">总互动<strong>${fmt(summary.totalEngagement)}</strong></div>
  606. <div class="cover-meta-item">真实评论<strong>${allComments.length} 条</strong></div>
  607. </div>
  608. <div class="toc">
  609. <a href="#sec-1">① 数据汇总</a>
  610. <a href="#sec-2">② 数据明细</a>
  611. <a href="#sec-3">③ 话题数据</a>
  612. <a href="#sec-4">④ 品牌相关词</a>
  613. <a href="#sec-5">⑤ 优点 / 不足 / 建议</a>
  614. <a href="#sec-6" style="border-color:var(--rose);color:var(--rose)">⑥ 新机会挖掘 ★</a>
  615. </div>
  616. </div>
  617. </section>
  618. <!-- ============= 1. 数据汇总 ============= -->
  619. <section id="sec-1" data-num="01 / SUMMARY">
  620. <div class="inner">
  621. <div class="eyebrow">Section 01</div>
  622. <h2 class="h1">数据汇总 — 投放规模与产出效率</h2>
  623. <p class="sub">本期 ${summary.totalKocs} 位 KOC,合计预算 ¥${fmt(summary.totalSpend)},总曝光 ${fmt(summary.totalImpressions)} / 总阅读 ${fmt(summary.totalReads)} / 总互动 ${fmt(summary.totalEngagement)},整体互动率 ${pct(summary.engagementRate)}。</p>
  624. <div class="grid grid-4">
  625. <div class="metric"><div class="metric-label">投放 KOC</div><div class="metric-value">${summary.totalKocs}<span class="metric-unit">位</span></div><div class="metric-sub">家居 ${catStats.find(c=>c.category==='家居')?.count||0} · 好物 ${catStats.find(c=>c.category==='好物')?.count||0}</div></div>
  626. <div class="metric"><div class="metric-label">总预算</div><div class="metric-value">¥${fmt(summary.totalSpend)}</div><div class="metric-sub">单价均值 ¥${Math.round(summary.totalSpend / summary.totalKocs)}</div></div>
  627. <div class="metric"><div class="metric-label">总曝光</div><div class="metric-value">${fmt(summary.totalImpressions)}</div><div class="metric-sub">CPM ¥${summary.cpm.toFixed(2)}</div></div>
  628. <div class="metric"><div class="metric-label">总阅读</div><div class="metric-value">${fmt(summary.totalReads)}</div><div class="metric-sub">阅读率 ${(summary.totalReads/Math.max(summary.totalImpressions,1)*100).toFixed(1)}%</div></div>
  629. <div class="metric"><div class="metric-label">总互动</div><div class="metric-value">${fmt(summary.totalEngagement)}</div><div class="metric-sub">CPE ¥${summary.cpe.toFixed(2)}</div></div>
  630. <div class="metric"><div class="metric-label">点赞</div><div class="metric-value">${fmt(summary.totalLikes)}</div><div class="metric-sub">占互动 ${(summary.totalLikes/Math.max(summary.totalEngagement,1)*100).toFixed(1)}%</div></div>
  631. <div class="metric"><div class="metric-label">收藏</div><div class="metric-value">${fmt(summary.totalCollects)}</div><div class="metric-sub">深度兴趣信号</div></div>
  632. <div class="metric"><div class="metric-label">评论</div><div class="metric-value">${fmt(summary.totalComments)}<span class="metric-unit">/ ${allComments.length} 抓取</span></div><div class="metric-sub">评论激发率偏低</div></div>
  633. </div>
  634. <h3 class="h2">内容形态产出对比</h3>
  635. <div class="grid grid-2">
  636. <div class="card"><div id="chart-type-engagement" class="chart"></div></div>
  637. <div class="card"><div id="chart-type-rate" class="chart"></div></div>
  638. </div>
  639. </div>
  640. </section>
  641. <!-- ============= 2. 数据明细 ============= -->
  642. <section id="sec-2" data-num="02 / DETAIL">
  643. <div class="inner">
  644. <div class="eyebrow">Section 02</div>
  645. <h2 class="h1">数据明细 — 全 ${summary.totalKocs} 位 KOC 投放表</h2>
  646. <p class="sub">按总互动数降序排列。原始 Excel 数据 + 短链解析后的真实笔记 ID + 公开评论数据合并展示。</p>
  647. <div class="scroll-table">
  648. <table>
  649. <thead><tr>
  650. <th class="num">#</th><th>KOC</th><th>类目</th><th>内容形态</th>
  651. <th class="num">粉丝</th><th class="num">单价</th>
  652. <th class="num">曝光</th><th class="num">阅读</th>
  653. <th class="num">点赞</th><th class="num">收藏</th><th class="num">评论</th><th class="num">互动</th>
  654. <th>笔记状态</th>
  655. </tr></thead>
  656. <tbody>${detailRows}</tbody>
  657. </table>
  658. </div>
  659. </div>
  660. </section>
  661. <!-- ============= 3. 话题数据 ============= -->
  662. <section id="sec-3" data-num="03 / TOPICS">
  663. <div class="inner">
  664. <div class="eyebrow">Section 03</div>
  665. <h2 class="h1">话题数据 — 评论 VOC 主题分布</h2>
  666. <p class="sub">基于 ${allComments.length} 条真实评论 (含 ${noiseComments.length} 条互助互赞噪声 = ${noiseRate.toFixed(1)}%),按 ${TOPICS.length} 个主题进行规则化标注。</p>
  667. <div class="grid grid-2">
  668. <div class="card"><div id="chart-topics" class="chart-tall"></div></div>
  669. <div class="card">
  670. <h3 class="h2" style="margin-top:0">主题命中明细</h3>
  671. <table>
  672. <thead><tr><th>主题</th><th class="num">命中</th><th class="num">占比</th><th>典型样本</th></tr></thead>
  673. <tbody>${topicRows}</tbody>
  674. </table>
  675. </div>
  676. </div>
  677. <h3 class="h2">真实评论原声 (Top ${topPicks.length})</h3>
  678. <p class="sub">产品强相关 · 多主题轮询 · 已剔除噪声/托式营销/博主自评,展示具有产品对话价值的真实评论。</p>
  679. <div class="grid grid-2">${realCommentList}</div>
  680. </div>
  681. </section>
  682. <!-- ============= 4. 品牌相关词存量 ============= -->
  683. <section id="sec-4" data-num="04 / WORDS">
  684. <div class="inner">
  685. <div class="eyebrow">Section 04</div>
  686. <h2 class="h1">品牌相关词存量 — 心智渗透盘点</h2>
  687. <p class="sub">从评论文本 + 投放预定关键词清单中提取,显示当前品牌词、活动词、品类词在用户对话中的实际渗透情况,以及笔记元数据词云。</p>
  688. <div class="grid grid-2">
  689. <div class="card card-accent">
  690. <h3 class="h2" style="margin-top:0">① 品牌词</h3>
  691. <p class="text-sm text-3">舒华 / SHUA / 舒华跑步机 等核心品牌词</p>
  692. <div id="cloud-brand" class="chart"></div>
  693. </div>
  694. <div class="card card-green">
  695. <h3 class="h2" style="margin-top:0">② 活动相关词</h3>
  696. <p class="text-sm text-3">新品首发 / 618 / 直播福利 / 领券 等本期投放预制活动词</p>
  697. <div id="cloud-activity" class="chart"></div>
  698. </div>
  699. <div class="card card-blue">
  700. <h3 class="h2" style="margin-top:0">③ 品类词占位</h3>
  701. <p class="text-sm text-3">跑步机 / 折叠 / 静音 / 走步机 等品类心智词</p>
  702. <div id="cloud-category" class="chart"></div>
  703. </div>
  704. <div class="card card-rose">
  705. <h3 class="h2" style="margin-top:0">④ 评论高频词云</h3>
  706. <p class="text-sm text-3">从全部评论文本提取的中文高频 2~3 字词组</p>
  707. <div id="cloud-comments" class="chart"></div>
  708. </div>
  709. </div>
  710. <h3 class="h2">关键洞察</h3>
  711. <div class="grid grid-3">
  712. <div class="card card-rose">
  713. <div class="tag rose">⚠️ 品牌词渗透不足</div>
  714. <p style="margin-top:14px;color:var(--text-2)">"舒华/SHUA" 在评论中出现频次极低,用户更多在谈论"易跑/麦瑞克"等竞品和"健身房/减肥"等通用品类话题。</p>
  715. </div>
  716. <div class="card card-accent">
  717. <div class="tag">⚠ 活动词记忆点散</div>
  718. <p style="margin-top:14px;color:var(--text-2)">没有形成统一可记忆的活动钩子,KOC 笔记 hook 太分散,建议下一轮锁定 1~2 个活动主张。</p>
  719. </div>
  720. <div class="card card-green">
  721. <div class="tag green">✓ 品类词承载明确</div>
  722. <p style="margin-top:14px;color:var(--text-2)">"跑步机"作为品类词在用户语境中清晰存在,应捆绑"静音/减震/折叠"3 大附属词形成差异化。</p>
  723. </div>
  724. </div>
  725. </div>
  726. </section>
  727. <!-- ============= 5. 优点 / 不足 / 建议 ============= -->
  728. <section id="sec-5" data-num="05 / VERDICT">
  729. <div class="inner">
  730. <div class="eyebrow">Section 05</div>
  731. <h2 class="h1">本次宣发的优点、不足 & 后续投放建议</h2>
  732. <h3 class="h2"><span class="tag green">✓ 优点</span></h3>
  733. <div class="grid grid-3">
  734. ${PROS.map((p) => `<div class="card card-green"><h4 style="margin-bottom:10px;color:var(--green)">${esc(p.title)}</h4><p style="color:var(--text-2);font-size:0.9rem">${p.text}</p></div>`).join('')}
  735. </div>
  736. <h3 class="h2"><span class="tag rose">✗ 不足</span></h3>
  737. <div class="grid grid-2">
  738. ${CONS.map((c) => `<div class="card card-rose"><h4 style="margin-bottom:10px;color:var(--rose)">${esc(c.title)}</h4><p style="color:var(--text-2);font-size:0.9rem">${c.text}</p></div>`).join('')}
  739. </div>
  740. <h3 class="h2"><span class="tag">→ 后续投放建议</span></h3>
  741. <div class="card">
  742. ${RECS.map((r) => `<div class="rec-row"><div class="rec-p ${r.p}">${r.p}</div><div class="rec-content"><h4>${esc(r.t)}</h4><p>${r.b}</p></div></div>`).join('')}
  743. </div>
  744. <h3 class="h2">下一轮预算建议分配</h3>
  745. <div class="grid grid-4">
  746. <div class="metric"><div class="metric-label">头 / 腰部 KOL</div><div class="metric-value">40<span class="metric-unit">%</span></div><div class="metric-sub">2~3 位垂类健身/减脂 KOL,粉丝 ≥ 10万,单篇深度内容</div></div>
  747. <div class="metric"><div class="metric-label">优质 KOC 矩阵</div><div class="metric-value">35<span class="metric-unit">%</span></div><div class="metric-sub">25~30 篇,粉丝 ≥ 3,000,2 种形态各 15 篇</div></div>
  748. <div class="metric"><div class="metric-label">薯条 / 聚光投流</div><div class="metric-value">20<span class="metric-unit">%</span></div><div class="metric-sub">追加 Top 互动笔记,放大长尾流量</div></div>
  749. <div class="metric"><div class="metric-label">评论区运营 + 数据</div><div class="metric-value">5<span class="metric-unit">%</span></div><div class="metric-sub">SOP 话术 + 风险词预警 + 投后 24h 抓取</div></div>
  750. </div>
  751. </div>
  752. </section>
  753. <!-- ============= 6. 新机会挖掘 ============= -->
  754. <section id="sec-6" data-num="06 / OPPORTUNITIES">
  755. <div class="inner">
  756. <div class="eyebrow" style="color:var(--rose)">Section 06 · ★ 重中之重</div>
  757. <h2 class="h1">新机会挖掘 — 从 ${allComments.length} 条评论逆向反推 ${OPPORTUNITIES.length} 个新机会</h2>
  758. <p class="sub">**结案报告告诉老板"过去花得值不值",本节告诉团队"下一步该怎么花得更值"。** 把每一条评论当成一次市场对话逐字解读,从微观信号反推产品/内容/人群/投放/数据 5 大象限的机会缺口。完整逐条解读见配套文档 <a href="./shuhua-new-opportunities.md" style="color:var(--amber);text-decoration:underline">shuhua-new-opportunities.md</a>。</p>
  759. <h3 class="h2">⭐ Top 5 P0 优先级 (下周可启动)</h3>
  760. <div class="grid grid-3">
  761. ${TOP5.map((id, idx) => {
  762. const o = OPPORTUNITIES.find((x) => x.id === id);
  763. return `<div class="card card-rose" style="position:relative">
  764. <div style="position:absolute;top:14px;right:18px;font-family:var(--font-mono);font-size:1.6rem;font-weight:800;color:var(--rose);opacity:0.6">#${idx + 1}</div>
  765. <div class="tag rose" style="margin-bottom:10px">${o.id} · ${o.p}</div>
  766. <h4 style="font-size:1.1rem;margin-bottom:10px">${esc(o.t)}</h4>
  767. <p style="color:var(--text-3);font-size:0.78rem;margin-bottom:8px"><strong style="color:var(--text-2)">来源信号:</strong> ${esc(o.signal)}</p>
  768. <p style="color:var(--text-2);font-size:0.88rem;line-height:1.6">${esc(o.action)}</p>
  769. </div>`;
  770. }).join('')}
  771. </div>
  772. <p class="sub" style="margin-top:32px">完整 ${OPPORTUNITIES.length} 个新机会按 5 大象限分独立章节深度展开,见下方 Section 06.A ~ 06.E。</p>
  773. <div class="toc" style="margin-top:24px">
  774. <a href="#sec-6a" style="border-color:var(--amber);color:var(--amber)">06.A 产品卖点 (${OPPORTUNITIES.filter((o)=>o.cat.startsWith('A')).length})</a>
  775. <a href="#sec-6b" style="border-color:var(--green);color:var(--green)">06.B 内容形态 (${OPPORTUNITIES.filter((o)=>o.cat.startsWith('B')).length})</a>
  776. <a href="#sec-6c" style="border-color:var(--purple);color:var(--purple)">06.C 人群渠道 (${OPPORTUNITIES.filter((o)=>o.cat.startsWith('C')).length})</a>
  777. <a href="#sec-6d" style="border-color:var(--blue);color:var(--blue)">06.D 投放机制 (${OPPORTUNITIES.filter((o)=>o.cat.startsWith('D')).length})</a>
  778. <a href="#sec-6e" style="border-color:var(--rose);color:var(--rose)">06.E 数据机制 (${OPPORTUNITIES.filter((o)=>o.cat.startsWith('E')).length})</a>
  779. </div>
  780. <h3 class="h2" style="margin-top:48px">方法论 — 4 条挖掘原则</h3>
  781. <div class="grid grid-4">
  782. <div class="card"><div class="tag">原则 1</div><h4 style="margin:10px 0">小样本不影响信号方向</h4><p style="color:var(--text-2);font-size:0.84rem">1 条专业参数评论 = 1 个产品价值锚点,<strong>信号密度</strong> > 统计显著性</p></div>
  783. <div class="card"><div class="tag green">原则 2</div><h4 style="margin:10px 0">每条评论都是潜在购买者</h4><p style="color:var(--text-2);font-size:0.84rem">不是数据,是真人,值得<strong>逐字解读</strong> — 词序、表情、IP 全有意义</p></div>
  784. <div class="card"><div class="tag purple">原则 3</div><h4 style="margin:10px 0">反直觉信号 > 平均值</h4><p style="color:var(--text-2);font-size:0.84rem">"泛流量型"互动最低却产生最高质量对话 — 这就是被低估的机会</p></div>
  785. <div class="card"><div class="tag rose">原则 4</div><h4 style="margin:10px 0">缺席与出现同等重要</h4><p style="color:var(--text-2);font-size:0.84rem">一线 IP 缺席、男性 KOC 缺席、品牌词命中 0 — <strong>"没有"才是最大的机会</strong></p></div>
  786. </div>
  787. </div>
  788. </section>
  789. ${(() => {
  790. const QUADRANTS = [
  791. { letter: 'A', cat: 'A · 产品卖点', color: 'amber', tagClass: '', accent: 'card-accent', desc: '从用户评论里反推的产品/卖点级机会 — 把"用户已经说出口"的需求变成可执行的内容指引/SKU/卖点。' },
  792. { letter: 'B', cat: 'B · 内容形态', color: 'green', tagClass: 'green', accent: 'card-green', desc: '内容打法升级 — 同样的预算,如何把"被低估的内容形态"翻转成主战场。' },
  793. { letter: 'C', cat: 'C · 人群渠道', color: 'purple', tagClass: 'purple', accent: '', desc: '人群和渠道的二八优化 — 让每一分预算都投在"会真买跑步机"的人身上,而不是看了不下单的远方观众。' },
  794. { letter: 'D', cat: 'D · 投放机制', color: 'blue', tagClass: 'blue', accent: 'card-blue', desc: '投放过程的机制化 — 把"凭感觉发笔记"变成"算法驱动止损/加投/预警",每轮都比上轮更聪明。' },
  795. { letter: 'E', cat: 'E · 数据机制', color: 'rose', tagClass: 'rose', accent: 'card-rose', desc: '长效数据资产 — 投后快照、博主行为预警,这是可以持续复用 N 轮投放的底层基建。' },
  796. ];
  797. return QUADRANTS.map((q) => {
  798. const list = OPPORTUNITIES.filter((o) => o.cat === q.cat);
  799. return `
  800. <!-- ============= 6.${q.letter} ${q.cat} ============= -->
  801. <section id="sec-6${q.letter.toLowerCase()}" data-num="06.${q.letter} / ${q.cat.split('·')[1].trim()}" style="justify-content:flex-start">
  802. <div class="inner">
  803. <div class="eyebrow" style="color:var(--${q.color})">Section 06.${q.letter} · ${q.cat}</div>
  804. <h2 class="h1" style="color:var(--${q.color})">${esc(q.cat)} <span style="color:var(--text-3);font-size:0.5em;font-weight:500">${list.length} 个新机会</span></h2>
  805. <p class="sub">${esc(q.desc)}</p>
  806. <div class="grid" style="gap:28px;margin-top:32px">
  807. ${list.map((o) => `
  808. <div class="card ${q.accent}" style="position:relative">
  809. <div style="position:absolute;top:18px;right:24px;font-family:var(--font-mono);font-size:2.4rem;font-weight:800;color:var(--${q.color});opacity:0.15;line-height:1">${o.id}</div>
  810. <div style="display:flex;gap:10px;align-items:center;margin-bottom:14px;flex-wrap:wrap">
  811. <span class="tag ${q.tagClass}" style="font-family:var(--font-mono);font-size:0.8rem">${o.id}</span>
  812. <span class="tag ${o.p === 'P0' ? 'rose' : o.p === 'P1' ? '' : 'purple'}">${o.p}</span>
  813. <h3 style="font-family:var(--font-head);font-size:1.4rem;font-weight:700;letter-spacing:-0.01em">${esc(o.t)}</h3>
  814. </div>
  815. <div class="grid grid-3" style="gap:18px;margin-top:18px">
  816. <div style="background:var(--bg-0);border-radius:8px;padding:18px;border-left:2px solid var(--${q.color})">
  817. <div class="metric-label" style="margin-bottom:8px">📍 信号 / Signal</div>
  818. <p style="color:var(--text-2);font-size:0.88rem;line-height:1.65">${esc(o.signal)}</p>
  819. </div>
  820. <div style="background:var(--bg-0);border-radius:8px;padding:18px;border-left:2px solid var(--${q.color})">
  821. <div class="metric-label" style="margin-bottom:8px">🎯 行动 / Action</div>
  822. <p style="color:var(--text-1);font-size:0.88rem;line-height:1.65;font-weight:500">${esc(o.action)}</p>
  823. </div>
  824. <div style="background:var(--bg-0);border-radius:8px;padding:18px;border-left:2px solid var(--${q.color})">
  825. <div class="metric-label" style="margin-bottom:8px">📈 预期 / Outcome</div>
  826. <p style="color:var(--text-2);font-size:0.88rem;line-height:1.65">${esc(o.outcome || '-')}</p>
  827. </div>
  828. </div>
  829. <div style="margin-top:22px;padding:20px;background:var(--bg-0);border-radius:8px">
  830. <div class="metric-label" style="margin-bottom:10px">💡 为什么要做 / Why</div>
  831. <p style="color:var(--text-2);font-size:0.92rem;line-height:1.75">${esc(o.why || '-')}</p>
  832. </div>
  833. ${Array.isArray(o.how) && o.how.length ? `
  834. <div style="margin-top:18px;padding:20px;background:var(--bg-0);border-radius:8px">
  835. <div class="metric-label" style="margin-bottom:12px">🛠️ 如何执行 / How (${o.how.length} 步)</div>
  836. <ol style="margin:0;padding-left:24px;color:var(--text-2);font-size:0.9rem;line-height:1.85">
  837. ${o.how.map((step) => `<li style="margin-bottom:6px">${esc(step)}</li>`).join('')}
  838. </ol>
  839. </div>
  840. ` : ''}
  841. </div>
  842. `).join('')}
  843. </div>
  844. </div>
  845. </section>`;
  846. }).join('');
  847. })()}
  848. <!-- ============= 页脚 ============= -->
  849. <section style="min-height:auto;padding:60px 24px;background:var(--bg-1)" data-num="END">
  850. <div class="inner" style="text-align:center">
  851. <p class="text-sm text-3">舒华跑步机 · 四月小红书 KOC 宣发结案报告 · 基于 ${summary.totalKocs} 位 KOC + ${allComments.length} 条评论真实数据生成</p>
  852. <p class="text-xs text-3 mono" style="margin-top:8px">Generated ${new Date().toISOString().slice(0,19).replace('T',' ')}</p>
  853. </div>
  854. </section>
  855. </main>
  856. <script>
  857. const TYPE_DATA = ${JSON.stringify(typeStats)};
  858. const TOPIC_DATA = ${JSON.stringify(TOPICS.map(t=>({tag:t.tag, color:t.color, count:topicHits[t.tag].length})))};
  859. const CLOUD_BRAND = ${JSON.stringify(cloudBrand)};
  860. const CLOUD_ACTIVITY = ${JSON.stringify(cloudActivity)};
  861. const CLOUD_CATEGORY = ${JSON.stringify(cloudCategory)};
  862. const CLOUD_COMMENTS = ${JSON.stringify(wordcloudComments.map(([n,v])=>({name:n,value:v})))};
  863. const COMMON = {
  864. backgroundColor: 'transparent',
  865. textStyle: { fontFamily: 'Inter, Noto Sans SC, system-ui', color: '#A8A8A8' }
  866. };
  867. function initWhenReady(){
  868. if (!window.echarts) { setTimeout(initWhenReady, 200); return; }
  869. // 内容形态 - 互动堆叠
  870. echarts.init(document.getElementById('chart-type-engagement')).setOption({
  871. ...COMMON,
  872. title: { text: '内容形态 · 总互动 / 篇数', left: 0, textStyle: { color:'#F5F5F5', fontSize:14, fontWeight:600 } },
  873. tooltip: { trigger:'axis' },
  874. legend: { textStyle:{color:'#A8A8A8'}, top: 0, right: 0 },
  875. grid: { left: 60, right: 30, bottom: 50, top: 60 },
  876. xAxis: { type:'category', data: TYPE_DATA.map(d=>d.type), axisLabel:{ color:'#A8A8A8', interval:0, rotate:15 }, axisLine:{ lineStyle:{ color:'rgba(255,255,255,0.1)'}} },
  877. yAxis: [
  878. { type:'value', name:'总互动', axisLabel:{color:'#A8A8A8'}, splitLine:{ lineStyle:{ color:'rgba(255,255,255,0.05)'}}, nameTextStyle:{color:'#A8A8A8'} },
  879. { type:'value', name:'篇数', axisLabel:{color:'#A8A8A8'}, splitLine:{show:false}, nameTextStyle:{color:'#A8A8A8'} }
  880. ],
  881. series: [
  882. { name:'总互动', type:'bar', data: TYPE_DATA.map(d=>d.engagement), itemStyle:{color:'#F5A623', borderRadius:[4,4,0,0]} },
  883. { name:'篇数', type:'line', yAxisIndex:1, data: TYPE_DATA.map(d=>d.count), itemStyle:{color:'#00DC82'}, lineStyle:{width:2}, smooth:true }
  884. ]
  885. });
  886. // 内容形态 - 互动率
  887. echarts.init(document.getElementById('chart-type-rate')).setOption({
  888. ...COMMON,
  889. title: { text: '内容形态 · 互动率 (%)', left: 0, textStyle: { color:'#F5F5F5', fontSize:14, fontWeight:600 } },
  890. tooltip: { trigger:'axis', formatter: (p)=> p[0].name+'<br/>'+p[0].value.toFixed(2)+'%' },
  891. grid: { left: 60, right: 30, bottom: 50, top: 60 },
  892. xAxis: { type:'category', data: TYPE_DATA.map(d=>d.type), axisLabel:{color:'#A8A8A8', interval:0, rotate:15} },
  893. yAxis: { type:'value', axisLabel:{color:'#A8A8A8', formatter:'{value}%'}, splitLine:{lineStyle:{color:'rgba(255,255,255,0.05)'}} },
  894. series: [{ type:'bar', data: TYPE_DATA.map(d=>+d.rate.toFixed(2)), itemStyle:{color:'#8B5CF6', borderRadius:[4,4,0,0]} }]
  895. });
  896. // 主题分布
  897. echarts.init(document.getElementById('chart-topics')).setOption({
  898. ...COMMON,
  899. title: { text: '评论主题命中分布', left: 0, textStyle: { color:'#F5F5F5', fontSize:14, fontWeight:600 } },
  900. tooltip: { trigger:'axis' },
  901. grid: { left: 140, right: 40, bottom: 30, top: 50 },
  902. xAxis: { type:'value', axisLabel:{color:'#A8A8A8'}, splitLine:{lineStyle:{color:'rgba(255,255,255,0.05)'}} },
  903. yAxis: { type:'category', data: TOPIC_DATA.map(d=>d.tag), axisLabel:{color:'#A8A8A8', fontSize:12} },
  904. series: [{
  905. type:'bar', data: TOPIC_DATA.map(d=>({value:d.count, itemStyle:{color:d.color, borderRadius:[0,4,4,0]}})),
  906. label:{show:true, position:'right', color:'#F5F5F5', fontSize:12}
  907. }]
  908. });
  909. function wordcloud(id, data, colors){
  910. if (!echarts.registerLayout && !echarts.WordCloud && !window['echarts-wordcloud']) {/* plugin loaded as side effect */}
  911. echarts.init(document.getElementById(id)).setOption({
  912. ...COMMON,
  913. tooltip: { show: true, formatter: (p)=> p.name + ' · ' + p.value },
  914. series: [{
  915. type: 'wordCloud',
  916. shape: 'circle',
  917. sizeRange: [12, 56],
  918. rotationRange: [-30, 30],
  919. rotationStep: 30,
  920. gridSize: 8,
  921. drawOutOfBound: false,
  922. layoutAnimation: true,
  923. textStyle: {
  924. fontFamily: 'Noto Sans SC, Inter, sans-serif',
  925. fontWeight: 600,
  926. color: () => colors[Math.floor(Math.random()*colors.length)]
  927. },
  928. emphasis: { textStyle: { color: '#F5A623', shadowBlur: 10, shadowColor: 'rgba(245,166,35,0.5)' } },
  929. data: data
  930. }]
  931. });
  932. }
  933. wordcloud('cloud-brand', CLOUD_BRAND, ['#F5A623','#FFB849','#FFC97D']);
  934. wordcloud('cloud-activity', CLOUD_ACTIVITY, ['#00DC82','#34E899','#5EEAB1']);
  935. wordcloud('cloud-category', CLOUD_CATEGORY, ['#3B82F6','#60A5FA','#93C5FD']);
  936. wordcloud('cloud-comments', CLOUD_COMMENTS, ['#FF4D8D','#FF7AAB','#FFA3C2','#A8A8A8']);
  937. }
  938. initWhenReady();
  939. </script>
  940. </body>
  941. </html>`;
  942. fs.writeFileSync(OUT_HTML, HTML, 'utf8');
  943. console.log('✓ HTML:', OUT_HTML, Math.round(HTML.length/1024)+'KB');
  944. // ============== 7. 渲染 MD ==============
  945. const md = [];
  946. const M = (s) => md.push(s);
  947. M('# 舒华跑步机 · 四月小红书 KOC 宣发 结案报告');
  948. M('');
  949. M(`> **投放期间**: 2026-04 · **生成时间**: ${new Date().toISOString().slice(0,19).replace('T',' ')} `);
  950. M(`> **数据范围**: ${summary.totalKocs} 位 KOC + ${allComments.length} 条公开评论 (覆盖 ${summary.withRealComments}/${summary.totalKocs} 篇笔记)`);
  951. M('');
  952. M('---');
  953. M('');
  954. M('## 一、数据汇总');
  955. M('');
  956. M('| 指标 | 数值 | 备注 |');
  957. M('|---|---:|---|');
  958. M(`| 投放 KOC | ${summary.totalKocs} | 家居 ${catStats.find(c=>c.category==='家居')?.count||0} + 好物 ${catStats.find(c=>c.category==='好物')?.count||0} |`);
  959. M(`| 总预算 | ¥${fmt(summary.totalSpend)} | 单价均值 ¥${Math.round(summary.totalSpend/summary.totalKocs)} |`);
  960. M(`| 总曝光 | ${fmt(summary.totalImpressions)} | CPM ¥${summary.cpm.toFixed(2)} |`);
  961. M(`| 总阅读 | ${fmt(summary.totalReads)} | 阅读率 ${(summary.totalReads/Math.max(summary.totalImpressions,1)*100).toFixed(1)}% |`);
  962. M(`| 总互动 | ${fmt(summary.totalEngagement)} | CPE ¥${summary.cpe.toFixed(2)} |`);
  963. M(`| 点赞 / 收藏 / 评论 | ${fmt(summary.totalLikes)} / ${fmt(summary.totalCollects)} / ${fmt(summary.totalComments)} | 评论激发率偏低 |`);
  964. M(`| 整体互动率 | ${pct(summary.engagementRate)} | 行业基准 3-5% |`);
  965. M(`| 笔记解析成功 | ${summary.resolved}/${summary.totalKocs} | 短链失效 ${summary.failed} 个 |`);
  966. M(`| 真实评论抓取 | ${allComments.length} 条 (xlsx 报备 ${summary.totalComments}) | 噪声率 ${noiseRate.toFixed(1)}% |`);
  967. M('');
  968. M('### 内容形态产出对比');
  969. M('');
  970. M('| 内容形态 | 篇数 | 总阅读 | 总互动 | 平均互动 | 互动率 | 总投入 |');
  971. M('|---|---:|---:|---:|---:|---:|---:|');
  972. for (const t of typeStats) {
  973. M(`| ${t.type} | ${t.count} | ${fmt(t.reads)} | ${fmt(t.engagement)} | ${t.avgEng} | ${t.rate.toFixed(2)}% | ¥${fmt(t.spend)} |`);
  974. }
  975. M('');
  976. M('---');
  977. M('');
  978. M('## 二、数据明细 (全 ' + summary.totalKocs + ' 位 KOC)');
  979. M('');
  980. M('> 按总互动数降序排列');
  981. M('');
  982. M('| # | KOC | 类目 | 内容形态 | 粉丝 | 单价 | 曝光 | 阅读 | 点赞 | 收藏 | 评论 | 互动 | 笔记ID |');
  983. M('|---:|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---|');
  984. const detailSorted = [...items].sort((a,b)=>num(b.engagement)-num(a.engagement));
  985. for (let i = 0; i < detailSorted.length; i++) {
  986. const r = detailSorted[i];
  987. M(`| ${i+1} | ${r.name} | ${r.category||'-'} | ${r.contentType||'-'} | ${fmt(r.followers)} | ¥${fmt(r.priceTotal)} | ${fmt(r.impressions)} | ${fmt(r.reads)} | ${fmt(r.likes)} | ${fmt(r.collects)} | ${fmt(r.comments)} | ${fmt(r.engagement)} | ${r.noteId||'❌失效'} |`);
  988. }
  989. M('');
  990. M('---');
  991. M('');
  992. M('## 三、话题数据 — 评论 VOC 主题分布');
  993. M('');
  994. M(`基于 **${allComments.length}** 条真实评论 (含 ${noiseComments.length} 条互助互赞噪声 = ${noiseRate.toFixed(1)}%) 的规则化主题命中:`);
  995. M('');
  996. M('| 主题 | 命中 | 占比 | 典型样本 |');
  997. M('|---|---:|---:|---|');
  998. for (const t of TOPICS) {
  999. const hits = topicHits[t.tag];
  1000. const sample = hits.sort((a,b)=>b.like-a.like)[0];
  1001. const sampleText = sample ? `[${sample.koc}@${sample.ip||'-'}] ${sample.content.replace(/\n/g,' ').replace(/\|/g,'\\|').slice(0,80)}` : '-';
  1002. M(`| ${t.tag} | ${hits.length} | ${(hits.length/Math.max(allComments.length,1)*100).toFixed(1)}% | ${sampleText} |`);
  1003. }
  1004. M('');
  1005. M(`### 真实评论原声 Top ${topPicks.length} (产品强相关 · 多主题轮询)`);
  1006. M('');
  1007. for (const { topic, c } of topPicks) {
  1008. M(`- **[${topic}]** \`${c.koc}@${c.ip||'-'}\`: ${c.content.replace(/\n/g,' ')}`);
  1009. }
  1010. M('');
  1011. M('---');
  1012. M('');
  1013. M('## 四、品牌相关词存量');
  1014. M('');
  1015. M('### 4.1 品牌词');
  1016. M('');
  1017. M('| 词 | 评论命中 | 状态 |');
  1018. M('|---|---:|---|');
  1019. for (const w of cloudBrand) M(`| ${w.name} | ${w.value-5 > 0 ? w.value-5 : 0} | ${w.value > 5 ? '✓ 已渗透' : '○ 待沉淀'} |`);
  1020. M('');
  1021. M('### 4.2 活动相关词');
  1022. M('');
  1023. M('| 词 | 评论命中 | 状态 |');
  1024. M('|---|---:|---|');
  1025. for (const w of cloudActivity) M(`| ${w.name} | ${w.value-5 > 0 ? w.value-5 : 0} | ${w.value > 5 ? '✓ 已渗透' : '○ 待沉淀'} |`);
  1026. M('');
  1027. M('### 4.3 品类词占位');
  1028. M('');
  1029. M('| 词 | 评论命中 | 状态 |');
  1030. M('|---|---:|---|');
  1031. for (const w of cloudCategory) M(`| ${w.name} | ${w.value-5 > 0 ? w.value-5 : 0} | ${w.value > 5 ? '✓ 已渗透' : '○ 待沉淀'} |`);
  1032. M('');
  1033. M('### 4.4 评论高频词 Top 30');
  1034. M('');
  1035. M('| 词 | 频次 |');
  1036. M('|---|---:|');
  1037. for (const [w, v] of wordcloudComments.slice(0, 30)) M(`| ${w} | ${v} |`);
  1038. M('');
  1039. M('### 关键洞察');
  1040. M('');
  1041. M('- **⚠️ 品牌词渗透不足**: "舒华/SHUA" 在评论中出现频次极低,用户更多在谈论"易跑/麦瑞克"等竞品和"健身房/减肥"等通用品类话题');
  1042. M('- **⚠️ 活动词记忆点散**: 没有形成统一可记忆的活动钩子,KOC 笔记 hook 太分散');
  1043. M('- **✓ 品类词承载明确**: "跑步机"作为品类词在用户语境中清晰存在,应捆绑"静音/减震/折叠"3 大附属词形成差异化');
  1044. M('');
  1045. M('---');
  1046. M('');
  1047. M('## 五、本次宣发的优点 / 不足 / 后续建议');
  1048. M('');
  1049. M('### 5.1 ✓ 优点');
  1050. M('');
  1051. for (const p of PROS) {
  1052. M(`**${p.title}**`);
  1053. M('');
  1054. M(p.text);
  1055. M('');
  1056. }
  1057. M('### 5.2 ✗ 不足');
  1058. M('');
  1059. for (const c of CONS) {
  1060. M(`**${c.title}**`);
  1061. M('');
  1062. M(c.text);
  1063. M('');
  1064. }
  1065. M('### 5.3 → 后续投放建议');
  1066. M('');
  1067. M('| 优先级 | 行动 | 说明 |');
  1068. M('|---|---|---|');
  1069. for (const r of RECS) {
  1070. M(`| **${r.p}** | ${r.t} | ${r.b} |`);
  1071. }
  1072. M('');
  1073. M('### 5.4 下一轮预算建议分配');
  1074. M('');
  1075. M('| 模块 | 占比 | 说明 |');
  1076. M('|---|---:|---|');
  1077. M('| 头/腰部 KOL | 40% | 2~3 位垂类健身/减脂 KOL,粉丝 ≥ 10万,单篇深度内容 |');
  1078. M('| 优质 KOC 矩阵 | 35% | 25~30 篇,粉丝 ≥ 3,000,2 种形态各 15 篇 |');
  1079. M('| 薯条/聚光投流 | 20% | 追加 Top 互动笔记,放大长尾流量 |');
  1080. M('| 评论区运营 + 数据 | 5% | SOP 话术 + 风险词预警 + 投后 24h 抓取 |');
  1081. M('');
  1082. M('---');
  1083. M('');
  1084. M(`*本结案报告基于 ${summary.totalKocs} 位 KOC 投放数据 + ${allComments.length} 条公开评论,通过可复用 VOC 工作流自动生成。配套交互式 HTML 报告: \`shuhua-closing-report.html\`*`);
  1085. fs.writeFileSync(OUT_MD, md.join('\n'), 'utf8');
  1086. console.log('✓ MD:', OUT_MD, md.length, 'lines');