| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 |
- #!/usr/bin/env node
- /**
- * 只跑 Market Intel:公共 Douyin 热榜 1 份 + 3 产品各自 TT Ads + 品类过滤
- * 结果合并到产品 VOC JSON 的 market_intel 字段
- */
- const fs = require('fs');
- const path = require('path');
- const {
- collectDouyinTrendsOnce,
- buildDouyinCategoryFiltered,
- collectMarketIntelForProduct,
- } = require('./collect-market-intel');
- const DATA_DIR = path.join(process.cwd(), 'data');
- const SNAPSHOT_PATH = path.join(DATA_DIR, 'douyin-trends-snapshot.json');
- const PRODUCTS = [
- {
- key: 'liver', cn: '江中肝纯片',
- keywords_en: ['milk thistle', 'liver cleanse detox'],
- douyin_filter_keywords: ['肝', '养肝', '护肝', '解酒', '脂肪肝', '熬夜', '调理肝', '转氨酶'],
- },
- {
- key: 'probiotic', cn: '儿童乳酸菌素片',
- keywords_en: ['kids probiotics', 'children probiotics gummy'],
- douyin_filter_keywords: ['益生菌', '菌种', '肠道', '便秘', '腹泻', '消化不良', '儿童肠', '宝宝肠', '肠胃', '调理肠'],
- },
- {
- key: 'monkey', cn: '江中猴菇饮',
- keywords_en: ['lions mane mushroom supplement', 'lion mane mushroom'],
- douyin_filter_keywords: ['猴头菇', '猴菇', '胃', '养胃', '胃病', '胃炎', '胃痛', '胃不好', '胃酸', '老胃病'],
- },
- ];
- async function main() {
- console.log('╔═══ Market Intel Collection ═══╗');
- const startAt = Date.now();
- // 1) 公共 Douyin 热榜快照(共享)
- const trends = await collectDouyinTrendsOnce();
- fs.writeFileSync(SNAPSHOT_PATH, JSON.stringify(trends, null, 2), 'utf-8');
- console.log(`\n✅ Douyin snapshot → ${SNAPSHOT_PATH}`);
- 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}`);
- // 2) 每产品:TT Ads + 过滤 Douyin 热榜
- const summary = [];
- for (const p of PRODUCTS) {
- const vocFile = path.join(DATA_DIR, `jiangzhong-${p.key}-voc.json`);
- if (!fs.existsSync(vocFile)) {
- console.log(`\n⚠ 跳过 ${p.key}:${vocFile} 不存在`);
- continue;
- }
- const ttAds = await collectMarketIntelForProduct(p.cn, {
- keywords_en: p.keywords_en,
- douyin_filter_keywords: p.douyin_filter_keywords,
- });
- const douyinFiltered = buildDouyinCategoryFiltered(trends, p.douyin_filter_keywords);
- // 合并写回 VOC JSON
- const voc = JSON.parse(fs.readFileSync(vocFile, 'utf-8'));
- voc.market_intel = {
- collected_at: new Date().toISOString(),
- tt_ads_by_keyword: ttAds.ads_by_keyword,
- douyin_filter_keywords: p.douyin_filter_keywords,
- douyin_category_snapshot: {
- hot_words: douyinFiltered.hot_words.length,
- web_hot_search: douyinFiltered.web_hot_search.length,
- hot_topics: douyinFiltered.hot_topics.length,
- hot_like_videos: douyinFiltered.hot_like_videos.length,
- hot_play_videos: douyinFiltered.hot_play_videos.length,
- },
- douyin_filtered: douyinFiltered,
- trends_global_sample: {
- hot_word_top5: (trends.hot_word_list || []).slice(0, 5).map((w) => ({ title: w.title, score: w.score })),
- web_hot_top5: (trends.web_hot_search || []).slice(0, 5).map((w) => ({ word: w.word, view_count: w.view_count })),
- 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 })),
- },
- };
- fs.writeFileSync(vocFile, JSON.stringify(voc, null, 2), 'utf-8');
- const relatedTotal = Object.values(ttAds.ads_by_keyword).reduce((s, x) => s + (x.related_keywords?.length || 0), 0);
- const insightsCount = Object.values(ttAds.ads_by_keyword).filter((x) => x.insights).length;
- console.log(`\n✅ [${p.key}] market_intel 已并入 VOC JSON`);
- console.log(` TT Ads: ${Object.keys(ttAds.ads_by_keyword).length} 关键词 · ${relatedTotal} 相关词 · ${insightsCount} insights`);
- 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} 热播`);
- summary.push({ key: p.key, related: relatedTotal, insights: insightsCount, dy: Object.values(douyinFiltered).reduce((s, a) => s + a.length, 0) });
- }
- console.log(`\n╚═══ 完成 ${((Date.now() - startAt) / 1000).toFixed(1)}s ═══╝`);
- console.log(summary.map((s) => ` ${s.key}: TT related=${s.related} insights=${s.insights} | DY matches=${s.dy}`).join('\n'));
- }
- main().catch((e) => { console.error(e); process.exit(1); });
|