| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- #!/usr/bin/env node
- /**
- * V2:完整提炼 VOC 报告中的"复合体方向"分析所需数据
- * 基于 voc-analyze.js 的实际输出结构
- */
- const fs = require('fs');
- const path = require('path');
- const { analyze } = require('./voc-analyze.js');
- const PRODUCTS = [
- { key: 'liver', label: '江中肝纯片(OTC 保肝片剂)', file: 'jiangzhong-liver-voc.json' },
- { key: 'probiotic', label: '江中儿童乳酸菌片', file: 'jiangzhong-probiotic-voc.json' },
- { key: 'monkey', label: '江中猴姑饼干', file: 'jiangzhong-monkey-voc.json' },
- ];
- const buffer = [];
- const out = (s) => buffer.push(s);
- for (const p of PRODUCTS) {
- const fp = path.join('data', p.file);
- if (!fs.existsSync(fp)) continue;
- const voc = JSON.parse(fs.readFileSync(fp, 'utf-8'));
- const A = analyze(voc);
- out('\n\n' + '='.repeat(80));
- out(`【${p.label}】`);
- out('='.repeat(80));
- // === 规模 ===
- out('\n▌规模');
- out(` XHS: ${A.metrics.notes}笔记 / ${A.metrics.sampleComments}评论 / ${A.kols?.length || 0}作者`);
- out(` 互动: 点赞${A.metrics.totalLikes} 评论${A.metrics.totalComments} 收藏${A.metrics.totalCollected} 分享${A.metrics.totalShared}`);
- if (A.amazon && !A.amazon.empty) {
- out(` Amazon: ${A.amazon.keywords?.length || 0}关键词 × ${A.amazon.total || 0}款产品`);
- }
- // === 小红书 Top 原声 (VOC 真实语料) ===
- if (A.topComments && A.topComments.length) {
- out('\n▌小红书 Top 高赞原声');
- A.topComments.slice(0, 15).forEach((c, i) => {
- const content = (c.content || '').replace(/\s+/g, ' ').slice(0, 140);
- out(` ${String(i + 1).padStart(2, '0')}. [${c.like_count || 0}赞] ${content}`);
- });
- }
- // === Top Notes(爆款笔记标题 → 用户内容倾向)===
- if (A.topNotes && A.topNotes.length) {
- out('\n▌爆款笔记 Top 10 标题(揭示用户内容兴趣)');
- A.topNotes.slice(0, 10).forEach((n, i) => {
- const title = (n.title || n.display_title || '').replace(/\s+/g, ' ').slice(0, 80);
- out(` ${String(i + 1).padStart(2, '0')}. [${n.liked_count || 0}赞] ${title}`);
- });
- }
- // === Amazon 分析层 ===
- if (A.amazon && !A.amazon.empty) {
- // 主题:英文 Amazon reviews 的 VOC 痛点/亮点/场景
- const th = A.amazon.amazonThemes;
- if (th) {
- if (th.pain && th.pain.length) {
- out('\n▌Amazon 英文评论 · 痛点主题 (海外用户差评)');
- th.pain.slice(0, 8).forEach((t, i) => {
- out(` ${i + 1}. ${t.tag} (提及 ${t.count} 次 · ${t.brandCoverage}品牌)`);
- });
- }
- if (th.bright && th.bright.length) {
- out('\n▌Amazon 英文评论 · 亮点主题 (海外用户好评)');
- th.bright.slice(0, 8).forEach((t, i) => {
- out(` ${i + 1}. ${t.tag} (提及 ${t.count} 次 · ${t.brandCoverage}品牌)`);
- });
- }
- if (th.scenarios && th.scenarios.length) {
- out('\n▌Amazon 英文评论 · 使用场景');
- th.scenarios.slice(0, 6).forEach((t, i) => {
- out(` ${i + 1}. ${t.tag} (${t.count} 次)`);
- });
- }
- }
- // 品类机会评估
- if (A.amazon.opportunity) {
- const op = A.amazon.opportunity;
- out('\n▌品类机会评估');
- out(` 等级: ${op.grade} (${op.totalScore}/${op.maxScore}) · 甜点价位: ${op.sweetSpot}`);
- if (op.dimensions) {
- op.dimensions.forEach((d) => out(` · ${d.name}: ${d.level} — ${(d.note || '').replace(/<[^>]+>/g, '').slice(0, 100)}`));
- }
- }
- // 竞品档案(4 家)
- if (A.amazon.competitorDossiers) {
- out('\n▌竞品档案 (Top 4)');
- A.amazon.competitorDossiers.slice(0, 4).forEach((d, i) => {
- out(` ${i + 1}. ${d.brand} · ${d.positioning} · ${d.share?.toFixed(1)}%份额 · 均价$${d.avgPrice?.toFixed(0)} · ★${d.avgRating?.toFixed(2)}`);
- if (d.topProduct) out(` Top产品: ${(d.topProduct.title || '').slice(0, 80)}`);
- if (d.strengths?.length) out(` 优势: ${d.strengths.slice(0, 3).map(s => `${s.label}×${s.n}`).join(' / ')}`);
- if (d.gaps?.length) out(` 缝隙: ${d.gaps.slice(0, 3).map(g => `${g.label}×${g.n}`).join(' / ')}`);
- });
- }
- // 深度产品详情(已有卖点)
- if (A.amazon.detailProducts) {
- out('\n▌Amazon Top 6 深度产品 (成分卖点参考)');
- A.amazon.detailProducts.slice(0, 6).forEach((prod, i) => {
- const title = (prod.title || '').slice(0, 100);
- const price = prod.price ? (prod.price / 100).toFixed(2) : '-';
- out(` ${i + 1}. [${prod.brand || '-'}] ${title}`);
- out(` $${price} · ★${prod.rating || '-'} · ${prod.ratingsCount || prod.reviewCount || 0} 评`);
- });
- }
- // 关键发现
- if (A.amazon.keyFindings) {
- out('\n▌关键发现');
- A.amazon.keyFindings.slice(0, 5).forEach((f, i) => {
- out(` ${i + 1}. ${f.title}`);
- out(` ${(f.body || '').replace(/<[^>]+>/g, '').slice(0, 200)}`);
- });
- }
- // 战略建议
- if (A.amazon.strategy) {
- const s = A.amazon.strategy;
- out('\n▌战略建议 (短/中/长)');
- ['short', 'mid', 'long'].forEach((phase) => {
- if (s[phase]?.items) {
- out(` ${phase.toUpperCase()}:`);
- s[phase].items.forEach((it) => out(` · ${it.replace(/<[^>]+>/g, '').slice(0, 140)}`));
- }
- });
- }
- }
- // === TikTok hashtag 网络 (Amazon 内容端) ===
- if (A.tiktok && !A.tiktok.empty) {
- out('\n▌TikTok 海外内容端');
- if (A.tiktok.topHashtags) {
- out(` Top 20 Hashtag: ${A.tiktok.topHashtags.slice(0, 20).map(h => '#' + (h.tag || h[0])).join(' ')}`);
- }
- if (A.tiktok.topComments) {
- out(' Top 8 海外用户原声:');
- A.tiktok.topComments.slice(0, 8).forEach((c, i) => {
- const text = (c.text || '').replace(/\s+/g, ' ').slice(0, 140);
- out(` ${i + 1}. [${c.digg_count || 0}赞] ${text}`);
- });
- }
- }
- // === TikTok Ads 关键词洞察 ===
- if (A.marketIntel && !A.marketIntel.empty && A.marketIntel.ttAds?.hasData) {
- const mi = A.marketIntel.ttAds;
- out('\n▌TikTok Ads 关键词洞察 (广告主视角)');
- const keys = Object.keys(mi).filter(k => !['hasData'].includes(k));
- out(` (可用字段: ${keys.join(', ')})`);
- if (mi.byProduct) {
- Object.entries(mi.byProduct).forEach(([prod, data]) => {
- out(` 产品: ${prod}`);
- (data.insights || []).forEach((ins) => {
- out(` · keyword="${ins.keyword}" | imp ${ins.impression || 0} | ctr ${ins.ctr ? (ins.ctr*100).toFixed(2)+'%' : '-'} | cvr ${ins.cvr ? (ins.cvr*100).toFixed(2)+'%' : '-'} | post ${ins.post_count || 0} (${ins.post_change ? (ins.post_change>0?'+':'')+(ins.post_change*100).toFixed(0)+'%' : '-'})`);
- });
- });
- }
- }
- // === Douyin 站级热榜基线 ===
- if (A.marketIntel && !A.marketIntel.empty && A.marketIntel.douyin) {
- const dy = A.marketIntel.douyin;
- out('\n▌Douyin 站级基线');
- if (dy.hotWords?.length) out(` 热词: ${dy.hotWords.slice(0,6).map(w => w.word || w[0]).join(' / ')}`);
- if (dy.hotChallenges?.length) out(` 热挑战: ${dy.hotChallenges.slice(0,5).map(c => c.title || c[0]).join(' / ')}`);
- if (dy.categoryFilterHit !== undefined) out(` 品类过滤命中: ${dy.categoryFilterHit} (${dy.categoryFilterHit === 0 ? '品牌蓝海' : '已有内容'})`);
- }
- // === XHS 痛点/亮点/场景 主题(来自 voc-analyze 的 extractThemes)===
- // 主报告里的 themesSection 是基于 xhs comments 做的
- // 让我直接从 voc.xiaohongshu.by_keyword[*].comments 提取
- const xhsComments = [];
- const byKw = voc.xiaohongshu?.by_keyword || {};
- Object.values(byKw).forEach((kwData) => {
- // comments 是 { noteId: [comment...] } 的对象结构
- const cmap = kwData.comments || {};
- if (Array.isArray(cmap)) {
- cmap.forEach((c) => { if (c && c.content) xhsComments.push(c.content); });
- } else {
- Object.values(cmap).forEach((arr) => {
- (arr || []).forEach((c) => { if (c && c.content) xhsComments.push(c.content); });
- });
- }
- });
- if (xhsComments.length) {
- const { extractThemes } = require('./voc-analyze.js');
- if (extractThemes) {
- const xhsThemes = extractThemes(xhsComments);
- if (xhsThemes) {
- out('\n▌小红书中文 VOC 主题');
- ['pain', 'bright', 'scenarios'].forEach((key) => {
- const arr = xhsThemes[key];
- if (arr && arr.length) {
- out(` ${key === 'pain' ? '痛点' : key === 'bright' ? '亮点' : '场景'}:`);
- arr.slice(0, 8).forEach((t, i) => out(` ${i + 1}. ${t.tag || t.label} (${t.count || t.freq || 0})`));
- }
- });
- }
- }
- }
- }
- const outPath = 'data/voc-summary-for-product-dev.txt';
- fs.writeFileSync(outPath, buffer.join('\n'), 'utf-8');
- console.log(`\n✅ ${outPath} (${(fs.statSync(outPath).size / 1024).toFixed(1)}KB)`);
|