collect-market-intel-only.js 4.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. #!/usr/bin/env node
  2. /**
  3. * 只跑 Market Intel:公共 Douyin 热榜 1 份 + 3 产品各自 TT Ads + 品类过滤
  4. * 结果合并到产品 VOC JSON 的 market_intel 字段
  5. */
  6. const fs = require('fs');
  7. const path = require('path');
  8. const {
  9. collectDouyinTrendsOnce,
  10. buildDouyinCategoryFiltered,
  11. collectMarketIntelForProduct,
  12. } = require('./collect-market-intel');
  13. const DATA_DIR = path.join(process.cwd(), 'data');
  14. const SNAPSHOT_PATH = path.join(DATA_DIR, 'douyin-trends-snapshot.json');
  15. const PRODUCTS = [
  16. {
  17. key: 'liver', cn: '江中肝纯片',
  18. keywords_en: ['milk thistle', 'liver cleanse detox'],
  19. douyin_filter_keywords: ['肝', '养肝', '护肝', '解酒', '脂肪肝', '熬夜', '调理肝', '转氨酶'],
  20. },
  21. {
  22. key: 'probiotic', cn: '儿童乳酸菌素片',
  23. keywords_en: ['kids probiotics', 'children probiotics gummy'],
  24. douyin_filter_keywords: ['益生菌', '菌种', '肠道', '便秘', '腹泻', '消化不良', '儿童肠', '宝宝肠', '肠胃', '调理肠'],
  25. },
  26. {
  27. key: 'monkey', cn: '江中猴菇饮',
  28. keywords_en: ['lions mane mushroom supplement', 'lion mane mushroom'],
  29. douyin_filter_keywords: ['猴头菇', '猴菇', '胃', '养胃', '胃病', '胃炎', '胃痛', '胃不好', '胃酸', '老胃病'],
  30. },
  31. ];
  32. async function main() {
  33. console.log('╔═══ Market Intel Collection ═══╗');
  34. const startAt = Date.now();
  35. // 1) 公共 Douyin 热榜快照(共享)
  36. const trends = await collectDouyinTrendsOnce();
  37. fs.writeFileSync(SNAPSHOT_PATH, JSON.stringify(trends, null, 2), 'utf-8');
  38. console.log(`\n✅ Douyin snapshot → ${SNAPSHOT_PATH}`);
  39. console.log(` hot_words=${trends.hot_word_list.length} · web_hot=${trends.web_hot_search.length} · topics=${trends.hot_topic_list.length} · likes=${trends.hot_like_videos.length} · plays=${trends.hot_play_videos.length}`);
  40. // 2) 每产品:TT Ads + 过滤 Douyin 热榜
  41. const summary = [];
  42. for (const p of PRODUCTS) {
  43. const vocFile = path.join(DATA_DIR, `jiangzhong-${p.key}-voc.json`);
  44. if (!fs.existsSync(vocFile)) {
  45. console.log(`\n⚠ 跳过 ${p.key}:${vocFile} 不存在`);
  46. continue;
  47. }
  48. const ttAds = await collectMarketIntelForProduct(p.cn, {
  49. keywords_en: p.keywords_en,
  50. douyin_filter_keywords: p.douyin_filter_keywords,
  51. });
  52. const douyinFiltered = buildDouyinCategoryFiltered(trends, p.douyin_filter_keywords);
  53. // 合并写回 VOC JSON
  54. const voc = JSON.parse(fs.readFileSync(vocFile, 'utf-8'));
  55. voc.market_intel = {
  56. collected_at: new Date().toISOString(),
  57. tt_ads_by_keyword: ttAds.ads_by_keyword,
  58. douyin_filter_keywords: p.douyin_filter_keywords,
  59. douyin_category_snapshot: {
  60. hot_words: douyinFiltered.hot_words.length,
  61. web_hot_search: douyinFiltered.web_hot_search.length,
  62. hot_topics: douyinFiltered.hot_topics.length,
  63. hot_like_videos: douyinFiltered.hot_like_videos.length,
  64. hot_play_videos: douyinFiltered.hot_play_videos.length,
  65. },
  66. douyin_filtered: douyinFiltered,
  67. trends_global_sample: {
  68. hot_word_top5: (trends.hot_word_list || []).slice(0, 5).map((w) => ({ title: w.title, score: w.score })),
  69. web_hot_top5: (trends.web_hot_search || []).slice(0, 5).map((w) => ({ word: w.word, view_count: w.view_count })),
  70. hot_topic_top5: (trends.hot_topic_list || []).slice(0, 5).map((t) => ({ challenge_name: t.challenge_name, play_cnt: t.play_cnt, publish_cnt: t.publish_cnt })),
  71. },
  72. };
  73. fs.writeFileSync(vocFile, JSON.stringify(voc, null, 2), 'utf-8');
  74. const relatedTotal = Object.values(ttAds.ads_by_keyword).reduce((s, x) => s + (x.related_keywords?.length || 0), 0);
  75. const insightsCount = Object.values(ttAds.ads_by_keyword).filter((x) => x.insights).length;
  76. console.log(`\n✅ [${p.key}] market_intel 已并入 VOC JSON`);
  77. console.log(` TT Ads: ${Object.keys(ttAds.ads_by_keyword).length} 关键词 · ${relatedTotal} 相关词 · ${insightsCount} insights`);
  78. console.log(` DY 过滤: ${douyinFiltered.hot_words.length} 热词 · ${douyinFiltered.web_hot_search.length} 热搜 · ${douyinFiltered.hot_topics.length} 话题 · ${douyinFiltered.hot_like_videos.length} 热赞 · ${douyinFiltered.hot_play_videos.length} 热播`);
  79. summary.push({ key: p.key, related: relatedTotal, insights: insightsCount, dy: Object.values(douyinFiltered).reduce((s, a) => s + a.length, 0) });
  80. }
  81. console.log(`\n╚═══ 完成 ${((Date.now() - startAt) / 1000).toFixed(1)}s ═══╝`);
  82. console.log(summary.map((s) => ` ${s.key}: TT related=${s.related} insights=${s.insights} | DY matches=${s.dy}`).join('\n'));
  83. }
  84. main().catch((e) => { console.error(e); process.exit(1); });