content-genie-mcp 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -14
- package/dist/index.js +639 -65
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
# Content Genie MCP v2.
|
|
1
|
+
# Content Genie MCP v2.6
|
|
2
2
|
|
|
3
|
-
> 한국 콘텐츠 크리에이터를 위한 올인원 AI 콘텐츠 어시스턴트
|
|
3
|
+
> 한국 콘텐츠 크리에이터를 위한 올인원 AI 콘텐츠 어시스턴트 (프로 버전)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/content-genie-mcp)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
Content Genie MCP는 블로거, 유튜버, 인스타그래머, 마케터를 위한 **
|
|
8
|
+
Content Genie MCP는 블로거, 유튜버, 인스타그래머, 마케터를 위한 **17가지 강력한 도구**를 제공하는 MCP 서버입니다. 한국 시장에 특화된 트렌드 분석, 콘텐츠 아이디어 생성, SEO 최적화, 바이럴 예측, 인플루언서 협업 분석 기능을 제공합니다.
|
|
9
9
|
|
|
10
|
-
## v2.
|
|
10
|
+
## v2.6 New Features (Latest)
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- **실시간 스크래핑 강화** - 하드코딩 제거, 실제 데이터 수집
|
|
13
|
+
- **Google Trends RSS 피드** 실시간 연동
|
|
14
|
+
- **네이버/다음 뉴스** 실시간 스크래핑 + 감성 분석
|
|
15
|
+
- **Zum 실시간 검색어** 스크래핑
|
|
16
|
+
- **키워드 자동 카테고리 분류** (12개 카테고리)
|
|
17
|
+
- **뉴스 키워드 빈도 분석** 기능 추가
|
|
18
|
+
|
|
19
|
+
## v2.5 Features
|
|
20
|
+
|
|
21
|
+
- **17개 도구**로 확장 (기존 12개 → 17개)
|
|
13
22
|
- 실시간 네이버/다음/구글/유튜브 트렌드 스크래핑
|
|
14
|
-
-
|
|
23
|
+
- **100+ 한국 기념일/이벤트 DB** 내장
|
|
15
24
|
- 고급 바이럴 점수 예측 알고리즘
|
|
16
|
-
-
|
|
17
|
-
-
|
|
25
|
+
- **썸네일 분석** 및 CTR 최적화
|
|
26
|
+
- **스크립트/대본 아웃라인** 자동 생성
|
|
27
|
+
- **콘텐츠 리퍼포징** 전략 (1개 → 7개 플랫폼)
|
|
28
|
+
- **인플루언서 협업** 분석
|
|
29
|
+
- **콘텐츠 성과 예측** AI
|
|
18
30
|
|
|
19
|
-
##
|
|
31
|
+
## 17가지 핵심 도구
|
|
20
32
|
|
|
21
33
|
| # | 도구 | 설명 |
|
|
22
34
|
|---|------|------|
|
|
@@ -32,6 +44,11 @@ Content Genie MCP는 블로거, 유튜버, 인스타그래머, 마케터를 위
|
|
|
32
44
|
| 10 | `benchmark_content_performance` | 업계별 성과 벤치마크 |
|
|
33
45
|
| 11 | `generate_ab_test_variants` | A/B 테스트 변형 자동 생성 |
|
|
34
46
|
| 12 | `get_seasonal_content_guide` | 시즌/이벤트 콘텐츠 가이드 |
|
|
47
|
+
| 13 | `analyze_thumbnail` | 썸네일 분석 + CTR 최적화 |
|
|
48
|
+
| 14 | `generate_script_outline` | 스크립트/대본 아웃라인 생성 |
|
|
49
|
+
| 15 | `repurpose_content` | 콘텐츠 리퍼포징 전략 |
|
|
50
|
+
| 16 | `analyze_influencer_collab` | 인플루언서 협업 분석 |
|
|
51
|
+
| 17 | `predict_content_performance` | 콘텐츠 성과 예측 AI |
|
|
35
52
|
|
|
36
53
|
## 설치 및 사용법
|
|
37
54
|
|
|
@@ -277,12 +294,108 @@ Returns:
|
|
|
277
294
|
- 해시태그 제안
|
|
278
295
|
```
|
|
279
296
|
|
|
280
|
-
## 한국 기념일 DB (
|
|
297
|
+
## 한국 기념일 DB (100+)
|
|
298
|
+
|
|
299
|
+
- **공휴일 (18개)**: 새해, 설날 연휴, 삼일절, 어린이날, 부처님오신날, 현충일, 광복절, 추석 연휴, 개천절, 한글날, 크리스마스 등
|
|
300
|
+
- **14일 데이 시리즈 (12개)**: 발렌타인데이, 화이트데이, 블랙데이, 로즈데이, 키스데이, 빼빼로데이 등
|
|
301
|
+
- **전통 절기 (15개)**: 정월대보름, 입춘, 경칩, 하지, 초복/중복/말복, 동지 등
|
|
302
|
+
- **글로벌/상업 이벤트 (15개)**: 할로윈, 블랙프라이데이, 사이버먼데이, 지구의날 등
|
|
303
|
+
- **학교/입시 관련 (10개)**: 개학, 수능, 졸업시즌, 방학 등
|
|
304
|
+
- **쇼핑 시즌 (10개)**: 신년 세일, 여름 세일, 가을 신상, 연말 세일 등
|
|
305
|
+
- **시즌/날씨 관련 (12개)**: 벚꽃 시즌, 장마, 폭염, 단풍, 김장철 등
|
|
306
|
+
- **크리에이터 특화 (8개)**: 연간 콘텐츠 기획, 알고리즘 시즌, 연말결산 등
|
|
281
307
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
308
|
+
## v2.5 신규 도구 상세
|
|
309
|
+
|
|
310
|
+
### 13. analyze_thumbnail
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
YouTube/Instagram 썸네일 컨셉을 분석하고 개선점을 제안합니다.
|
|
314
|
+
|
|
315
|
+
Parameters:
|
|
316
|
+
- title: 콘텐츠 제목
|
|
317
|
+
- thumbnail_description: 썸네일 설명 (예: 놀란 표정의 사람, 음식 클로즈업)
|
|
318
|
+
- platform: "youtube" | "instagram" | "tiktok" | "blog"
|
|
319
|
+
- content_category: 카테고리 (예: 먹방, 뷰티, 테크)
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
- 썸네일 점수 (0-100)
|
|
323
|
+
- 감지된 요소 (얼굴, 텍스트, 색상 등)
|
|
324
|
+
- 개선점 및 플랫폼별 베스트 프랙티스
|
|
325
|
+
- CTR 예측
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 14. generate_script_outline
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
유튜브, 팟캐스트, 릴스용 스크립트 아웃라인을 자동 생성합니다.
|
|
332
|
+
|
|
333
|
+
Parameters:
|
|
334
|
+
- topic: 콘텐츠 주제
|
|
335
|
+
- format: "youtube_long" | "youtube_short" | "podcast" | "reels" | "tiktok" | "live"
|
|
336
|
+
- duration: 예상 길이 (예: 10분, 30초)
|
|
337
|
+
- style: "educational" | "entertainment" | "storytelling" | "review" | "tutorial"
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
- 섹션별 아웃라인 (인트로, 훅, 본문, CTA, 아웃트로)
|
|
341
|
+
- 각 섹션별 스크립트 템플릿
|
|
342
|
+
- 오프닝 훅 예시 5개
|
|
343
|
+
- 촬영/편집 팁
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 15. repurpose_content
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
하나의 콘텐츠를 여러 플랫폼용으로 변환하는 전략을 제안합니다.
|
|
350
|
+
|
|
351
|
+
Parameters:
|
|
352
|
+
- original_content: 원본 콘텐츠 (제목 또는 설명)
|
|
353
|
+
- source_platform: "youtube" | "blog" | "podcast" | "instagram" | "newsletter"
|
|
354
|
+
- target_platforms: ["youtube_shorts", "instagram_reels", "tiktok", "blog", "twitter", "threads", "linkedin"]
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
- 플랫폼별 변환 전략
|
|
358
|
+
- 예상 소요 시간
|
|
359
|
+
- 우선순위 점수
|
|
360
|
+
- 제목 자동 변환
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### 16. analyze_influencer_collab
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
인플루언서 협업 전략 및 적합도를 분석합니다.
|
|
367
|
+
|
|
368
|
+
Parameters:
|
|
369
|
+
- brand_category: 브랜드/제품 카테고리
|
|
370
|
+
- target_audience: 타겟 오디언스
|
|
371
|
+
- budget_range: "low" | "medium" | "high" | "premium"
|
|
372
|
+
- campaign_goal: "awareness" | "engagement" | "conversion" | "content"
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
- 추천 인플루언서 티어 (나노~메가)
|
|
376
|
+
- 예상 비용 및 참여율
|
|
377
|
+
- 협업 유형 추천
|
|
378
|
+
- 협상 팁 및 주의사항
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 17. predict_content_performance
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
콘텐츠의 예상 성과를 AI 기반으로 예측합니다.
|
|
385
|
+
|
|
386
|
+
Parameters:
|
|
387
|
+
- title: 콘텐츠 제목
|
|
388
|
+
- description: 콘텐츠 설명
|
|
389
|
+
- platform: 플랫폼
|
|
390
|
+
- posting_time: 게시 예정 시간
|
|
391
|
+
- has_trending_topic: 트렌딩 주제 포함 여부
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
- 성과 점수 (0-100)
|
|
395
|
+
- 등급 (A/B/C/D)
|
|
396
|
+
- 예상 조회수/참여율/공유수
|
|
397
|
+
- 최적화 제안
|
|
398
|
+
```
|
|
286
399
|
|
|
287
400
|
## 타겟 사용자
|
|
288
401
|
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import * as cheerio from "cheerio";
|
|
|
9
9
|
// =============================================================================
|
|
10
10
|
const server = new McpServer({
|
|
11
11
|
name: "content-genie-mcp",
|
|
12
|
-
version: "2.
|
|
12
|
+
version: "2.6.0",
|
|
13
13
|
});
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// 공통 스키마
|
|
@@ -469,6 +469,35 @@ server.tool("get_seasonal_content_guide", "다가오는 시즌/이벤트에 맞
|
|
|
469
469
|
// =============================================================================
|
|
470
470
|
// Helper Functions - 고도화
|
|
471
471
|
// =============================================================================
|
|
472
|
+
// 키워드 카테고리 자동 분류
|
|
473
|
+
function categorizeKeyword(keyword) {
|
|
474
|
+
const text = keyword.toLowerCase();
|
|
475
|
+
if (/ai|gpt|인공지능|기술|테크|앱|소프트웨어|코딩|개발/.test(text))
|
|
476
|
+
return "tech";
|
|
477
|
+
if (/주식|코인|비트코인|투자|금리|경제|재테크|부동산|환율/.test(text))
|
|
478
|
+
return "finance";
|
|
479
|
+
if (/운동|헬스|다이어트|건강|병원|의료|영양/.test(text))
|
|
480
|
+
return "health";
|
|
481
|
+
if (/맛집|음식|요리|레시피|카페|먹방|배달/.test(text))
|
|
482
|
+
return "food";
|
|
483
|
+
if (/여행|호텔|관광|항공|휴가|리조트/.test(text))
|
|
484
|
+
return "travel";
|
|
485
|
+
if (/드라마|영화|연예|아이돌|방송|예능|넷플릭스|kpop/.test(text))
|
|
486
|
+
return "entertainment";
|
|
487
|
+
if (/게임|롤|배그|스팀|플스|닌텐도/.test(text))
|
|
488
|
+
return "gaming";
|
|
489
|
+
if (/뷰티|화장품|스킨케어|메이크업|패션|옷/.test(text))
|
|
490
|
+
return "beauty";
|
|
491
|
+
if (/축구|야구|농구|스포츠|올림픽|월드컵/.test(text))
|
|
492
|
+
return "sports";
|
|
493
|
+
if (/교육|공부|학교|시험|자격증|취업/.test(text))
|
|
494
|
+
return "education";
|
|
495
|
+
if (/쇼핑|할인|세일|구매|가격/.test(text))
|
|
496
|
+
return "shopping";
|
|
497
|
+
if (/뉴스|정치|사회|이슈/.test(text))
|
|
498
|
+
return "news";
|
|
499
|
+
return "general";
|
|
500
|
+
}
|
|
472
501
|
// 네이버 트렌드 스크래핑
|
|
473
502
|
async function scrapeNaverTrends() {
|
|
474
503
|
try {
|
|
@@ -572,38 +601,391 @@ function getDaumFallbackTrends() {
|
|
|
572
601
|
{ keyword: "날씨 정보", platform: "daum", rank: 5, category: "general" },
|
|
573
602
|
];
|
|
574
603
|
}
|
|
575
|
-
// 구글 트렌드 코리아
|
|
604
|
+
// 구글 트렌드 코리아 - 실제 RSS 피드 스크래핑
|
|
576
605
|
async function scrapeGoogleTrendsKorea() {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
606
|
+
try {
|
|
607
|
+
// Google Trends Daily RSS Feed for Korea
|
|
608
|
+
const response = await axios.get('https://trends.google.com/trending/rss?geo=KR', {
|
|
609
|
+
headers: {
|
|
610
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
611
|
+
'Accept': 'application/rss+xml, application/xml, text/xml'
|
|
612
|
+
},
|
|
613
|
+
timeout: 8000,
|
|
614
|
+
});
|
|
615
|
+
const $ = cheerio.load(response.data, { xmlMode: true });
|
|
616
|
+
const trends = [];
|
|
617
|
+
$('item').each((i, el) => {
|
|
618
|
+
if (i >= 15)
|
|
619
|
+
return false; // 최대 15개
|
|
620
|
+
const title = $(el).find('title').text().trim();
|
|
621
|
+
const traffic = $(el).find('ht\\:approx_traffic, approx_traffic').text().trim();
|
|
622
|
+
const newsItem = $(el).find('ht\\:news_item_title, news_item_title').first().text().trim();
|
|
623
|
+
if (title) {
|
|
624
|
+
trends.push({
|
|
625
|
+
keyword: title,
|
|
626
|
+
platform: "google",
|
|
627
|
+
rank: i + 1,
|
|
628
|
+
category: categorizeKeyword(title),
|
|
629
|
+
trend: "rising",
|
|
630
|
+
traffic: traffic || "10K+",
|
|
631
|
+
related_news: newsItem || null,
|
|
632
|
+
source: "google_trends_rss"
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
if (trends.length > 0) {
|
|
637
|
+
return trends;
|
|
638
|
+
}
|
|
639
|
+
// Fallback: 실시간 검색 트렌드 페이지 스크래핑 시도
|
|
640
|
+
return await scrapeGoogleTrendsFallback();
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
return await scrapeGoogleTrendsFallback();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
// Google Trends Fallback - 트렌드 페이지 스크래핑
|
|
647
|
+
async function scrapeGoogleTrendsFallback() {
|
|
648
|
+
try {
|
|
649
|
+
const response = await axios.get('https://trends.google.co.kr/trends/trendingsearches/daily?geo=KR', {
|
|
650
|
+
headers: {
|
|
651
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
652
|
+
'Accept-Language': 'ko-KR,ko;q=0.9'
|
|
653
|
+
},
|
|
654
|
+
timeout: 8000,
|
|
655
|
+
});
|
|
656
|
+
const $ = cheerio.load(response.data);
|
|
657
|
+
const trends = [];
|
|
658
|
+
// 다양한 셀렉터 시도
|
|
659
|
+
$('[class*="feed-item"], [class*="trending"], .title').each((i, el) => {
|
|
660
|
+
if (i >= 10)
|
|
661
|
+
return false;
|
|
662
|
+
const keyword = $(el).text().trim();
|
|
663
|
+
if (keyword && keyword.length > 1 && keyword.length < 50) {
|
|
664
|
+
trends.push({
|
|
665
|
+
keyword,
|
|
666
|
+
platform: "google",
|
|
667
|
+
rank: i + 1,
|
|
668
|
+
category: categorizeKeyword(keyword),
|
|
669
|
+
trend: "rising",
|
|
670
|
+
source: "google_trends_page"
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
if (trends.length > 0)
|
|
675
|
+
return trends;
|
|
676
|
+
// 최종 Fallback: 시간대별 동적 데이터
|
|
677
|
+
return getGoogleFallbackTrends();
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
return getGoogleFallbackTrends();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function getGoogleFallbackTrends() {
|
|
684
|
+
const hour = new Date().getHours();
|
|
685
|
+
const dayOfWeek = new Date().getDay();
|
|
686
|
+
const baseTrends = [
|
|
687
|
+
{ keyword: "AI 활용법", category: "tech" },
|
|
688
|
+
{ keyword: "ChatGPT 팁", category: "tech" },
|
|
689
|
+
{ keyword: "주식 시세", category: "finance" },
|
|
690
|
+
{ keyword: "비트코인", category: "finance" },
|
|
691
|
+
{ keyword: "다이어트", category: "health" },
|
|
585
692
|
];
|
|
693
|
+
// 시간대별 추가 트렌드
|
|
694
|
+
const timeTrends = hour >= 9 && hour <= 18
|
|
695
|
+
? [{ keyword: "점심 메뉴", category: "food" }, { keyword: "카페 추천", category: "food" }]
|
|
696
|
+
: [{ keyword: "넷플릭스 추천", category: "entertainment" }, { keyword: "게임 추천", category: "entertainment" }];
|
|
697
|
+
// 요일별 추가 트렌드
|
|
698
|
+
const dayTrends = dayOfWeek === 0 || dayOfWeek === 6
|
|
699
|
+
? [{ keyword: "주말 나들이", category: "travel" }, { keyword: "브런치 맛집", category: "food" }]
|
|
700
|
+
: [{ keyword: "재택근무 팁", category: "work" }, { keyword: "퇴근 후 취미", category: "lifestyle" }];
|
|
701
|
+
return [...baseTrends, ...timeTrends, ...dayTrends].map((item, i) => ({
|
|
702
|
+
...item,
|
|
703
|
+
platform: "google",
|
|
704
|
+
rank: i + 1,
|
|
705
|
+
trend: "stable",
|
|
706
|
+
source: "fallback_dynamic"
|
|
707
|
+
}));
|
|
586
708
|
}
|
|
587
|
-
// 유튜브 트렌드 코리아
|
|
709
|
+
// 유튜브 트렌드 코리아 - 실제 스크래핑
|
|
588
710
|
async function scrapeYoutubeTrendsKorea() {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
711
|
+
try {
|
|
712
|
+
// YouTube Korea Trending 페이지
|
|
713
|
+
const response = await axios.get('https://www.youtube.com/feed/trending?gl=KR&hl=ko', {
|
|
714
|
+
headers: {
|
|
715
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
716
|
+
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
717
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
718
|
+
},
|
|
719
|
+
timeout: 10000,
|
|
720
|
+
});
|
|
721
|
+
const trends = [];
|
|
722
|
+
const html = response.data;
|
|
723
|
+
// YouTube 초기 데이터 JSON 추출
|
|
724
|
+
const ytInitialDataMatch = html.match(/var ytInitialData = ({.*?});<\/script>/s);
|
|
725
|
+
if (ytInitialDataMatch) {
|
|
726
|
+
try {
|
|
727
|
+
const data = JSON.parse(ytInitialDataMatch[1]);
|
|
728
|
+
const tabs = data?.contents?.twoColumnBrowseResultsRenderer?.tabs || [];
|
|
729
|
+
for (const tab of tabs) {
|
|
730
|
+
const contents = tab?.tabRenderer?.content?.sectionListRenderer?.contents || [];
|
|
731
|
+
for (const section of contents) {
|
|
732
|
+
const items = section?.itemSectionRenderer?.contents || [];
|
|
733
|
+
for (const item of items) {
|
|
734
|
+
const video = item?.videoRenderer;
|
|
735
|
+
if (video && trends.length < 15) {
|
|
736
|
+
const title = video?.title?.runs?.[0]?.text || video?.title?.simpleText || '';
|
|
737
|
+
const viewCount = video?.viewCountText?.simpleText || video?.shortViewCountText?.simpleText || '';
|
|
738
|
+
const channel = video?.ownerText?.runs?.[0]?.text || '';
|
|
739
|
+
if (title) {
|
|
740
|
+
trends.push({
|
|
741
|
+
keyword: extractKeywordFromTitle(title),
|
|
742
|
+
title: title,
|
|
743
|
+
platform: "youtube",
|
|
744
|
+
rank: trends.length + 1,
|
|
745
|
+
category: categorizeYouTubeContent(title, channel),
|
|
746
|
+
views: viewCount,
|
|
747
|
+
channel: channel,
|
|
748
|
+
format: detectVideoFormat(title),
|
|
749
|
+
source: "youtube_trending"
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
catch (parseError) {
|
|
758
|
+
// JSON 파싱 실패 시 fallback
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// HTML 파싱 시도 (JSON 실패 시)
|
|
762
|
+
if (trends.length === 0) {
|
|
763
|
+
const $ = cheerio.load(html);
|
|
764
|
+
$('a#video-title').each((i, el) => {
|
|
765
|
+
if (i >= 15)
|
|
766
|
+
return false;
|
|
767
|
+
const title = $(el).text().trim();
|
|
768
|
+
if (title) {
|
|
769
|
+
trends.push({
|
|
770
|
+
keyword: extractKeywordFromTitle(title),
|
|
771
|
+
title: title,
|
|
772
|
+
platform: "youtube",
|
|
773
|
+
rank: i + 1,
|
|
774
|
+
category: categorizeYouTubeContent(title, ''),
|
|
775
|
+
format: detectVideoFormat(title),
|
|
776
|
+
source: "youtube_html"
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
if (trends.length > 0)
|
|
782
|
+
return trends;
|
|
783
|
+
return getYoutubeFallbackTrends();
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
return getYoutubeFallbackTrends();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// YouTube 제목에서 키워드 추출
|
|
790
|
+
function extractKeywordFromTitle(title) {
|
|
791
|
+
// 대괄호, 괄호 내용 제거
|
|
792
|
+
let keyword = title.replace(/[\[\(【].*?[\]\)】]/g, '').trim();
|
|
793
|
+
// 특수문자 제거
|
|
794
|
+
keyword = keyword.replace(/[^\w\sㄱ-ㅎㅏ-ㅣ가-힣]/g, ' ').trim();
|
|
795
|
+
// 너무 길면 자르기
|
|
796
|
+
if (keyword.length > 30) {
|
|
797
|
+
keyword = keyword.substring(0, 30) + '...';
|
|
798
|
+
}
|
|
799
|
+
return keyword || title.substring(0, 30);
|
|
800
|
+
}
|
|
801
|
+
// YouTube 콘텐츠 카테고리 분류
|
|
802
|
+
function categorizeYouTubeContent(title, channel) {
|
|
803
|
+
const text = (title + ' ' + channel).toLowerCase();
|
|
804
|
+
if (/먹방|mukbang|음식|요리|레시피|맛집/.test(text))
|
|
805
|
+
return "food";
|
|
806
|
+
if (/게임|gaming|롤|lol|배그|minecraft/.test(text))
|
|
807
|
+
return "gaming";
|
|
808
|
+
if (/뷰티|메이크업|화장|스킨케어|뷰스타/.test(text))
|
|
809
|
+
return "beauty";
|
|
810
|
+
if (/운동|헬스|다이어트|fitness|workout/.test(text))
|
|
811
|
+
return "fitness";
|
|
812
|
+
if (/브이로그|vlog|일상/.test(text))
|
|
813
|
+
return "lifestyle";
|
|
814
|
+
if (/여행|travel|trip/.test(text))
|
|
815
|
+
return "travel";
|
|
816
|
+
if (/음악|노래|커버|music|mv/.test(text))
|
|
817
|
+
return "music";
|
|
818
|
+
if (/드라마|예능|영화|movie/.test(text))
|
|
819
|
+
return "entertainment";
|
|
820
|
+
if (/공부|강의|교육|tutorial/.test(text))
|
|
821
|
+
return "education";
|
|
822
|
+
if (/테크|리뷰|tech|unboxing/.test(text))
|
|
823
|
+
return "tech";
|
|
824
|
+
if (/뉴스|이슈|news/.test(text))
|
|
825
|
+
return "news";
|
|
826
|
+
if (/shorts|쇼츠|숏/.test(text))
|
|
827
|
+
return "shorts";
|
|
828
|
+
return "general";
|
|
829
|
+
}
|
|
830
|
+
// 영상 포맷 감지
|
|
831
|
+
function detectVideoFormat(title) {
|
|
832
|
+
const text = title.toLowerCase();
|
|
833
|
+
if (/shorts|쇼츠/.test(text))
|
|
834
|
+
return "shorts";
|
|
835
|
+
if (/vlog|브이로그|일상/.test(text))
|
|
836
|
+
return "vlog";
|
|
837
|
+
if (/먹방|mukbang/.test(text))
|
|
838
|
+
return "mukbang";
|
|
839
|
+
if (/asmr/.test(text))
|
|
840
|
+
return "asmr";
|
|
841
|
+
if (/리뷰|review|언박싱|unboxing/.test(text))
|
|
842
|
+
return "review";
|
|
843
|
+
if (/튜토리얼|tutorial|강의|하는 법/.test(text))
|
|
844
|
+
return "tutorial";
|
|
845
|
+
if (/live|라이브/.test(text))
|
|
846
|
+
return "live";
|
|
847
|
+
if (/mv|뮤비|music video/.test(text))
|
|
848
|
+
return "music_video";
|
|
849
|
+
return "standard";
|
|
850
|
+
}
|
|
851
|
+
function getYoutubeFallbackTrends() {
|
|
852
|
+
const hour = new Date().getHours();
|
|
853
|
+
// 시간대별 인기 카테고리
|
|
854
|
+
const popularCategories = hour >= 18 || hour <= 2
|
|
855
|
+
? ["entertainment", "gaming", "music"] // 저녁/밤
|
|
856
|
+
: hour >= 6 && hour <= 9
|
|
857
|
+
? ["news", "education", "lifestyle"] // 아침
|
|
858
|
+
: ["food", "lifestyle", "tech"]; // 낮
|
|
859
|
+
const trends = [
|
|
860
|
+
{ keyword: "유튜브 인기 급상승", category: popularCategories[0], format: "trending" },
|
|
861
|
+
{ keyword: "쇼츠 챌린지", category: "shorts", format: "shorts", views: "500K+" },
|
|
862
|
+
{ keyword: "브이로그", category: "lifestyle", format: "vlog", views: "300K+" },
|
|
863
|
+
{ keyword: "먹방 ASMR", category: "food", format: "mukbang", views: "400K+" },
|
|
864
|
+
{ keyword: "게임 실황", category: "gaming", format: "streaming", views: "350K+" },
|
|
865
|
+
{ keyword: "K-POP 커버", category: "music", format: "cover", views: "600K+" },
|
|
866
|
+
{ keyword: "메이크업 튜토리얼", category: "beauty", format: "tutorial", views: "250K+" },
|
|
867
|
+
{ keyword: "IT 리뷰", category: "tech", format: "review", views: "200K+" },
|
|
598
868
|
];
|
|
869
|
+
return trends.map((item, i) => ({
|
|
870
|
+
...item,
|
|
871
|
+
platform: "youtube",
|
|
872
|
+
rank: i + 1,
|
|
873
|
+
source: "fallback_dynamic"
|
|
874
|
+
}));
|
|
599
875
|
}
|
|
600
|
-
// 줌 트렌드
|
|
876
|
+
// 줌 트렌드 - 실제 스크래핑
|
|
601
877
|
async function scrapeZumTrends() {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
878
|
+
try {
|
|
879
|
+
// Zum 메인 페이지 실시간 검색어
|
|
880
|
+
const response = await axios.get('https://zum.com/', {
|
|
881
|
+
headers: {
|
|
882
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
883
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
884
|
+
},
|
|
885
|
+
timeout: 8000,
|
|
886
|
+
});
|
|
887
|
+
const $ = cheerio.load(response.data);
|
|
888
|
+
const trends = [];
|
|
889
|
+
// 줌 실시간 검색어 셀렉터들
|
|
890
|
+
const selectors = [
|
|
891
|
+
'.realtime_keyword_list li',
|
|
892
|
+
'.issue_keyword li',
|
|
893
|
+
'[class*="ranking"] li',
|
|
894
|
+
'[class*="keyword"] a',
|
|
895
|
+
'.hot_keyword li'
|
|
896
|
+
];
|
|
897
|
+
for (const selector of selectors) {
|
|
898
|
+
if (trends.length >= 10)
|
|
899
|
+
break;
|
|
900
|
+
$(selector).each((i, el) => {
|
|
901
|
+
if (trends.length >= 10)
|
|
902
|
+
return false;
|
|
903
|
+
const keyword = $(el).text().trim()
|
|
904
|
+
.replace(/^\d+\.?\s*/, '') // 순위 번호 제거
|
|
905
|
+
.replace(/new|↑|↓|─/gi, '') // 변동 표시 제거
|
|
906
|
+
.trim();
|
|
907
|
+
if (keyword && keyword.length > 1 && keyword.length < 30 && !trends.find(t => t.keyword === keyword)) {
|
|
908
|
+
trends.push({
|
|
909
|
+
keyword,
|
|
910
|
+
platform: "zum",
|
|
911
|
+
rank: trends.length + 1,
|
|
912
|
+
category: categorizeKeyword(keyword),
|
|
913
|
+
source: "zum_realtime"
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
if (trends.length > 0)
|
|
919
|
+
return trends;
|
|
920
|
+
// Zum 뉴스 섹션 시도
|
|
921
|
+
return await scrapeZumNewsTrends();
|
|
922
|
+
}
|
|
923
|
+
catch (error) {
|
|
924
|
+
return await scrapeZumNewsTrends();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
async function scrapeZumNewsTrends() {
|
|
928
|
+
try {
|
|
929
|
+
const response = await axios.get('https://news.zum.com/', {
|
|
930
|
+
headers: {
|
|
931
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
932
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
933
|
+
},
|
|
934
|
+
timeout: 8000,
|
|
935
|
+
});
|
|
936
|
+
const $ = cheerio.load(response.data);
|
|
937
|
+
const trends = [];
|
|
938
|
+
// 뉴스 헤드라인에서 키워드 추출
|
|
939
|
+
$('h2, h3, .headline, .title, [class*="news_title"]').each((i, el) => {
|
|
940
|
+
if (trends.length >= 10)
|
|
941
|
+
return false;
|
|
942
|
+
const text = $(el).text().trim();
|
|
943
|
+
if (text && text.length > 5 && text.length < 50) {
|
|
944
|
+
const keyword = extractNewsKeyword(text);
|
|
945
|
+
if (keyword && !trends.find(t => t.keyword === keyword)) {
|
|
946
|
+
trends.push({
|
|
947
|
+
keyword,
|
|
948
|
+
platform: "zum",
|
|
949
|
+
rank: trends.length + 1,
|
|
950
|
+
category: categorizeKeyword(keyword),
|
|
951
|
+
source: "zum_news"
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
if (trends.length > 0)
|
|
957
|
+
return trends;
|
|
958
|
+
return getZumFallbackTrends();
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
return getZumFallbackTrends();
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
function extractNewsKeyword(headline) {
|
|
965
|
+
// 헤드라인에서 핵심 키워드 추출
|
|
966
|
+
const words = headline.split(/[\s,\.…]+/).filter(w => w.length >= 2 && w.length <= 10);
|
|
967
|
+
return words.slice(0, 3).join(' ') || headline.substring(0, 20);
|
|
968
|
+
}
|
|
969
|
+
function getZumFallbackTrends() {
|
|
970
|
+
const hour = new Date().getHours();
|
|
971
|
+
const baseKeywords = [
|
|
972
|
+
{ keyword: "오늘의 뉴스", category: "news" },
|
|
973
|
+
{ keyword: "실시간 이슈", category: "general" },
|
|
974
|
+
{ keyword: "연예 소식", category: "entertainment" },
|
|
975
|
+
{ keyword: "스포츠 결과", category: "sports" },
|
|
976
|
+
{ keyword: "경제 동향", category: "finance" },
|
|
606
977
|
];
|
|
978
|
+
const timeBased = hour >= 7 && hour <= 9
|
|
979
|
+
? [{ keyword: "아침 뉴스", category: "news" }]
|
|
980
|
+
: hour >= 12 && hour <= 14
|
|
981
|
+
? [{ keyword: "점심 이슈", category: "general" }]
|
|
982
|
+
: [{ keyword: "저녁 뉴스", category: "news" }];
|
|
983
|
+
return [...baseKeywords, ...timeBased].map((item, i) => ({
|
|
984
|
+
...item,
|
|
985
|
+
platform: "zum",
|
|
986
|
+
rank: i + 1,
|
|
987
|
+
source: "fallback"
|
|
988
|
+
}));
|
|
607
989
|
}
|
|
608
990
|
// 고급 트렌드 인사이트 생성
|
|
609
991
|
function generateAdvancedTrendInsights(trends) {
|
|
@@ -1374,57 +1756,249 @@ function predictAdvancedViralScore(title, description, platform, hashtags, conte
|
|
|
1374
1756
|
].filter(Boolean).slice(0, 3),
|
|
1375
1757
|
};
|
|
1376
1758
|
}
|
|
1377
|
-
// 뉴스 분석
|
|
1759
|
+
// 뉴스 분석 - 실제 스크래핑
|
|
1378
1760
|
async function analyzeKoreanNews(category, timeRange, extractKeywords) {
|
|
1379
|
-
|
|
1380
|
-
const
|
|
1761
|
+
const news = [];
|
|
1762
|
+
const allKeywords = [];
|
|
1763
|
+
// 카테고리별 네이버 뉴스 섹션 URL
|
|
1764
|
+
const categoryUrls = {
|
|
1765
|
+
general: 'https://news.naver.com/',
|
|
1766
|
+
politics: 'https://news.naver.com/section/100',
|
|
1767
|
+
economy: 'https://news.naver.com/section/101',
|
|
1768
|
+
society: 'https://news.naver.com/section/102',
|
|
1769
|
+
culture: 'https://news.naver.com/section/103',
|
|
1770
|
+
tech: 'https://news.naver.com/section/105',
|
|
1771
|
+
sports: 'https://sports.news.naver.com/',
|
|
1772
|
+
entertainment: 'https://entertain.naver.com/home',
|
|
1773
|
+
};
|
|
1774
|
+
const url = categoryUrls[category] || categoryUrls.general;
|
|
1775
|
+
try {
|
|
1776
|
+
// 네이버 뉴스 스크래핑
|
|
1777
|
+
const response = await axios.get(url, {
|
|
1778
|
+
headers: {
|
|
1779
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
1780
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
1781
|
+
},
|
|
1782
|
+
timeout: 10000,
|
|
1783
|
+
});
|
|
1784
|
+
const $ = cheerio.load(response.data);
|
|
1785
|
+
// 헤드라인 추출 - 다양한 셀렉터 시도
|
|
1786
|
+
const headlineSelectors = [
|
|
1787
|
+
'.cjs_t', // 네이버 뉴스 메인
|
|
1788
|
+
'.sa_text_title',
|
|
1789
|
+
'a.news_tit',
|
|
1790
|
+
'.cluster_text_headline',
|
|
1791
|
+
'.cluster_head_topic',
|
|
1792
|
+
'h2.tit',
|
|
1793
|
+
'.link_news',
|
|
1794
|
+
'[class*="headline"] a',
|
|
1795
|
+
'[class*="title"] a',
|
|
1796
|
+
];
|
|
1797
|
+
for (const selector of headlineSelectors) {
|
|
1798
|
+
if (news.length >= 15)
|
|
1799
|
+
break;
|
|
1800
|
+
$(selector).each((i, el) => {
|
|
1801
|
+
if (news.length >= 15)
|
|
1802
|
+
return false;
|
|
1803
|
+
const headline = $(el).text().trim();
|
|
1804
|
+
const href = $(el).attr('href') || '';
|
|
1805
|
+
if (headline && headline.length > 10 && headline.length < 100) {
|
|
1806
|
+
// 중복 체크
|
|
1807
|
+
if (!news.find(n => n.headline === headline)) {
|
|
1808
|
+
const sentiment = analyzeSentiment(headline);
|
|
1809
|
+
news.push({
|
|
1810
|
+
headline,
|
|
1811
|
+
source: detectNewsSource(href, category),
|
|
1812
|
+
sentiment,
|
|
1813
|
+
url: href.startsWith('http') ? href : `https://news.naver.com${href}`,
|
|
1814
|
+
});
|
|
1815
|
+
// 키워드 추출
|
|
1816
|
+
if (extractKeywords) {
|
|
1817
|
+
const words = extractKeywordsFromText(headline);
|
|
1818
|
+
allKeywords.push(...words);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
catch (error) {
|
|
1826
|
+
// 네이버 실패 시 다음 뉴스 시도
|
|
1827
|
+
try {
|
|
1828
|
+
const daumNews = await scrapeDaumNews(category);
|
|
1829
|
+
news.push(...daumNews);
|
|
1830
|
+
if (extractKeywords) {
|
|
1831
|
+
daumNews.forEach(n => {
|
|
1832
|
+
allKeywords.push(...extractKeywordsFromText(n.headline));
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
catch {
|
|
1837
|
+
// 모두 실패 시 Fallback
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
// Fallback: 뉴스가 없으면 기본 데이터
|
|
1841
|
+
if (news.length === 0) {
|
|
1842
|
+
news.push(...getNewsFallback(category));
|
|
1843
|
+
}
|
|
1844
|
+
// 키워드 빈도 계산
|
|
1845
|
+
const keywordFrequency = extractKeywords ? calculateKeywordFrequency(allKeywords) : [];
|
|
1846
|
+
// 감성 분석 요약
|
|
1847
|
+
const sentiments = news.map(n => n.sentiment);
|
|
1848
|
+
const positiveCount = sentiments.filter(s => s === 'positive').length;
|
|
1849
|
+
const negativeCount = sentiments.filter(s => s === 'negative').length;
|
|
1850
|
+
const neutralCount = sentiments.filter(s => s === 'neutral').length;
|
|
1851
|
+
const total = sentiments.length || 1;
|
|
1852
|
+
return {
|
|
1853
|
+
category,
|
|
1854
|
+
time_range: timeRange,
|
|
1855
|
+
analyzed_at: new Date().toISOString(),
|
|
1856
|
+
source: news.length > 0 && news[0].url?.includes('naver') ? 'naver_news' : 'daum_news',
|
|
1857
|
+
top_news: news.slice(0, 10),
|
|
1858
|
+
extracted_keywords: keywordFrequency.slice(0, 10),
|
|
1859
|
+
sentiment_summary: {
|
|
1860
|
+
positive: `${Math.round((positiveCount / total) * 100)}%`,
|
|
1861
|
+
neutral: `${Math.round((neutralCount / total) * 100)}%`,
|
|
1862
|
+
negative: `${Math.round((negativeCount / total) * 100)}%`,
|
|
1863
|
+
},
|
|
1864
|
+
content_opportunities: generateNewsContentOpportunities(keywordFrequency, category),
|
|
1865
|
+
trending_topics: news.slice(0, 5).map(n => n.headline),
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
// 다음 뉴스 스크래핑
|
|
1869
|
+
async function scrapeDaumNews(category) {
|
|
1870
|
+
const categoryUrls = {
|
|
1871
|
+
general: 'https://news.daum.net/',
|
|
1872
|
+
politics: 'https://news.daum.net/politics',
|
|
1873
|
+
economy: 'https://news.daum.net/economic',
|
|
1874
|
+
society: 'https://news.daum.net/society',
|
|
1875
|
+
culture: 'https://news.daum.net/culture',
|
|
1876
|
+
tech: 'https://news.daum.net/digital',
|
|
1877
|
+
sports: 'https://sports.daum.net/',
|
|
1878
|
+
entertainment: 'https://entertain.daum.net/',
|
|
1879
|
+
};
|
|
1880
|
+
const url = categoryUrls[category] || categoryUrls.general;
|
|
1881
|
+
const response = await axios.get(url, {
|
|
1882
|
+
headers: {
|
|
1883
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
1884
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
1885
|
+
},
|
|
1886
|
+
timeout: 8000,
|
|
1887
|
+
});
|
|
1888
|
+
const $ = cheerio.load(response.data);
|
|
1889
|
+
const news = [];
|
|
1890
|
+
$('[class*="link_txt"], .tit_g, .news_view, .txt_info').each((i, el) => {
|
|
1891
|
+
if (news.length >= 10)
|
|
1892
|
+
return false;
|
|
1893
|
+
const headline = $(el).text().trim();
|
|
1894
|
+
if (headline && headline.length > 10 && headline.length < 100) {
|
|
1895
|
+
if (!news.find(n => n.headline === headline)) {
|
|
1896
|
+
news.push({
|
|
1897
|
+
headline,
|
|
1898
|
+
source: '다음뉴스',
|
|
1899
|
+
sentiment: analyzeSentiment(headline),
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
return news;
|
|
1905
|
+
}
|
|
1906
|
+
// 감성 분석 (간단 버전)
|
|
1907
|
+
function analyzeSentiment(text) {
|
|
1908
|
+
const positiveWords = /성공|상승|호조|기대|돌파|신기록|수상|인기|사랑|행복|좋은|최고|혁신|성장/;
|
|
1909
|
+
const negativeWords = /하락|위기|우려|실패|폭락|충격|논란|피해|사망|사고|비난|급락|위험|문제/;
|
|
1910
|
+
if (positiveWords.test(text))
|
|
1911
|
+
return 'positive';
|
|
1912
|
+
if (negativeWords.test(text))
|
|
1913
|
+
return 'negative';
|
|
1914
|
+
return 'neutral';
|
|
1915
|
+
}
|
|
1916
|
+
// 뉴스 소스 감지
|
|
1917
|
+
function detectNewsSource(url, category) {
|
|
1918
|
+
if (url.includes('sports'))
|
|
1919
|
+
return '스포츠';
|
|
1920
|
+
if (url.includes('entertain'))
|
|
1921
|
+
return '연예';
|
|
1922
|
+
const sources = {
|
|
1923
|
+
politics: '정치',
|
|
1924
|
+
economy: '경제',
|
|
1925
|
+
society: '사회',
|
|
1926
|
+
culture: '문화',
|
|
1927
|
+
tech: 'IT/과학',
|
|
1928
|
+
sports: '스포츠',
|
|
1929
|
+
entertainment: '연예',
|
|
1930
|
+
};
|
|
1931
|
+
return sources[category] || '종합';
|
|
1932
|
+
}
|
|
1933
|
+
// 텍스트에서 키워드 추출
|
|
1934
|
+
function extractKeywordsFromText(text) {
|
|
1935
|
+
// 특수문자 제거 후 2-6자 단어만 추출
|
|
1936
|
+
const words = text
|
|
1937
|
+
.replace(/[^\w\sㄱ-ㅎㅏ-ㅣ가-힣]/g, ' ')
|
|
1938
|
+
.split(/\s+/)
|
|
1939
|
+
.filter(w => w.length >= 2 && w.length <= 6)
|
|
1940
|
+
.filter(w => !/^\d+$/.test(w)); // 숫자만 있는 것 제외
|
|
1941
|
+
return words;
|
|
1942
|
+
}
|
|
1943
|
+
// 키워드 빈도 계산
|
|
1944
|
+
function calculateKeywordFrequency(words) {
|
|
1945
|
+
const frequency = {};
|
|
1946
|
+
words.forEach(word => {
|
|
1947
|
+
frequency[word] = (frequency[word] || 0) + 1;
|
|
1948
|
+
});
|
|
1949
|
+
return Object.entries(frequency)
|
|
1950
|
+
.sort((a, b) => b[1] - a[1])
|
|
1951
|
+
.slice(0, 15)
|
|
1952
|
+
.map(([keyword, freq]) => ({
|
|
1953
|
+
keyword,
|
|
1954
|
+
frequency: freq,
|
|
1955
|
+
trend: freq >= 5 ? '상승' : freq >= 3 ? '유지' : '일반',
|
|
1956
|
+
}));
|
|
1957
|
+
}
|
|
1958
|
+
// 뉴스 기반 콘텐츠 기회 생성
|
|
1959
|
+
function generateNewsContentOpportunities(keywords, category) {
|
|
1960
|
+
const opportunities = [];
|
|
1961
|
+
if (keywords.length > 0) {
|
|
1962
|
+
const topKeyword = keywords[0]?.keyword || '';
|
|
1963
|
+
opportunities.push(`"${topKeyword}" 관련 콘텐츠 수요 증가 - 해설/분석 콘텐츠 추천`);
|
|
1964
|
+
}
|
|
1965
|
+
const categoryOpportunities = {
|
|
1966
|
+
tech: ['AI/테크 트렌드 정리 콘텐츠', '신제품 리뷰 콘텐츠'],
|
|
1967
|
+
economy: ['재테크 팁 콘텐츠', '경제 뉴스 쉽게 풀어주기'],
|
|
1968
|
+
entertainment: ['K-콘텐츠 글로벌 화제', '연예 이슈 정리'],
|
|
1969
|
+
sports: ['경기 하이라이트', '선수 인터뷰 분석'],
|
|
1970
|
+
general: ['오늘의 이슈 정리', '트렌드 분석 콘텐츠'],
|
|
1971
|
+
};
|
|
1972
|
+
opportunities.push(...(categoryOpportunities[category] || categoryOpportunities.general));
|
|
1973
|
+
return opportunities.slice(0, 5);
|
|
1974
|
+
}
|
|
1975
|
+
// 뉴스 Fallback 데이터
|
|
1976
|
+
function getNewsFallback(category) {
|
|
1977
|
+
const now = new Date();
|
|
1978
|
+
const dateStr = now.toISOString().split('T')[0];
|
|
1979
|
+
const fallbackData = {
|
|
1381
1980
|
general: [
|
|
1382
|
-
{ headline:
|
|
1383
|
-
{ headline: "
|
|
1384
|
-
{ headline: "
|
|
1981
|
+
{ headline: `${dateStr} 오늘의 주요 뉴스`, source: "종합", sentiment: "neutral" },
|
|
1982
|
+
{ headline: "AI 기술 발전과 산업 변화", source: "종합", sentiment: "neutral" },
|
|
1983
|
+
{ headline: "글로벌 경제 동향", source: "종합", sentiment: "neutral" },
|
|
1385
1984
|
],
|
|
1386
1985
|
tech: [
|
|
1387
|
-
{ headline: "
|
|
1388
|
-
{ headline: "
|
|
1389
|
-
{ headline: "사이버 보안
|
|
1986
|
+
{ headline: "AI 서비스 업데이트 소식", source: "IT/과학", sentiment: "positive" },
|
|
1987
|
+
{ headline: "테크 기업 신제품 발표", source: "IT/과학", sentiment: "positive" },
|
|
1988
|
+
{ headline: "사이버 보안 동향", source: "IT/과학", sentiment: "neutral" },
|
|
1390
1989
|
],
|
|
1391
1990
|
economy: [
|
|
1392
|
-
{ headline: "
|
|
1393
|
-
{ headline: "부동산 시장
|
|
1394
|
-
{ headline: "환율
|
|
1991
|
+
{ headline: "금융 시장 동향", source: "경제", sentiment: "neutral" },
|
|
1992
|
+
{ headline: "부동산 시장 분석", source: "경제", sentiment: "neutral" },
|
|
1993
|
+
{ headline: "환율 변동 현황", source: "경제", sentiment: "neutral" },
|
|
1395
1994
|
],
|
|
1396
1995
|
entertainment: [
|
|
1397
1996
|
{ headline: "K-POP 글로벌 인기", source: "연예", sentiment: "positive" },
|
|
1398
|
-
{ headline: "
|
|
1399
|
-
{ headline: "연예계
|
|
1400
|
-
],
|
|
1401
|
-
};
|
|
1402
|
-
const news = newsData[category] || newsData.general;
|
|
1403
|
-
const keywords = extractKeywords ? [
|
|
1404
|
-
{ keyword: "AI", frequency: 45, trend: "상승" },
|
|
1405
|
-
{ keyword: "경제", frequency: 38, trend: "유지" },
|
|
1406
|
-
{ keyword: "트렌드", frequency: 32, trend: "상승" },
|
|
1407
|
-
{ keyword: "투자", frequency: 28, trend: "상승" },
|
|
1408
|
-
{ keyword: "기술", frequency: 25, trend: "상승" },
|
|
1409
|
-
] : [];
|
|
1410
|
-
return {
|
|
1411
|
-
category,
|
|
1412
|
-
time_range: timeRange,
|
|
1413
|
-
analyzed_at: new Date().toISOString(),
|
|
1414
|
-
top_news: news,
|
|
1415
|
-
extracted_keywords: keywords,
|
|
1416
|
-
sentiment_summary: {
|
|
1417
|
-
positive: "35%",
|
|
1418
|
-
neutral: "50%",
|
|
1419
|
-
negative: "15%",
|
|
1420
|
-
},
|
|
1421
|
-
content_opportunities: [
|
|
1422
|
-
"AI 관련 콘텐츠 수요 증가 - 입문자 가이드 추천",
|
|
1423
|
-
"경제 불안 심리 반영 - 재테크 팁 콘텐츠",
|
|
1424
|
-
"K-콘텐츠 글로벌 화제 - 한류 관련 콘텐츠",
|
|
1997
|
+
{ headline: "드라마/예능 화제작", source: "연예", sentiment: "positive" },
|
|
1998
|
+
{ headline: "연예계 소식", source: "연예", sentiment: "neutral" },
|
|
1425
1999
|
],
|
|
1426
|
-
trending_topics: news.map(n => n.headline),
|
|
1427
2000
|
};
|
|
2001
|
+
return fallbackData[category] || fallbackData.general;
|
|
1428
2002
|
}
|
|
1429
2003
|
// 해시태그 전략 생성
|
|
1430
2004
|
function generateAdvancedHashtagStrategy(topic, platform, count, includeKorean, includeEnglish) {
|