content-genie-mcp 2.5.0 → 2.7.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 +137 -14
- package/dist/index.js +1033 -105
- package/package.json +1 -1
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.7.0",
|
|
13
13
|
});
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// 공통 스키마
|
|
@@ -469,6 +469,224 @@ server.tool("get_seasonal_content_guide", "다가오는 시즌/이벤트에 맞
|
|
|
469
469
|
// =============================================================================
|
|
470
470
|
// Helper Functions - 고도화
|
|
471
471
|
// =============================================================================
|
|
472
|
+
// =============================================================================
|
|
473
|
+
// SEO 실시간 데이터 수집 함수들
|
|
474
|
+
// =============================================================================
|
|
475
|
+
// 네이버 자동완성 API (API 키 불필요)
|
|
476
|
+
async function getNaverAutocomplete(keyword) {
|
|
477
|
+
try {
|
|
478
|
+
const response = await axios.get(`https://ac.search.naver.com/nx/ac`, {
|
|
479
|
+
params: {
|
|
480
|
+
q: keyword,
|
|
481
|
+
con: 1,
|
|
482
|
+
frm: 'nv',
|
|
483
|
+
ans: 2,
|
|
484
|
+
r_format: 'json',
|
|
485
|
+
r_enc: 'UTF-8',
|
|
486
|
+
r_unicode: 0,
|
|
487
|
+
t_koreng: 1,
|
|
488
|
+
run: 2,
|
|
489
|
+
rev: 4,
|
|
490
|
+
q_enc: 'UTF-8'
|
|
491
|
+
},
|
|
492
|
+
headers: {
|
|
493
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
494
|
+
'Accept': 'application/json',
|
|
495
|
+
},
|
|
496
|
+
timeout: 5000,
|
|
497
|
+
});
|
|
498
|
+
const suggestions = [];
|
|
499
|
+
const items = response.data?.items || [];
|
|
500
|
+
for (const group of items) {
|
|
501
|
+
if (Array.isArray(group)) {
|
|
502
|
+
for (const item of group) {
|
|
503
|
+
if (Array.isArray(item) && item[0]) {
|
|
504
|
+
suggestions.push(item[0]);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return suggestions.slice(0, 10);
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// 구글 자동완성 API (API 키 불필요)
|
|
516
|
+
async function getGoogleAutocomplete(keyword) {
|
|
517
|
+
try {
|
|
518
|
+
const response = await axios.get(`https://suggestqueries.google.com/complete/search`, {
|
|
519
|
+
params: {
|
|
520
|
+
client: 'firefox',
|
|
521
|
+
q: keyword,
|
|
522
|
+
hl: 'ko',
|
|
523
|
+
},
|
|
524
|
+
headers: {
|
|
525
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
526
|
+
},
|
|
527
|
+
timeout: 5000,
|
|
528
|
+
});
|
|
529
|
+
// 응답 형식: ["keyword", ["suggestion1", "suggestion2", ...]]
|
|
530
|
+
if (Array.isArray(response.data) && Array.isArray(response.data[1])) {
|
|
531
|
+
return response.data[1].slice(0, 10);
|
|
532
|
+
}
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// 네이버 연관검색어 스크래핑
|
|
540
|
+
async function getNaverRelatedKeywords(keyword) {
|
|
541
|
+
try {
|
|
542
|
+
const response = await axios.get(`https://search.naver.com/search.naver`, {
|
|
543
|
+
params: {
|
|
544
|
+
where: 'nexearch',
|
|
545
|
+
query: keyword,
|
|
546
|
+
},
|
|
547
|
+
headers: {
|
|
548
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
549
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
550
|
+
},
|
|
551
|
+
timeout: 8000,
|
|
552
|
+
});
|
|
553
|
+
const $ = cheerio.load(response.data);
|
|
554
|
+
const relatedKeywords = [];
|
|
555
|
+
// 연관검색어 영역
|
|
556
|
+
$('.related_srch .keyword, .lst_related_srch .tit, [class*="related"] a').each((i, el) => {
|
|
557
|
+
const kw = $(el).text().trim();
|
|
558
|
+
if (kw && kw !== keyword && !relatedKeywords.find(r => r.keyword === kw)) {
|
|
559
|
+
relatedKeywords.push({
|
|
560
|
+
keyword: kw,
|
|
561
|
+
source: 'naver_related'
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
return relatedKeywords.slice(0, 10);
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return [];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// 네이버 검색 결과 수 추정 (경쟁도 측정)
|
|
572
|
+
async function getNaverSearchResultCount(keyword) {
|
|
573
|
+
try {
|
|
574
|
+
const response = await axios.get(`https://search.naver.com/search.naver`, {
|
|
575
|
+
params: {
|
|
576
|
+
where: 'blog',
|
|
577
|
+
query: keyword,
|
|
578
|
+
},
|
|
579
|
+
headers: {
|
|
580
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
581
|
+
},
|
|
582
|
+
timeout: 5000,
|
|
583
|
+
});
|
|
584
|
+
const $ = cheerio.load(response.data);
|
|
585
|
+
// 검색 결과 수 추출 시도
|
|
586
|
+
const countText = $('.title_num, .result_num, [class*="count"]').first().text();
|
|
587
|
+
const match = countText.match(/[\d,]+/);
|
|
588
|
+
if (match) {
|
|
589
|
+
return parseInt(match[0].replace(/,/g, ''), 10);
|
|
590
|
+
}
|
|
591
|
+
// 대략적 추정: 검색 결과 아이템 수 기반
|
|
592
|
+
const itemCount = $('.lst_total li, .api_txt_lines').length;
|
|
593
|
+
return itemCount > 0 ? itemCount * 10000 : 50000;
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
return 50000; // 기본값
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// 구글 검색 결과 수 추정
|
|
600
|
+
async function getGoogleSearchResultCount(keyword) {
|
|
601
|
+
try {
|
|
602
|
+
const response = await axios.get(`https://www.google.com/search`, {
|
|
603
|
+
params: {
|
|
604
|
+
q: keyword,
|
|
605
|
+
hl: 'ko',
|
|
606
|
+
gl: 'kr',
|
|
607
|
+
},
|
|
608
|
+
headers: {
|
|
609
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
610
|
+
},
|
|
611
|
+
timeout: 5000,
|
|
612
|
+
});
|
|
613
|
+
const $ = cheerio.load(response.data);
|
|
614
|
+
// "약 X개의 결과" 텍스트 추출
|
|
615
|
+
const resultStats = $('#result-stats').text();
|
|
616
|
+
const match = resultStats.match(/[\d,]+/);
|
|
617
|
+
if (match) {
|
|
618
|
+
return parseInt(match[0].replace(/,/g, ''), 10);
|
|
619
|
+
}
|
|
620
|
+
return 100000; // 기본값
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
return 100000;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// 검색량 레벨 추정 (자동완성 순위 기반)
|
|
627
|
+
function estimateSearchVolume(autocompleteRank, resultCount) {
|
|
628
|
+
// 자동완성 상위 + 결과 수 많음 = 검색량 높음
|
|
629
|
+
if (autocompleteRank <= 3 && resultCount > 100000)
|
|
630
|
+
return "매우 높음";
|
|
631
|
+
if (autocompleteRank <= 5 && resultCount > 50000)
|
|
632
|
+
return "높음";
|
|
633
|
+
if (autocompleteRank <= 8 && resultCount > 10000)
|
|
634
|
+
return "중간";
|
|
635
|
+
return "낮음";
|
|
636
|
+
}
|
|
637
|
+
// 경쟁도 레벨 추정
|
|
638
|
+
function estimateCompetition(resultCount) {
|
|
639
|
+
if (resultCount > 1000000)
|
|
640
|
+
return { level: "매우 높음", score: 90 };
|
|
641
|
+
if (resultCount > 500000)
|
|
642
|
+
return { level: "높음", score: 75 };
|
|
643
|
+
if (resultCount > 100000)
|
|
644
|
+
return { level: "중간", score: 55 };
|
|
645
|
+
if (resultCount > 10000)
|
|
646
|
+
return { level: "낮음", score: 35 };
|
|
647
|
+
return { level: "매우 낮음", score: 20 };
|
|
648
|
+
}
|
|
649
|
+
// SEO 난이도 계산
|
|
650
|
+
function calculateSEODifficulty(competition, resultCount) {
|
|
651
|
+
const baseScore = competition;
|
|
652
|
+
const resultFactor = Math.min(30, Math.log10(resultCount) * 5);
|
|
653
|
+
return Math.min(100, Math.round(baseScore + resultFactor));
|
|
654
|
+
}
|
|
655
|
+
// 콘텐츠 기회 점수 계산
|
|
656
|
+
function calculateOpportunityScore(searchVolume, competition) {
|
|
657
|
+
const volumeScores = { "매우 높음": 40, "높음": 30, "중간": 20, "낮음": 10 };
|
|
658
|
+
const competitionScores = { "매우 낮음": 40, "낮음": 30, "중간": 20, "높음": 10, "매우 높음": 5 };
|
|
659
|
+
return (volumeScores[searchVolume] || 20) + (competitionScores[competition] || 20);
|
|
660
|
+
}
|
|
661
|
+
// 키워드 카테고리 자동 분류
|
|
662
|
+
function categorizeKeyword(keyword) {
|
|
663
|
+
const text = keyword.toLowerCase();
|
|
664
|
+
if (/ai|gpt|인공지능|기술|테크|앱|소프트웨어|코딩|개발/.test(text))
|
|
665
|
+
return "tech";
|
|
666
|
+
if (/주식|코인|비트코인|투자|금리|경제|재테크|부동산|환율/.test(text))
|
|
667
|
+
return "finance";
|
|
668
|
+
if (/운동|헬스|다이어트|건강|병원|의료|영양/.test(text))
|
|
669
|
+
return "health";
|
|
670
|
+
if (/맛집|음식|요리|레시피|카페|먹방|배달/.test(text))
|
|
671
|
+
return "food";
|
|
672
|
+
if (/여행|호텔|관광|항공|휴가|리조트/.test(text))
|
|
673
|
+
return "travel";
|
|
674
|
+
if (/드라마|영화|연예|아이돌|방송|예능|넷플릭스|kpop/.test(text))
|
|
675
|
+
return "entertainment";
|
|
676
|
+
if (/게임|롤|배그|스팀|플스|닌텐도/.test(text))
|
|
677
|
+
return "gaming";
|
|
678
|
+
if (/뷰티|화장품|스킨케어|메이크업|패션|옷/.test(text))
|
|
679
|
+
return "beauty";
|
|
680
|
+
if (/축구|야구|농구|스포츠|올림픽|월드컵/.test(text))
|
|
681
|
+
return "sports";
|
|
682
|
+
if (/교육|공부|학교|시험|자격증|취업/.test(text))
|
|
683
|
+
return "education";
|
|
684
|
+
if (/쇼핑|할인|세일|구매|가격/.test(text))
|
|
685
|
+
return "shopping";
|
|
686
|
+
if (/뉴스|정치|사회|이슈/.test(text))
|
|
687
|
+
return "news";
|
|
688
|
+
return "general";
|
|
689
|
+
}
|
|
472
690
|
// 네이버 트렌드 스크래핑
|
|
473
691
|
async function scrapeNaverTrends() {
|
|
474
692
|
try {
|
|
@@ -572,38 +790,391 @@ function getDaumFallbackTrends() {
|
|
|
572
790
|
{ keyword: "날씨 정보", platform: "daum", rank: 5, category: "general" },
|
|
573
791
|
];
|
|
574
792
|
}
|
|
575
|
-
// 구글 트렌드 코리아
|
|
793
|
+
// 구글 트렌드 코리아 - 실제 RSS 피드 스크래핑
|
|
576
794
|
async function scrapeGoogleTrendsKorea() {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
795
|
+
try {
|
|
796
|
+
// Google Trends Daily RSS Feed for Korea
|
|
797
|
+
const response = await axios.get('https://trends.google.com/trending/rss?geo=KR', {
|
|
798
|
+
headers: {
|
|
799
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
800
|
+
'Accept': 'application/rss+xml, application/xml, text/xml'
|
|
801
|
+
},
|
|
802
|
+
timeout: 8000,
|
|
803
|
+
});
|
|
804
|
+
const $ = cheerio.load(response.data, { xmlMode: true });
|
|
805
|
+
const trends = [];
|
|
806
|
+
$('item').each((i, el) => {
|
|
807
|
+
if (i >= 15)
|
|
808
|
+
return false; // 최대 15개
|
|
809
|
+
const title = $(el).find('title').text().trim();
|
|
810
|
+
const traffic = $(el).find('ht\\:approx_traffic, approx_traffic').text().trim();
|
|
811
|
+
const newsItem = $(el).find('ht\\:news_item_title, news_item_title').first().text().trim();
|
|
812
|
+
if (title) {
|
|
813
|
+
trends.push({
|
|
814
|
+
keyword: title,
|
|
815
|
+
platform: "google",
|
|
816
|
+
rank: i + 1,
|
|
817
|
+
category: categorizeKeyword(title),
|
|
818
|
+
trend: "rising",
|
|
819
|
+
traffic: traffic || "10K+",
|
|
820
|
+
related_news: newsItem || null,
|
|
821
|
+
source: "google_trends_rss"
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
if (trends.length > 0) {
|
|
826
|
+
return trends;
|
|
827
|
+
}
|
|
828
|
+
// Fallback: 실시간 검색 트렌드 페이지 스크래핑 시도
|
|
829
|
+
return await scrapeGoogleTrendsFallback();
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
return await scrapeGoogleTrendsFallback();
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Google Trends Fallback - 트렌드 페이지 스크래핑
|
|
836
|
+
async function scrapeGoogleTrendsFallback() {
|
|
837
|
+
try {
|
|
838
|
+
const response = await axios.get('https://trends.google.co.kr/trends/trendingsearches/daily?geo=KR', {
|
|
839
|
+
headers: {
|
|
840
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
841
|
+
'Accept-Language': 'ko-KR,ko;q=0.9'
|
|
842
|
+
},
|
|
843
|
+
timeout: 8000,
|
|
844
|
+
});
|
|
845
|
+
const $ = cheerio.load(response.data);
|
|
846
|
+
const trends = [];
|
|
847
|
+
// 다양한 셀렉터 시도
|
|
848
|
+
$('[class*="feed-item"], [class*="trending"], .title').each((i, el) => {
|
|
849
|
+
if (i >= 10)
|
|
850
|
+
return false;
|
|
851
|
+
const keyword = $(el).text().trim();
|
|
852
|
+
if (keyword && keyword.length > 1 && keyword.length < 50) {
|
|
853
|
+
trends.push({
|
|
854
|
+
keyword,
|
|
855
|
+
platform: "google",
|
|
856
|
+
rank: i + 1,
|
|
857
|
+
category: categorizeKeyword(keyword),
|
|
858
|
+
trend: "rising",
|
|
859
|
+
source: "google_trends_page"
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
if (trends.length > 0)
|
|
864
|
+
return trends;
|
|
865
|
+
// 최종 Fallback: 시간대별 동적 데이터
|
|
866
|
+
return getGoogleFallbackTrends();
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
return getGoogleFallbackTrends();
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function getGoogleFallbackTrends() {
|
|
873
|
+
const hour = new Date().getHours();
|
|
874
|
+
const dayOfWeek = new Date().getDay();
|
|
875
|
+
const baseTrends = [
|
|
876
|
+
{ keyword: "AI 활용법", category: "tech" },
|
|
877
|
+
{ keyword: "ChatGPT 팁", category: "tech" },
|
|
878
|
+
{ keyword: "주식 시세", category: "finance" },
|
|
879
|
+
{ keyword: "비트코인", category: "finance" },
|
|
880
|
+
{ keyword: "다이어트", category: "health" },
|
|
585
881
|
];
|
|
882
|
+
// 시간대별 추가 트렌드
|
|
883
|
+
const timeTrends = hour >= 9 && hour <= 18
|
|
884
|
+
? [{ keyword: "점심 메뉴", category: "food" }, { keyword: "카페 추천", category: "food" }]
|
|
885
|
+
: [{ keyword: "넷플릭스 추천", category: "entertainment" }, { keyword: "게임 추천", category: "entertainment" }];
|
|
886
|
+
// 요일별 추가 트렌드
|
|
887
|
+
const dayTrends = dayOfWeek === 0 || dayOfWeek === 6
|
|
888
|
+
? [{ keyword: "주말 나들이", category: "travel" }, { keyword: "브런치 맛집", category: "food" }]
|
|
889
|
+
: [{ keyword: "재택근무 팁", category: "work" }, { keyword: "퇴근 후 취미", category: "lifestyle" }];
|
|
890
|
+
return [...baseTrends, ...timeTrends, ...dayTrends].map((item, i) => ({
|
|
891
|
+
...item,
|
|
892
|
+
platform: "google",
|
|
893
|
+
rank: i + 1,
|
|
894
|
+
trend: "stable",
|
|
895
|
+
source: "fallback_dynamic"
|
|
896
|
+
}));
|
|
586
897
|
}
|
|
587
|
-
// 유튜브 트렌드 코리아
|
|
898
|
+
// 유튜브 트렌드 코리아 - 실제 스크래핑
|
|
588
899
|
async function scrapeYoutubeTrendsKorea() {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
900
|
+
try {
|
|
901
|
+
// YouTube Korea Trending 페이지
|
|
902
|
+
const response = await axios.get('https://www.youtube.com/feed/trending?gl=KR&hl=ko', {
|
|
903
|
+
headers: {
|
|
904
|
+
'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',
|
|
905
|
+
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
906
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
907
|
+
},
|
|
908
|
+
timeout: 10000,
|
|
909
|
+
});
|
|
910
|
+
const trends = [];
|
|
911
|
+
const html = response.data;
|
|
912
|
+
// YouTube 초기 데이터 JSON 추출
|
|
913
|
+
const ytInitialDataMatch = html.match(/var ytInitialData = ({.*?});<\/script>/s);
|
|
914
|
+
if (ytInitialDataMatch) {
|
|
915
|
+
try {
|
|
916
|
+
const data = JSON.parse(ytInitialDataMatch[1]);
|
|
917
|
+
const tabs = data?.contents?.twoColumnBrowseResultsRenderer?.tabs || [];
|
|
918
|
+
for (const tab of tabs) {
|
|
919
|
+
const contents = tab?.tabRenderer?.content?.sectionListRenderer?.contents || [];
|
|
920
|
+
for (const section of contents) {
|
|
921
|
+
const items = section?.itemSectionRenderer?.contents || [];
|
|
922
|
+
for (const item of items) {
|
|
923
|
+
const video = item?.videoRenderer;
|
|
924
|
+
if (video && trends.length < 15) {
|
|
925
|
+
const title = video?.title?.runs?.[0]?.text || video?.title?.simpleText || '';
|
|
926
|
+
const viewCount = video?.viewCountText?.simpleText || video?.shortViewCountText?.simpleText || '';
|
|
927
|
+
const channel = video?.ownerText?.runs?.[0]?.text || '';
|
|
928
|
+
if (title) {
|
|
929
|
+
trends.push({
|
|
930
|
+
keyword: extractKeywordFromTitle(title),
|
|
931
|
+
title: title,
|
|
932
|
+
platform: "youtube",
|
|
933
|
+
rank: trends.length + 1,
|
|
934
|
+
category: categorizeYouTubeContent(title, channel),
|
|
935
|
+
views: viewCount,
|
|
936
|
+
channel: channel,
|
|
937
|
+
format: detectVideoFormat(title),
|
|
938
|
+
source: "youtube_trending"
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
catch (parseError) {
|
|
947
|
+
// JSON 파싱 실패 시 fallback
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
// HTML 파싱 시도 (JSON 실패 시)
|
|
951
|
+
if (trends.length === 0) {
|
|
952
|
+
const $ = cheerio.load(html);
|
|
953
|
+
$('a#video-title').each((i, el) => {
|
|
954
|
+
if (i >= 15)
|
|
955
|
+
return false;
|
|
956
|
+
const title = $(el).text().trim();
|
|
957
|
+
if (title) {
|
|
958
|
+
trends.push({
|
|
959
|
+
keyword: extractKeywordFromTitle(title),
|
|
960
|
+
title: title,
|
|
961
|
+
platform: "youtube",
|
|
962
|
+
rank: i + 1,
|
|
963
|
+
category: categorizeYouTubeContent(title, ''),
|
|
964
|
+
format: detectVideoFormat(title),
|
|
965
|
+
source: "youtube_html"
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
if (trends.length > 0)
|
|
971
|
+
return trends;
|
|
972
|
+
return getYoutubeFallbackTrends();
|
|
973
|
+
}
|
|
974
|
+
catch (error) {
|
|
975
|
+
return getYoutubeFallbackTrends();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
// YouTube 제목에서 키워드 추출
|
|
979
|
+
function extractKeywordFromTitle(title) {
|
|
980
|
+
// 대괄호, 괄호 내용 제거
|
|
981
|
+
let keyword = title.replace(/[\[\(【].*?[\]\)】]/g, '').trim();
|
|
982
|
+
// 특수문자 제거
|
|
983
|
+
keyword = keyword.replace(/[^\w\sㄱ-ㅎㅏ-ㅣ가-힣]/g, ' ').trim();
|
|
984
|
+
// 너무 길면 자르기
|
|
985
|
+
if (keyword.length > 30) {
|
|
986
|
+
keyword = keyword.substring(0, 30) + '...';
|
|
987
|
+
}
|
|
988
|
+
return keyword || title.substring(0, 30);
|
|
989
|
+
}
|
|
990
|
+
// YouTube 콘텐츠 카테고리 분류
|
|
991
|
+
function categorizeYouTubeContent(title, channel) {
|
|
992
|
+
const text = (title + ' ' + channel).toLowerCase();
|
|
993
|
+
if (/먹방|mukbang|음식|요리|레시피|맛집/.test(text))
|
|
994
|
+
return "food";
|
|
995
|
+
if (/게임|gaming|롤|lol|배그|minecraft/.test(text))
|
|
996
|
+
return "gaming";
|
|
997
|
+
if (/뷰티|메이크업|화장|스킨케어|뷰스타/.test(text))
|
|
998
|
+
return "beauty";
|
|
999
|
+
if (/운동|헬스|다이어트|fitness|workout/.test(text))
|
|
1000
|
+
return "fitness";
|
|
1001
|
+
if (/브이로그|vlog|일상/.test(text))
|
|
1002
|
+
return "lifestyle";
|
|
1003
|
+
if (/여행|travel|trip/.test(text))
|
|
1004
|
+
return "travel";
|
|
1005
|
+
if (/음악|노래|커버|music|mv/.test(text))
|
|
1006
|
+
return "music";
|
|
1007
|
+
if (/드라마|예능|영화|movie/.test(text))
|
|
1008
|
+
return "entertainment";
|
|
1009
|
+
if (/공부|강의|교육|tutorial/.test(text))
|
|
1010
|
+
return "education";
|
|
1011
|
+
if (/테크|리뷰|tech|unboxing/.test(text))
|
|
1012
|
+
return "tech";
|
|
1013
|
+
if (/뉴스|이슈|news/.test(text))
|
|
1014
|
+
return "news";
|
|
1015
|
+
if (/shorts|쇼츠|숏/.test(text))
|
|
1016
|
+
return "shorts";
|
|
1017
|
+
return "general";
|
|
1018
|
+
}
|
|
1019
|
+
// 영상 포맷 감지
|
|
1020
|
+
function detectVideoFormat(title) {
|
|
1021
|
+
const text = title.toLowerCase();
|
|
1022
|
+
if (/shorts|쇼츠/.test(text))
|
|
1023
|
+
return "shorts";
|
|
1024
|
+
if (/vlog|브이로그|일상/.test(text))
|
|
1025
|
+
return "vlog";
|
|
1026
|
+
if (/먹방|mukbang/.test(text))
|
|
1027
|
+
return "mukbang";
|
|
1028
|
+
if (/asmr/.test(text))
|
|
1029
|
+
return "asmr";
|
|
1030
|
+
if (/리뷰|review|언박싱|unboxing/.test(text))
|
|
1031
|
+
return "review";
|
|
1032
|
+
if (/튜토리얼|tutorial|강의|하는 법/.test(text))
|
|
1033
|
+
return "tutorial";
|
|
1034
|
+
if (/live|라이브/.test(text))
|
|
1035
|
+
return "live";
|
|
1036
|
+
if (/mv|뮤비|music video/.test(text))
|
|
1037
|
+
return "music_video";
|
|
1038
|
+
return "standard";
|
|
1039
|
+
}
|
|
1040
|
+
function getYoutubeFallbackTrends() {
|
|
1041
|
+
const hour = new Date().getHours();
|
|
1042
|
+
// 시간대별 인기 카테고리
|
|
1043
|
+
const popularCategories = hour >= 18 || hour <= 2
|
|
1044
|
+
? ["entertainment", "gaming", "music"] // 저녁/밤
|
|
1045
|
+
: hour >= 6 && hour <= 9
|
|
1046
|
+
? ["news", "education", "lifestyle"] // 아침
|
|
1047
|
+
: ["food", "lifestyle", "tech"]; // 낮
|
|
1048
|
+
const trends = [
|
|
1049
|
+
{ keyword: "유튜브 인기 급상승", category: popularCategories[0], format: "trending" },
|
|
1050
|
+
{ keyword: "쇼츠 챌린지", category: "shorts", format: "shorts", views: "500K+" },
|
|
1051
|
+
{ keyword: "브이로그", category: "lifestyle", format: "vlog", views: "300K+" },
|
|
1052
|
+
{ keyword: "먹방 ASMR", category: "food", format: "mukbang", views: "400K+" },
|
|
1053
|
+
{ keyword: "게임 실황", category: "gaming", format: "streaming", views: "350K+" },
|
|
1054
|
+
{ keyword: "K-POP 커버", category: "music", format: "cover", views: "600K+" },
|
|
1055
|
+
{ keyword: "메이크업 튜토리얼", category: "beauty", format: "tutorial", views: "250K+" },
|
|
1056
|
+
{ keyword: "IT 리뷰", category: "tech", format: "review", views: "200K+" },
|
|
598
1057
|
];
|
|
1058
|
+
return trends.map((item, i) => ({
|
|
1059
|
+
...item,
|
|
1060
|
+
platform: "youtube",
|
|
1061
|
+
rank: i + 1,
|
|
1062
|
+
source: "fallback_dynamic"
|
|
1063
|
+
}));
|
|
599
1064
|
}
|
|
600
|
-
// 줌 트렌드
|
|
1065
|
+
// 줌 트렌드 - 실제 스크래핑
|
|
601
1066
|
async function scrapeZumTrends() {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
1067
|
+
try {
|
|
1068
|
+
// Zum 메인 페이지 실시간 검색어
|
|
1069
|
+
const response = await axios.get('https://zum.com/', {
|
|
1070
|
+
headers: {
|
|
1071
|
+
'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',
|
|
1072
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
1073
|
+
},
|
|
1074
|
+
timeout: 8000,
|
|
1075
|
+
});
|
|
1076
|
+
const $ = cheerio.load(response.data);
|
|
1077
|
+
const trends = [];
|
|
1078
|
+
// 줌 실시간 검색어 셀렉터들
|
|
1079
|
+
const selectors = [
|
|
1080
|
+
'.realtime_keyword_list li',
|
|
1081
|
+
'.issue_keyword li',
|
|
1082
|
+
'[class*="ranking"] li',
|
|
1083
|
+
'[class*="keyword"] a',
|
|
1084
|
+
'.hot_keyword li'
|
|
1085
|
+
];
|
|
1086
|
+
for (const selector of selectors) {
|
|
1087
|
+
if (trends.length >= 10)
|
|
1088
|
+
break;
|
|
1089
|
+
$(selector).each((i, el) => {
|
|
1090
|
+
if (trends.length >= 10)
|
|
1091
|
+
return false;
|
|
1092
|
+
const keyword = $(el).text().trim()
|
|
1093
|
+
.replace(/^\d+\.?\s*/, '') // 순위 번호 제거
|
|
1094
|
+
.replace(/new|↑|↓|─/gi, '') // 변동 표시 제거
|
|
1095
|
+
.trim();
|
|
1096
|
+
if (keyword && keyword.length > 1 && keyword.length < 30 && !trends.find(t => t.keyword === keyword)) {
|
|
1097
|
+
trends.push({
|
|
1098
|
+
keyword,
|
|
1099
|
+
platform: "zum",
|
|
1100
|
+
rank: trends.length + 1,
|
|
1101
|
+
category: categorizeKeyword(keyword),
|
|
1102
|
+
source: "zum_realtime"
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
if (trends.length > 0)
|
|
1108
|
+
return trends;
|
|
1109
|
+
// Zum 뉴스 섹션 시도
|
|
1110
|
+
return await scrapeZumNewsTrends();
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
return await scrapeZumNewsTrends();
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
async function scrapeZumNewsTrends() {
|
|
1117
|
+
try {
|
|
1118
|
+
const response = await axios.get('https://news.zum.com/', {
|
|
1119
|
+
headers: {
|
|
1120
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
1121
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
1122
|
+
},
|
|
1123
|
+
timeout: 8000,
|
|
1124
|
+
});
|
|
1125
|
+
const $ = cheerio.load(response.data);
|
|
1126
|
+
const trends = [];
|
|
1127
|
+
// 뉴스 헤드라인에서 키워드 추출
|
|
1128
|
+
$('h2, h3, .headline, .title, [class*="news_title"]').each((i, el) => {
|
|
1129
|
+
if (trends.length >= 10)
|
|
1130
|
+
return false;
|
|
1131
|
+
const text = $(el).text().trim();
|
|
1132
|
+
if (text && text.length > 5 && text.length < 50) {
|
|
1133
|
+
const keyword = extractNewsKeyword(text);
|
|
1134
|
+
if (keyword && !trends.find(t => t.keyword === keyword)) {
|
|
1135
|
+
trends.push({
|
|
1136
|
+
keyword,
|
|
1137
|
+
platform: "zum",
|
|
1138
|
+
rank: trends.length + 1,
|
|
1139
|
+
category: categorizeKeyword(keyword),
|
|
1140
|
+
source: "zum_news"
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
if (trends.length > 0)
|
|
1146
|
+
return trends;
|
|
1147
|
+
return getZumFallbackTrends();
|
|
1148
|
+
}
|
|
1149
|
+
catch {
|
|
1150
|
+
return getZumFallbackTrends();
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function extractNewsKeyword(headline) {
|
|
1154
|
+
// 헤드라인에서 핵심 키워드 추출
|
|
1155
|
+
const words = headline.split(/[\s,\.…]+/).filter(w => w.length >= 2 && w.length <= 10);
|
|
1156
|
+
return words.slice(0, 3).join(' ') || headline.substring(0, 20);
|
|
1157
|
+
}
|
|
1158
|
+
function getZumFallbackTrends() {
|
|
1159
|
+
const hour = new Date().getHours();
|
|
1160
|
+
const baseKeywords = [
|
|
1161
|
+
{ keyword: "오늘의 뉴스", category: "news" },
|
|
1162
|
+
{ keyword: "실시간 이슈", category: "general" },
|
|
1163
|
+
{ keyword: "연예 소식", category: "entertainment" },
|
|
1164
|
+
{ keyword: "스포츠 결과", category: "sports" },
|
|
1165
|
+
{ keyword: "경제 동향", category: "finance" },
|
|
606
1166
|
];
|
|
1167
|
+
const timeBased = hour >= 7 && hour <= 9
|
|
1168
|
+
? [{ keyword: "아침 뉴스", category: "news" }]
|
|
1169
|
+
: hour >= 12 && hour <= 14
|
|
1170
|
+
? [{ keyword: "점심 이슈", category: "general" }]
|
|
1171
|
+
: [{ keyword: "저녁 뉴스", category: "news" }];
|
|
1172
|
+
return [...baseKeywords, ...timeBased].map((item, i) => ({
|
|
1173
|
+
...item,
|
|
1174
|
+
platform: "zum",
|
|
1175
|
+
rank: i + 1,
|
|
1176
|
+
source: "fallback"
|
|
1177
|
+
}));
|
|
607
1178
|
}
|
|
608
1179
|
// 고급 트렌드 인사이트 생성
|
|
609
1180
|
function generateAdvancedTrendInsights(trends) {
|
|
@@ -902,80 +1473,245 @@ function calculateReadabilityScore(text) {
|
|
|
902
1473
|
const optimalLength = length >= 20 && length <= 50 ? 15 : length >= 50 && length <= 70 ? 10 : 5;
|
|
903
1474
|
return Math.min(100, 60 + hasNumbers + hasEmoji + optimalLength);
|
|
904
1475
|
}
|
|
905
|
-
// 고급 SEO 키워드 분석
|
|
1476
|
+
// 고급 SEO 키워드 분석 - 실시간 데이터 기반
|
|
906
1477
|
async function analyzeAdvancedSEOKeywords(keyword, searchEngine, includeQuestions, includeLongtail, competitorAnalysis) {
|
|
907
|
-
//
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1478
|
+
// 병렬로 실시간 데이터 수집
|
|
1479
|
+
const [naverAutocomplete, googleAutocomplete, naverRelated, naverResultCount, googleResultCount] = await Promise.all([
|
|
1480
|
+
searchEngine !== 'google' ? getNaverAutocomplete(keyword) : Promise.resolve([]),
|
|
1481
|
+
searchEngine !== 'naver' ? getGoogleAutocomplete(keyword) : Promise.resolve([]),
|
|
1482
|
+
getNaverRelatedKeywords(keyword),
|
|
1483
|
+
searchEngine !== 'google' ? getNaverSearchResultCount(keyword) : Promise.resolve(0),
|
|
1484
|
+
searchEngine !== 'naver' ? getGoogleSearchResultCount(keyword) : Promise.resolve(0),
|
|
1485
|
+
]);
|
|
1486
|
+
// 주요 검색 엔진 결과 수 선택
|
|
1487
|
+
const primaryResultCount = searchEngine === 'naver' ? naverResultCount :
|
|
1488
|
+
searchEngine === 'google' ? googleResultCount :
|
|
1489
|
+
Math.max(naverResultCount, googleResultCount);
|
|
1490
|
+
// 경쟁도 및 검색량 추정
|
|
1491
|
+
const competition = estimateCompetition(primaryResultCount);
|
|
1492
|
+
const autocompleteKeywords = [...new Set([...naverAutocomplete, ...googleAutocomplete])];
|
|
1493
|
+
const keywordRank = autocompleteKeywords.findIndex(k => k.includes(keyword)) + 1 || 10;
|
|
1494
|
+
const searchVolume = estimateSearchVolume(keywordRank, primaryResultCount);
|
|
1495
|
+
// SEO 난이도 계산
|
|
1496
|
+
const seoDifficulty = calculateSEODifficulty(competition.score, primaryResultCount);
|
|
1497
|
+
const opportunityScore = calculateOpportunityScore(searchVolume, competition.level);
|
|
1498
|
+
// 실시간 관련 키워드 생성
|
|
1499
|
+
const relatedKeywords = [];
|
|
1500
|
+
// 자동완성에서 추출
|
|
1501
|
+
for (let i = 0; i < Math.min(autocompleteKeywords.length, 5); i++) {
|
|
1502
|
+
const kw = autocompleteKeywords[i];
|
|
1503
|
+
if (kw && kw !== keyword) {
|
|
1504
|
+
relatedKeywords.push({
|
|
1505
|
+
keyword: kw,
|
|
1506
|
+
volume: estimateSearchVolume(i + 1, primaryResultCount * 0.7),
|
|
1507
|
+
competition: i < 3 ? "높음" : "중간",
|
|
1508
|
+
trend: "상승",
|
|
1509
|
+
source: "autocomplete"
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
// 연관검색어에서 추출
|
|
1514
|
+
for (const related of naverRelated.slice(0, 5)) {
|
|
1515
|
+
if (!relatedKeywords.find(r => r.keyword === related.keyword)) {
|
|
1516
|
+
relatedKeywords.push({
|
|
1517
|
+
keyword: related.keyword,
|
|
1518
|
+
volume: "중간",
|
|
1519
|
+
competition: "중간",
|
|
1520
|
+
trend: "유지",
|
|
1521
|
+
source: "naver_related"
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
// 템플릿 기반 추가 키워드
|
|
1526
|
+
const templateKeywords = [
|
|
1527
|
+
{ suffix: " 방법", volume: "높음", competition: "중간" },
|
|
1528
|
+
{ suffix: " 추천", volume: "매우 높음", competition: "높음" },
|
|
1529
|
+
{ suffix: " 후기", volume: "높음", competition: "중간" },
|
|
1530
|
+
{ suffix: " 비교", volume: "중간", competition: "낮음" },
|
|
1531
|
+
{ suffix: " 가격", volume: "매우 높음", competition: "매우 높음" },
|
|
917
1532
|
];
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1533
|
+
for (const tmpl of templateKeywords) {
|
|
1534
|
+
const kw = keyword + tmpl.suffix;
|
|
1535
|
+
if (!relatedKeywords.find(r => r.keyword === kw)) {
|
|
1536
|
+
relatedKeywords.push({
|
|
1537
|
+
keyword: kw,
|
|
1538
|
+
volume: tmpl.volume,
|
|
1539
|
+
competition: tmpl.competition,
|
|
1540
|
+
trend: "유지",
|
|
1541
|
+
source: "template"
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
// 질문형 키워드 (자동완성 기반)
|
|
1546
|
+
const questionKeywords = [];
|
|
1547
|
+
if (includeQuestions) {
|
|
1548
|
+
const questionPrefixes = ["", "어떻게 ", "왜 ", "언제 ", "어디서 "];
|
|
1549
|
+
const questionSuffixes = ["란", "이란", " 뭐", " 무엇", " 어떻게", " 왜", " 방법"];
|
|
1550
|
+
// 자동완성에서 질문형 추출
|
|
1551
|
+
for (const ac of autocompleteKeywords) {
|
|
1552
|
+
if (questionSuffixes.some(s => ac.includes(s)) || ac.includes("?")) {
|
|
1553
|
+
questionKeywords.push({
|
|
1554
|
+
keyword: ac,
|
|
1555
|
+
type: detectQuestionType(ac),
|
|
1556
|
+
intent: detectSearchIntent(ac),
|
|
1557
|
+
source: "autocomplete"
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
// 기본 질문 템플릿
|
|
1562
|
+
if (questionKeywords.length < 5) {
|
|
1563
|
+
const defaultQuestions = [
|
|
1564
|
+
{ keyword: `${keyword}이란?`, type: "정의", intent: "정보탐색" },
|
|
1565
|
+
{ keyword: `${keyword} 어떻게 하나요?`, type: "방법", intent: "정보탐색" },
|
|
1566
|
+
{ keyword: `${keyword} 왜 필요한가요?`, type: "이유", intent: "정보탐색" },
|
|
1567
|
+
{ keyword: `${keyword} 얼마인가요?`, type: "가격", intent: "구매의도" },
|
|
1568
|
+
];
|
|
1569
|
+
for (const q of defaultQuestions) {
|
|
1570
|
+
if (!questionKeywords.find(qk => qk.keyword === q.keyword)) {
|
|
1571
|
+
questionKeywords.push({ ...q, source: "template" });
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
// 롱테일 키워드
|
|
1577
|
+
const longtailKeywords = [];
|
|
1578
|
+
if (includeLongtail) {
|
|
1579
|
+
// 자동완성에서 긴 키워드 추출
|
|
1580
|
+
for (const ac of autocompleteKeywords) {
|
|
1581
|
+
if (ac.length > keyword.length + 5 && !relatedKeywords.find(r => r.keyword === ac)) {
|
|
1582
|
+
longtailKeywords.push({
|
|
1583
|
+
keyword: ac,
|
|
1584
|
+
difficulty: Math.round(seoDifficulty * 0.6 + Math.random() * 20),
|
|
1585
|
+
opportunity: "높음",
|
|
1586
|
+
source: "autocomplete"
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
// 템플릿 기반 롱테일
|
|
1591
|
+
const longtailTemplates = [
|
|
1592
|
+
{ pattern: `초보자를 위한 ${keyword} 완벽 가이드`, difficulty: 35 },
|
|
1593
|
+
{ pattern: `${keyword} 실수 피하는 방법`, difficulty: 28 },
|
|
1594
|
+
{ pattern: `2025년 ${keyword} 트렌드`, difficulty: 42 },
|
|
1595
|
+
{ pattern: `${keyword} 비용 절약 팁`, difficulty: 31 },
|
|
1596
|
+
{ pattern: `${keyword} 전문가 추천`, difficulty: 38 },
|
|
1597
|
+
];
|
|
1598
|
+
for (const tmpl of longtailTemplates) {
|
|
1599
|
+
if (!longtailKeywords.find(l => l.keyword === tmpl.pattern)) {
|
|
1600
|
+
longtailKeywords.push({
|
|
1601
|
+
keyword: tmpl.pattern,
|
|
1602
|
+
difficulty: Math.round(tmpl.difficulty + (seoDifficulty - 50) * 0.3),
|
|
1603
|
+
opportunity: tmpl.difficulty < 35 ? "매우 높음" : "높음",
|
|
1604
|
+
source: "template"
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
// 검색엔진별 전략
|
|
933
1610
|
const searchEngineStrategy = {
|
|
934
1611
|
naver: {
|
|
1612
|
+
result_count: naverResultCount.toLocaleString(),
|
|
1613
|
+
competition: estimateCompetition(naverResultCount).level,
|
|
935
1614
|
tips: [
|
|
936
1615
|
"네이버 블로그/포스트에 발행하세요",
|
|
937
1616
|
"키워드를 제목에 정확히 포함하세요",
|
|
938
1617
|
"이미지 ALT 태그에 키워드 추가",
|
|
939
1618
|
"체류시간을 늘리는 콘텐츠 작성",
|
|
1619
|
+
`경쟁 블로그 ${Math.min(naverResultCount, 1000000).toLocaleString()}개 이상 - 차별화 필수`,
|
|
940
1620
|
],
|
|
941
1621
|
content_types: ["블로그", "포스트", "지식iN"],
|
|
942
1622
|
},
|
|
943
1623
|
google: {
|
|
1624
|
+
result_count: googleResultCount.toLocaleString(),
|
|
1625
|
+
competition: estimateCompetition(googleResultCount).level,
|
|
944
1626
|
tips: [
|
|
945
1627
|
"H1, H2 태그에 키워드 배치",
|
|
946
1628
|
"메타 디스크립션 최적화",
|
|
947
1629
|
"모바일 친화적 디자인 필수",
|
|
948
1630
|
"페이지 로딩 속도 개선",
|
|
1631
|
+
"백링크 확보 전략 수립",
|
|
949
1632
|
],
|
|
950
1633
|
content_types: ["웹사이트", "유튜브", "뉴스"],
|
|
951
1634
|
},
|
|
952
1635
|
};
|
|
1636
|
+
// 추천 액션 생성
|
|
1637
|
+
const recommendedAction = seoDifficulty > 70
|
|
1638
|
+
? "경쟁이 치열합니다. 롱테일 키워드로 진입 후 메인 키워드 공략을 권장합니다."
|
|
1639
|
+
: seoDifficulty > 50
|
|
1640
|
+
? "중간 경쟁입니다. 고품질 콘텐츠와 꾸준한 발행이 중요합니다."
|
|
1641
|
+
: "경쟁이 낮습니다. 빠른 진입으로 선점 효과를 노리세요.";
|
|
953
1642
|
return {
|
|
954
1643
|
main_keyword: keyword,
|
|
1644
|
+
data_source: {
|
|
1645
|
+
naver_autocomplete: naverAutocomplete.length,
|
|
1646
|
+
google_autocomplete: googleAutocomplete.length,
|
|
1647
|
+
naver_related: naverRelated.length,
|
|
1648
|
+
naver_results: naverResultCount.toLocaleString(),
|
|
1649
|
+
google_results: googleResultCount.toLocaleString(),
|
|
1650
|
+
},
|
|
955
1651
|
overall_analysis: {
|
|
956
|
-
search_volume:
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1652
|
+
search_volume: searchVolume,
|
|
1653
|
+
search_volume_indicator: keywordRank <= 3 ? "🔥 매우 높음" : keywordRank <= 6 ? "📈 높음" : "📊 보통",
|
|
1654
|
+
competition_level: competition.level,
|
|
1655
|
+
competition_score: competition.score,
|
|
1656
|
+
seo_difficulty: seoDifficulty,
|
|
1657
|
+
seo_difficulty_grade: seoDifficulty > 70 ? "어려움" : seoDifficulty > 50 ? "보통" : "쉬움",
|
|
1658
|
+
content_opportunity_score: opportunityScore,
|
|
1659
|
+
recommended_action: recommendedAction,
|
|
961
1660
|
},
|
|
962
|
-
related_keywords: relatedKeywords,
|
|
963
|
-
question_keywords: questionKeywords,
|
|
964
|
-
longtail_keywords: longtailKeywords,
|
|
1661
|
+
related_keywords: relatedKeywords.slice(0, 15),
|
|
1662
|
+
question_keywords: questionKeywords.slice(0, 8),
|
|
1663
|
+
longtail_keywords: longtailKeywords.slice(0, 8),
|
|
965
1664
|
search_engine_strategy: searchEngine === "both" ? searchEngineStrategy : searchEngineStrategy[searchEngine],
|
|
966
1665
|
content_recommendations: {
|
|
967
|
-
ideal_length: "
|
|
968
|
-
must_include: ["정의", "방법", "예시", "FAQ"],
|
|
969
|
-
format: "종합 가이드 형식",
|
|
1666
|
+
ideal_length: seoDifficulty > 60 ? "4000-6000자 (경쟁 대응)" : "2500-4000자",
|
|
1667
|
+
must_include: ["정의", "방법", "예시", "FAQ", "비교"],
|
|
1668
|
+
format: seoDifficulty > 60 ? "종합 가이드 형식 (심층 분석)" : "핵심 정리 형식",
|
|
970
1669
|
media: ["이미지 5-10개", "인포그래픽 1개", "영상 임베드"],
|
|
1670
|
+
posting_frequency: seoDifficulty > 70 ? "주 3회 이상" : "주 1-2회",
|
|
971
1671
|
},
|
|
972
1672
|
competitor_insights: competitorAnalysis ? {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1673
|
+
estimated_competitors: primaryResultCount > 100000 ? "10만+" : primaryResultCount > 10000 ? "1만+" : "1천+",
|
|
1674
|
+
top_ranking_strategy: [
|
|
1675
|
+
"제목에 키워드 정확히 포함",
|
|
1676
|
+
"3000자 이상의 상세 콘텐츠",
|
|
1677
|
+
"이미지/영상 풍부하게 활용",
|
|
1678
|
+
"정기적인 업데이트",
|
|
1679
|
+
],
|
|
1680
|
+
gap_opportunities: [
|
|
1681
|
+
"최신 2025년 트렌드 반영",
|
|
1682
|
+
"실제 사례/후기 포함",
|
|
1683
|
+
"비교 분석 콘텐츠",
|
|
1684
|
+
"FAQ 섹션 추가",
|
|
1685
|
+
],
|
|
976
1686
|
} : null,
|
|
977
1687
|
};
|
|
978
1688
|
}
|
|
1689
|
+
// 질문 유형 감지
|
|
1690
|
+
function detectQuestionType(text) {
|
|
1691
|
+
if (/이란|무엇|뭐야|뜻/.test(text))
|
|
1692
|
+
return "정의";
|
|
1693
|
+
if (/어떻게|방법|하는법/.test(text))
|
|
1694
|
+
return "방법";
|
|
1695
|
+
if (/왜|이유/.test(text))
|
|
1696
|
+
return "이유";
|
|
1697
|
+
if (/얼마|가격|비용/.test(text))
|
|
1698
|
+
return "가격";
|
|
1699
|
+
if (/어디|장소|위치/.test(text))
|
|
1700
|
+
return "장소";
|
|
1701
|
+
if (/언제|시간|기간/.test(text))
|
|
1702
|
+
return "시간";
|
|
1703
|
+
return "일반";
|
|
1704
|
+
}
|
|
1705
|
+
// 검색 의도 감지
|
|
1706
|
+
function detectSearchIntent(text) {
|
|
1707
|
+
if (/구매|가격|얼마|싼|저렴|할인/.test(text))
|
|
1708
|
+
return "구매의도";
|
|
1709
|
+
if (/vs|비교|차이|어떤게/.test(text))
|
|
1710
|
+
return "비교검토";
|
|
1711
|
+
if (/후기|리뷰|평가|사용/.test(text))
|
|
1712
|
+
return "사용경험";
|
|
1713
|
+
return "정보탐색";
|
|
1714
|
+
}
|
|
979
1715
|
// 고급 콘텐츠 캘린더 생성
|
|
980
1716
|
function createAdvancedContentCalendar(topics, durationWeeks, postsPerWeek, platforms, includeEvents, contentMix) {
|
|
981
1717
|
const calendar = [];
|
|
@@ -1374,57 +2110,249 @@ function predictAdvancedViralScore(title, description, platform, hashtags, conte
|
|
|
1374
2110
|
].filter(Boolean).slice(0, 3),
|
|
1375
2111
|
};
|
|
1376
2112
|
}
|
|
1377
|
-
// 뉴스 분석
|
|
2113
|
+
// 뉴스 분석 - 실제 스크래핑
|
|
1378
2114
|
async function analyzeKoreanNews(category, timeRange, extractKeywords) {
|
|
1379
|
-
|
|
1380
|
-
const
|
|
2115
|
+
const news = [];
|
|
2116
|
+
const allKeywords = [];
|
|
2117
|
+
// 카테고리별 네이버 뉴스 섹션 URL
|
|
2118
|
+
const categoryUrls = {
|
|
2119
|
+
general: 'https://news.naver.com/',
|
|
2120
|
+
politics: 'https://news.naver.com/section/100',
|
|
2121
|
+
economy: 'https://news.naver.com/section/101',
|
|
2122
|
+
society: 'https://news.naver.com/section/102',
|
|
2123
|
+
culture: 'https://news.naver.com/section/103',
|
|
2124
|
+
tech: 'https://news.naver.com/section/105',
|
|
2125
|
+
sports: 'https://sports.news.naver.com/',
|
|
2126
|
+
entertainment: 'https://entertain.naver.com/home',
|
|
2127
|
+
};
|
|
2128
|
+
const url = categoryUrls[category] || categoryUrls.general;
|
|
2129
|
+
try {
|
|
2130
|
+
// 네이버 뉴스 스크래핑
|
|
2131
|
+
const response = await axios.get(url, {
|
|
2132
|
+
headers: {
|
|
2133
|
+
'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',
|
|
2134
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
2135
|
+
},
|
|
2136
|
+
timeout: 10000,
|
|
2137
|
+
});
|
|
2138
|
+
const $ = cheerio.load(response.data);
|
|
2139
|
+
// 헤드라인 추출 - 다양한 셀렉터 시도
|
|
2140
|
+
const headlineSelectors = [
|
|
2141
|
+
'.cjs_t', // 네이버 뉴스 메인
|
|
2142
|
+
'.sa_text_title',
|
|
2143
|
+
'a.news_tit',
|
|
2144
|
+
'.cluster_text_headline',
|
|
2145
|
+
'.cluster_head_topic',
|
|
2146
|
+
'h2.tit',
|
|
2147
|
+
'.link_news',
|
|
2148
|
+
'[class*="headline"] a',
|
|
2149
|
+
'[class*="title"] a',
|
|
2150
|
+
];
|
|
2151
|
+
for (const selector of headlineSelectors) {
|
|
2152
|
+
if (news.length >= 15)
|
|
2153
|
+
break;
|
|
2154
|
+
$(selector).each((i, el) => {
|
|
2155
|
+
if (news.length >= 15)
|
|
2156
|
+
return false;
|
|
2157
|
+
const headline = $(el).text().trim();
|
|
2158
|
+
const href = $(el).attr('href') || '';
|
|
2159
|
+
if (headline && headline.length > 10 && headline.length < 100) {
|
|
2160
|
+
// 중복 체크
|
|
2161
|
+
if (!news.find(n => n.headline === headline)) {
|
|
2162
|
+
const sentiment = analyzeSentiment(headline);
|
|
2163
|
+
news.push({
|
|
2164
|
+
headline,
|
|
2165
|
+
source: detectNewsSource(href, category),
|
|
2166
|
+
sentiment,
|
|
2167
|
+
url: href.startsWith('http') ? href : `https://news.naver.com${href}`,
|
|
2168
|
+
});
|
|
2169
|
+
// 키워드 추출
|
|
2170
|
+
if (extractKeywords) {
|
|
2171
|
+
const words = extractKeywordsFromText(headline);
|
|
2172
|
+
allKeywords.push(...words);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
catch (error) {
|
|
2180
|
+
// 네이버 실패 시 다음 뉴스 시도
|
|
2181
|
+
try {
|
|
2182
|
+
const daumNews = await scrapeDaumNews(category);
|
|
2183
|
+
news.push(...daumNews);
|
|
2184
|
+
if (extractKeywords) {
|
|
2185
|
+
daumNews.forEach(n => {
|
|
2186
|
+
allKeywords.push(...extractKeywordsFromText(n.headline));
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
catch {
|
|
2191
|
+
// 모두 실패 시 Fallback
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
// Fallback: 뉴스가 없으면 기본 데이터
|
|
2195
|
+
if (news.length === 0) {
|
|
2196
|
+
news.push(...getNewsFallback(category));
|
|
2197
|
+
}
|
|
2198
|
+
// 키워드 빈도 계산
|
|
2199
|
+
const keywordFrequency = extractKeywords ? calculateKeywordFrequency(allKeywords) : [];
|
|
2200
|
+
// 감성 분석 요약
|
|
2201
|
+
const sentiments = news.map(n => n.sentiment);
|
|
2202
|
+
const positiveCount = sentiments.filter(s => s === 'positive').length;
|
|
2203
|
+
const negativeCount = sentiments.filter(s => s === 'negative').length;
|
|
2204
|
+
const neutralCount = sentiments.filter(s => s === 'neutral').length;
|
|
2205
|
+
const total = sentiments.length || 1;
|
|
2206
|
+
return {
|
|
2207
|
+
category,
|
|
2208
|
+
time_range: timeRange,
|
|
2209
|
+
analyzed_at: new Date().toISOString(),
|
|
2210
|
+
source: news.length > 0 && news[0].url?.includes('naver') ? 'naver_news' : 'daum_news',
|
|
2211
|
+
top_news: news.slice(0, 10),
|
|
2212
|
+
extracted_keywords: keywordFrequency.slice(0, 10),
|
|
2213
|
+
sentiment_summary: {
|
|
2214
|
+
positive: `${Math.round((positiveCount / total) * 100)}%`,
|
|
2215
|
+
neutral: `${Math.round((neutralCount / total) * 100)}%`,
|
|
2216
|
+
negative: `${Math.round((negativeCount / total) * 100)}%`,
|
|
2217
|
+
},
|
|
2218
|
+
content_opportunities: generateNewsContentOpportunities(keywordFrequency, category),
|
|
2219
|
+
trending_topics: news.slice(0, 5).map(n => n.headline),
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
// 다음 뉴스 스크래핑
|
|
2223
|
+
async function scrapeDaumNews(category) {
|
|
2224
|
+
const categoryUrls = {
|
|
2225
|
+
general: 'https://news.daum.net/',
|
|
2226
|
+
politics: 'https://news.daum.net/politics',
|
|
2227
|
+
economy: 'https://news.daum.net/economic',
|
|
2228
|
+
society: 'https://news.daum.net/society',
|
|
2229
|
+
culture: 'https://news.daum.net/culture',
|
|
2230
|
+
tech: 'https://news.daum.net/digital',
|
|
2231
|
+
sports: 'https://sports.daum.net/',
|
|
2232
|
+
entertainment: 'https://entertain.daum.net/',
|
|
2233
|
+
};
|
|
2234
|
+
const url = categoryUrls[category] || categoryUrls.general;
|
|
2235
|
+
const response = await axios.get(url, {
|
|
2236
|
+
headers: {
|
|
2237
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
2238
|
+
'Accept-Language': 'ko-KR,ko;q=0.9',
|
|
2239
|
+
},
|
|
2240
|
+
timeout: 8000,
|
|
2241
|
+
});
|
|
2242
|
+
const $ = cheerio.load(response.data);
|
|
2243
|
+
const news = [];
|
|
2244
|
+
$('[class*="link_txt"], .tit_g, .news_view, .txt_info').each((i, el) => {
|
|
2245
|
+
if (news.length >= 10)
|
|
2246
|
+
return false;
|
|
2247
|
+
const headline = $(el).text().trim();
|
|
2248
|
+
if (headline && headline.length > 10 && headline.length < 100) {
|
|
2249
|
+
if (!news.find(n => n.headline === headline)) {
|
|
2250
|
+
news.push({
|
|
2251
|
+
headline,
|
|
2252
|
+
source: '다음뉴스',
|
|
2253
|
+
sentiment: analyzeSentiment(headline),
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
});
|
|
2258
|
+
return news;
|
|
2259
|
+
}
|
|
2260
|
+
// 감성 분석 (간단 버전)
|
|
2261
|
+
function analyzeSentiment(text) {
|
|
2262
|
+
const positiveWords = /성공|상승|호조|기대|돌파|신기록|수상|인기|사랑|행복|좋은|최고|혁신|성장/;
|
|
2263
|
+
const negativeWords = /하락|위기|우려|실패|폭락|충격|논란|피해|사망|사고|비난|급락|위험|문제/;
|
|
2264
|
+
if (positiveWords.test(text))
|
|
2265
|
+
return 'positive';
|
|
2266
|
+
if (negativeWords.test(text))
|
|
2267
|
+
return 'negative';
|
|
2268
|
+
return 'neutral';
|
|
2269
|
+
}
|
|
2270
|
+
// 뉴스 소스 감지
|
|
2271
|
+
function detectNewsSource(url, category) {
|
|
2272
|
+
if (url.includes('sports'))
|
|
2273
|
+
return '스포츠';
|
|
2274
|
+
if (url.includes('entertain'))
|
|
2275
|
+
return '연예';
|
|
2276
|
+
const sources = {
|
|
2277
|
+
politics: '정치',
|
|
2278
|
+
economy: '경제',
|
|
2279
|
+
society: '사회',
|
|
2280
|
+
culture: '문화',
|
|
2281
|
+
tech: 'IT/과학',
|
|
2282
|
+
sports: '스포츠',
|
|
2283
|
+
entertainment: '연예',
|
|
2284
|
+
};
|
|
2285
|
+
return sources[category] || '종합';
|
|
2286
|
+
}
|
|
2287
|
+
// 텍스트에서 키워드 추출
|
|
2288
|
+
function extractKeywordsFromText(text) {
|
|
2289
|
+
// 특수문자 제거 후 2-6자 단어만 추출
|
|
2290
|
+
const words = text
|
|
2291
|
+
.replace(/[^\w\sㄱ-ㅎㅏ-ㅣ가-힣]/g, ' ')
|
|
2292
|
+
.split(/\s+/)
|
|
2293
|
+
.filter(w => w.length >= 2 && w.length <= 6)
|
|
2294
|
+
.filter(w => !/^\d+$/.test(w)); // 숫자만 있는 것 제외
|
|
2295
|
+
return words;
|
|
2296
|
+
}
|
|
2297
|
+
// 키워드 빈도 계산
|
|
2298
|
+
function calculateKeywordFrequency(words) {
|
|
2299
|
+
const frequency = {};
|
|
2300
|
+
words.forEach(word => {
|
|
2301
|
+
frequency[word] = (frequency[word] || 0) + 1;
|
|
2302
|
+
});
|
|
2303
|
+
return Object.entries(frequency)
|
|
2304
|
+
.sort((a, b) => b[1] - a[1])
|
|
2305
|
+
.slice(0, 15)
|
|
2306
|
+
.map(([keyword, freq]) => ({
|
|
2307
|
+
keyword,
|
|
2308
|
+
frequency: freq,
|
|
2309
|
+
trend: freq >= 5 ? '상승' : freq >= 3 ? '유지' : '일반',
|
|
2310
|
+
}));
|
|
2311
|
+
}
|
|
2312
|
+
// 뉴스 기반 콘텐츠 기회 생성
|
|
2313
|
+
function generateNewsContentOpportunities(keywords, category) {
|
|
2314
|
+
const opportunities = [];
|
|
2315
|
+
if (keywords.length > 0) {
|
|
2316
|
+
const topKeyword = keywords[0]?.keyword || '';
|
|
2317
|
+
opportunities.push(`"${topKeyword}" 관련 콘텐츠 수요 증가 - 해설/분석 콘텐츠 추천`);
|
|
2318
|
+
}
|
|
2319
|
+
const categoryOpportunities = {
|
|
2320
|
+
tech: ['AI/테크 트렌드 정리 콘텐츠', '신제품 리뷰 콘텐츠'],
|
|
2321
|
+
economy: ['재테크 팁 콘텐츠', '경제 뉴스 쉽게 풀어주기'],
|
|
2322
|
+
entertainment: ['K-콘텐츠 글로벌 화제', '연예 이슈 정리'],
|
|
2323
|
+
sports: ['경기 하이라이트', '선수 인터뷰 분석'],
|
|
2324
|
+
general: ['오늘의 이슈 정리', '트렌드 분석 콘텐츠'],
|
|
2325
|
+
};
|
|
2326
|
+
opportunities.push(...(categoryOpportunities[category] || categoryOpportunities.general));
|
|
2327
|
+
return opportunities.slice(0, 5);
|
|
2328
|
+
}
|
|
2329
|
+
// 뉴스 Fallback 데이터
|
|
2330
|
+
function getNewsFallback(category) {
|
|
2331
|
+
const now = new Date();
|
|
2332
|
+
const dateStr = now.toISOString().split('T')[0];
|
|
2333
|
+
const fallbackData = {
|
|
1381
2334
|
general: [
|
|
1382
|
-
{ headline:
|
|
1383
|
-
{ headline: "
|
|
1384
|
-
{ headline: "
|
|
2335
|
+
{ headline: `${dateStr} 오늘의 주요 뉴스`, source: "종합", sentiment: "neutral" },
|
|
2336
|
+
{ headline: "AI 기술 발전과 산업 변화", source: "종합", sentiment: "neutral" },
|
|
2337
|
+
{ headline: "글로벌 경제 동향", source: "종합", sentiment: "neutral" },
|
|
1385
2338
|
],
|
|
1386
2339
|
tech: [
|
|
1387
|
-
{ headline: "
|
|
1388
|
-
{ headline: "
|
|
1389
|
-
{ headline: "사이버 보안
|
|
2340
|
+
{ headline: "AI 서비스 업데이트 소식", source: "IT/과학", sentiment: "positive" },
|
|
2341
|
+
{ headline: "테크 기업 신제품 발표", source: "IT/과학", sentiment: "positive" },
|
|
2342
|
+
{ headline: "사이버 보안 동향", source: "IT/과학", sentiment: "neutral" },
|
|
1390
2343
|
],
|
|
1391
2344
|
economy: [
|
|
1392
|
-
{ headline: "
|
|
1393
|
-
{ headline: "부동산 시장
|
|
1394
|
-
{ headline: "환율
|
|
2345
|
+
{ headline: "금융 시장 동향", source: "경제", sentiment: "neutral" },
|
|
2346
|
+
{ headline: "부동산 시장 분석", source: "경제", sentiment: "neutral" },
|
|
2347
|
+
{ headline: "환율 변동 현황", source: "경제", sentiment: "neutral" },
|
|
1395
2348
|
],
|
|
1396
2349
|
entertainment: [
|
|
1397
2350
|
{ 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-콘텐츠 글로벌 화제 - 한류 관련 콘텐츠",
|
|
2351
|
+
{ headline: "드라마/예능 화제작", source: "연예", sentiment: "positive" },
|
|
2352
|
+
{ headline: "연예계 소식", source: "연예", sentiment: "neutral" },
|
|
1425
2353
|
],
|
|
1426
|
-
trending_topics: news.map(n => n.headline),
|
|
1427
2354
|
};
|
|
2355
|
+
return fallbackData[category] || fallbackData.general;
|
|
1428
2356
|
}
|
|
1429
2357
|
// 해시태그 전략 생성
|
|
1430
2358
|
function generateAdvancedHashtagStrategy(topic, platform, count, includeKorean, includeEnglish) {
|