Files
2nd/_company/_agents/youtube/tools/trend_sniper.py
T
2026-05-07 22:33:12 +09:00

152 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""Trend Sniper — pulls top YouTube videos for target keywords, asks a local
LLM (Ollama/LM Studio) to extract the algorithmic patterns, and writes a
planning report next to this script.
Shared keys (API key, OLLAMA_URL, MODEL) come from youtube_account.json so
you only set them once. Per-tool keys (TARGET_KEYWORDS) come from
trend_sniper.json. If a key exists in both, trend_sniper.json wins.
Requires: pip install google-api-python-client requests
"""
import os, json, time, random, datetime, sys
HERE = os.path.dirname(os.path.abspath(__file__))
CONFIG_PATH = os.path.join(HERE, "trend_sniper.json")
ACCOUNT_PATH = os.path.join(HERE, "youtube_account.json")
REPORT_PATH = os.path.join(HERE, "trend_sniper_report.md")
def load_config():
try:
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
print(f"❌ 설정 파일을 읽을 수 없어요: {CONFIG_PATH}\n{e}")
sys.exit(1)
def load_account():
try:
if os.path.exists(ACCOUNT_PATH):
with open(ACCOUNT_PATH, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
pass
return {}
def _shared(cfg, acct, key, default=""):
"""Per-tool config wins; falls back to shared account; finally default."""
v = cfg.get(key)
if v not in (None, "", []):
return v
v = acct.get(key)
if v not in (None, "", []):
return v
return default
def main():
cfg = load_config()
acct = load_account()
api_key = (_shared(cfg, acct, "YOUTUBE_API_KEY") or "").strip()
if not api_key:
print("⚠️ YOUTUBE_API_KEY가 비어있어요. youtube_account.json 또는 trend_sniper.json에 입력하세요.")
print(" 발급: https://console.cloud.google.com/ → YouTube Data API v3 사용 설정 → 사용자 인증 정보 → API 키")
sys.exit(1)
target_keywords = cfg.get("TARGET_KEYWORDS", [])
if not target_keywords:
print("⚠️ TARGET_KEYWORDS가 비어있어요. 분석할 키워드를 1개 이상 추가하세요.")
sys.exit(1)
ollama_url = (_shared(cfg, acct, "OLLAMA_URL", "http://127.0.0.1:11434") or "http://127.0.0.1:11434").rstrip("/")
model = _shared(cfg, acct, "MODEL", "") or ""
pick = min(2, len(target_keywords))
chosen = random.sample(target_keywords, pick)
try:
from googleapiclient.discovery import build
except ImportError:
print("❌ google-api-python-client가 설치되지 않았어요.")
print(" 설치: pip install google-api-python-client requests")
sys.exit(1)
try:
import requests
except ImportError:
print("❌ requests가 설치되지 않았어요. pip install requests")
sys.exit(1)
print(f"\n🎯 [트렌드 스나이퍼] 키워드 {chosen} 스캔 시작...")
youtube = build('youtube', 'v3', developerKey=api_key)
last_month = (datetime.datetime.utcnow() - datetime.timedelta(days=30)).isoformat("T") + "Z"
sniper_data = []
for q in chosen:
print(f"📡 [{q}] 검색 중...")
try:
req = youtube.search().list(
part="snippet", q=q, maxResults=5, order="viewCount",
publishedAfter=last_month, type="video"
)
res = req.execute()
for item in res.get('items', []):
title = item['snippet']['title']
channel = item['snippet']['channelTitle']
sniper_data.append(f"[{q}] 채널: {channel} | 제목: {title}")
except Exception as e:
print(f"❌ 검색 오류 ({q}): {e}")
if not sniper_data:
print("❌ 수집된 데이터 없음. API 키 한도/네트워크 확인.")
sys.exit(1)
data_text = "\n".join(sniper_data)
prompt = f"""당신은 유튜브 알고리즘 마스터마인드입니다. 아래는 최근 30일 떡상 영상입니다.
[키워드] {', '.join(chosen)}
[데이터]
{data_text}
분석해서 마크다운 보고서를 작성하세요. 반드시 3섹션:
1. 🌍 트렌드 해킹 분석 — 어떤 패턴이 조회수를 끌고 있는지
2. 🎯 빈집 털기 전략 — 차별화 가능한 틈새 주제
3. 🎬 파괴적 영상 기획안 — 썸네일 카피, 제목 3개, 후킹 오프닝(첫 5초)
"""
print("🧠 [LLM 분석 중...]")
if not model:
# Try first available model
try:
r = requests.get(f"{ollama_url}/api/tags", timeout=5)
r.raise_for_status()
models = [m["name"] for m in r.json().get("models", [])]
if not models:
print("❌ 로컬 LLM에 설치된 모델이 없어요. Ollama/LM Studio에서 모델을 풀(pull)하세요.")
sys.exit(1)
model = models[0]
except Exception as e:
print(f"❌ 로컬 LLM 연결 실패 ({ollama_url}): {e}")
sys.exit(1)
try:
r = requests.post(
f"{ollama_url}/api/generate",
json={"model": model, "prompt": prompt, "stream": False},
timeout=180,
)
r.raise_for_status()
report = r.json().get("response", "").strip()
except Exception as e:
print(f"❌ LLM 호출 실패: {e}")
sys.exit(1)
print("\n" + "="*60)
print(report)
print("="*60)
with open(REPORT_PATH, "a", encoding="utf-8") as f:
now = time.strftime('%Y-%m-%d %H:%M:%S')
f.write(f"\n\n# 🎯 트렌드 스나이핑 보고서 — {now}\n")
f.write(f"## 📡 키워드: {', '.join(chosen)}\n\n")
f.write(report)
f.write("\n\n---\n")
print(f"\n✅ 보고서 저장: {REPORT_PATH}")
if __name__ == "__main__":
main()