hanjunjung
fix
1c2a588
raw
history blame
35.8 kB
import os
import time
import base64
import json
import traceback
import threading
import subprocess
import sys
from typing import Tuple, Optional
import gradio as gr
import anthropic
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
# Hugging Face Space ํ™˜๊ฒฝ ํ™•์ธ
IS_HUGGINGFACE = os.getenv("SPACE_ID") is not None
print(f'IS_HUGGINGFACE = {IS_HUGGINGFACE}')
# Claude API ์„ค์ •
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
if not ANTHROPIC_API_KEY:
print("ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ, Secret ํ™•์ธ ํ•„์š”")
else:
print("Claude API ํ‚ค ํ™•์ธ ์™„๋ฃŒ")
def create_claude_client():
try:
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
print("Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์„ฑ๊ณต")
return client
except Exception as e:
print(f"Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์‹คํŒจ: {e}")
return None
def install_system_dependencies():
"""์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„"""
try:
print("์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„ ์ค‘...")
# ํ•„์ˆ˜ ํŒจํ‚ค์ง€ ๋ชฉ๋ก
packages = [
"libnss3",
"libnspr4",
"libatk1.0-0",
"libatk-bridge2.0-0",
"libcups2",
"libatspi2.0-0",
"libxcomposite1",
"libxdamage1",
"libxrandr2",
"libgconf-2-4",
"libxss1",
"libgtk-3-0",
"libasound2"
]
# ํŒจํ‚ค์ง€ ์„ค์น˜ ์‹œ๋„ (๊ถŒํ•œ์ด ์žˆ๋Š” ๊ฒฝ์šฐ)
if not IS_HUGGINGFACE:
try:
subprocess.run(["sudo", "apt-get", "update"], check=True, capture_output=True)
subprocess.run(["sudo", "apt-get", "install", "-y"] + packages, check=True, capture_output=True)
print("์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์™„๋ฃŒ")
return True
except subprocess.CalledProcessError as e:
print(f"์‹œ์Šคํ…œ ํŒจํ‚ค์ง€ ์„ค์น˜ ์‹คํŒจ: {e}")
# Hugging Face Space์—์„œ๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์‹œ๋„
if IS_HUGGINGFACE:
print("Hugging Face Space: ์‹œ์Šคํ…œ ํŒจํ‚ค์ง€ ์„ค์น˜ ๊ถŒํ•œ ์—†์Œ")
return False
except Exception as e:
print(f"์˜์กด์„ฑ ์„ค์น˜ ์ค‘ ์˜ค๋ฅ˜: {e}")
return False
def install_playwright_with_deps():
"""Playwright์™€ ์˜์กด์„ฑ ์„ค์น˜"""
try:
print("Playwright ์„ค์น˜ ์‹œ์ž‘...")
# Playwright ์„ค์น˜
result = subprocess.run([
sys.executable, "-m", "pip", "install", "playwright==1.40.0"
], capture_output=True, text=True, timeout=300)
if result.returncode != 0:
print(f"Playwright pip ์„ค์น˜ ์‹คํŒจ: {result.stderr}")
return False
# ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜
result = subprocess.run([
sys.executable, "-m", "playwright", "install", "chromium"
], capture_output=True, text=True, timeout=300)
if result.returncode != 0:
print(f"Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜ ์‹คํŒจ: {result.stderr}")
return False
# ์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„
try:
result = subprocess.run([
sys.executable, "-m", "playwright", "install-deps", "chromium"
], capture_output=True, text=True, timeout=300)
if result.returncode == 0:
print("Playwright ์˜์กด์„ฑ ์„ค์น˜ ์™„๋ฃŒ")
return True
else:
print(f"Playwright ์˜์กด์„ฑ ์„ค์น˜ ์‹คํŒจ: {result.stderr}")
print("์ˆ˜๋™ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„...")
return install_system_dependencies()
except subprocess.CalledProcessError:
print("Playwright install-deps ์‹คํŒจ, ์ˆ˜๋™ ์„ค์น˜ ์‹œ๋„")
return install_system_dependencies()
except Exception as e:
print(f"Playwright ์„ค์น˜ ์ค‘ ์˜ค๋ฅ˜: {e}")
return False
class SmartGoogleTrendsAutomator:
"""์Šค๋งˆํŠธ Google Trends ์ž๋™ํ™” (์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ• ์‹œ๋„)"""
def __init__(self):
self.method = None
self.browser = None
self.page = None
self.driver = None
self.is_ready = False
self.lock = threading.Lock()
def initialize_browser(self):
"""๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” (์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ• ์‹œ๋„)"""
with self.lock:
if self.is_ready:
return True
# ๋ฐฉ๋ฒ• 1: Playwright ์‹œ๋„
if self.try_playwright():
self.method = "playwright"
self.is_ready = True
return True
# ๋ฐฉ๋ฒ• 2: Selenium ์‹œ๋„
if self.try_selenium():
self.method = "selenium"
self.is_ready = True
return True
# ๋ฐฉ๋ฒ• 3: ๋Œ€์ฒด ๋ฐฉ๋ฒ• (API ๊ธฐ๋ฐ˜)
if self.try_alternative():
self.method = "alternative"
self.is_ready = True
return True
return False
def try_playwright(self):
"""Playwright ์ดˆ๊ธฐํ™” ์‹œ๋„"""
try:
print("Playwright ๋ฐฉ๋ฒ• ์‹œ๋„ ์ค‘...")
# ์˜์กด์„ฑ ์„ค์น˜
if not install_playwright_with_deps():
print("Playwright ์˜์กด์„ฑ ์„ค์น˜ ์‹คํŒจ")
return False
# Playwright ์ž„ํฌํŠธ
from playwright.sync_api import sync_playwright
# ๋ธŒ๋ผ์šฐ์ € ์‹œ์ž‘
self.playwright = sync_playwright().start()
self.browser = self.playwright.chromium.launch(
headless=True,
args=[
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--disable-software-rasterizer',
'--disable-web-security',
'--disable-features=VizDisplayCompositor'
]
)
self.page = self.browser.new_page()
self.page.set_user_agent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
)
# ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€ ๋กœ๋“œ
self.page.goto("https://trends.google.com/trending?geo=KR&hl=ko", timeout=30000)
self.page.wait_for_load_state('networkidle', timeout=15000)
print("Playwright ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
return True
except Exception as e:
print(f"Playwright ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
self.cleanup_playwright()
return False
def try_selenium(self):
"""Selenium ์ดˆ๊ธฐํ™” ์‹œ๋„"""
try:
print("Selenium ๋ฐฉ๋ฒ• ์‹œ๋„ ์ค‘...")
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-gpu")
options.add_argument("--disable-software-rasterizer")
# Chrome ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฒฝ๋กœ ์ˆ˜๋™ ์„ค์ • ์‹œ๋„
chrome_paths = [
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable",
"/usr/bin/chromium-browser",
"/usr/bin/chromium",
"/opt/google/chrome/google-chrome"
]
for chrome_path in chrome_paths:
if os.path.exists(chrome_path):
options.binary_location = chrome_path
print(f"Chrome ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐœ๊ฒฌ: {chrome_path}")
break
# webdriver-manager ์‹œ๋„
try:
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
service = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=service, options=options)
except Exception as wm_error:
print(f"webdriver-manager ์‹คํŒจ: {wm_error}")
# ์‹œ์Šคํ…œ chromedriver ์‹œ๋„
self.driver = webdriver.Chrome(options=options)
# ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€ ๋กœ๋“œ
self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
print("Selenium ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
return True
except Exception as e:
print(f"Selenium ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
self.cleanup_selenium()
return False
def try_alternative(self):
"""๋Œ€์ฒด ๋ฐฉ๋ฒ• (API/์Šคํฌ๋ž˜ํ•‘ ๊ธฐ๋ฐ˜)"""
try:
print("๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‹œ๋„ ์ค‘...")
import requests
from bs4 import BeautifulSoup
# Google Trends RSS ์‹œ๋„
rss_url = "https://trends.google.com/trending/rss?geo=KR"
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
})
response = session.get(rss_url, timeout=10)
response.raise_for_status()
# RSS ํŒŒ์‹ฑ ํ…Œ์ŠคํŠธ
from xml.etree import ElementTree as ET
root = ET.fromstring(response.content)
# ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ ํ™•์ธ
items = root.findall('.//item')
if len(items) > 0:
print(f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์„ฑ๊ณต: {len(items)}๊ฐœ ํŠธ๋ Œ๋“œ ๋ฐœ๊ฒฌ")
self.session = session
return True
return False
except Exception as e:
print(f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‹คํŒจ: {e}")
return False
def capture_screenshot(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
"""์„ ํƒ๋œ ๋ฐฉ๋ฒ•์œผ๋กœ ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜"""
if not self.is_ready:
if not self.initialize_browser():
return None, False, "๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ๋ฐฉ๋ฒ• ์‹คํŒจ"
try:
if self.method == "playwright":
return self.capture_with_playwright(region, period, category)
elif self.method == "selenium":
return self.capture_with_selenium(region, period, category)
elif self.method == "alternative":
return self.capture_with_alternative(region, period, category)
else:
return None, False, "์•Œ ์ˆ˜ ์—†๋Š” ๋ฐฉ๋ฒ•"
except Exception as e:
return None, False, f"์บก์ฒ˜ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
def capture_with_playwright(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
"""Playwright๋กœ ์บก์ฒ˜"""
try:
# ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
self.page.reload(wait_until='domcontentloaded')
self.page.wait_for_load_state('networkidle', timeout=10000)
# ์„ค์ • ๋ณ€๊ฒฝ (๊ฐ„๋‹จํ•œ ๋ฒ„์ „)
time.sleep(2)
# ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
screenshot_bytes = self.page.screenshot(full_page=False)
screenshot_b64 = base64.b64encode(screenshot_bytes).decode()
return screenshot_b64, True, f"Playwright ์บก์ฒ˜ ์„ฑ๊ณต: {region}"
except Exception as e:
return None, False, f"Playwright ์บก์ฒ˜ ์‹คํŒจ: {str(e)}"
def capture_with_selenium(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
"""Selenium์œผ๋กœ ์บก์ฒ˜"""
try:
# ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
self.driver.refresh()
time.sleep(3)
# ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
screenshot = self.driver.get_screenshot_as_png()
screenshot_b64 = base64.b64encode(screenshot).decode()
return screenshot_b64, True, f"Selenium ์บก์ฒ˜ ์„ฑ๊ณต: {region}"
except Exception as e:
return None, False, f"Selenium ์บก์ฒ˜ ์‹คํŒจ: {str(e)}"
def capture_with_alternative(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
"""๋Œ€์ฒด ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘"""
try:
# RSS ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘
rss_url = f"https://trends.google.com/trending/rss?geo=KR"
response = self.session.get(rss_url, timeout=10)
response.raise_for_status()
# ๊ฐ€์งœ ์Šคํฌ๋ฆฐ์ƒท ์ƒ์„ฑ (์‹ค์ œ๋กœ๋Š” HTML ๊ธฐ๋ฐ˜ ์‹œ๊ฐํ™”)
fake_screenshot = self.generate_fake_screenshot(response.content, region)
return fake_screenshot, True, f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์„ฑ๊ณต: {region}"
except Exception as e:
return None, False, f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‹คํŒจ: {str(e)}"
def generate_fake_screenshot(self, rss_content: bytes, region: str) -> str:
"""RSS ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ€์งœ ์Šคํฌ๋ฆฐ์ƒท ์ƒ์„ฑ"""
try:
from xml.etree import ElementTree as ET
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from io import BytesIO
# RSS ํŒŒ์‹ฑ
root = ET.fromstring(rss_content)
trends = []
for item in root.findall('.//item')[:10]:
title = item.find('title')
if title is not None:
trends.append(title.text)
# ์ฐจํŠธ ์ƒ์„ฑ
fig, ax = plt.subplots(figsize=(12, 8))
fig.patch.set_facecolor('white')
# ํ•œ๊ธ€ ํฐํŠธ ์„ค์ • ์‹œ๋„
try:
plt.rcParams['font.family'] = 'DejaVu Sans'
except:
pass
# ํŠธ๋ Œ๋“œ ์ฐจํŠธ
y_pos = range(len(trends))
values = [100 - i*10 for i in range(len(trends))]
bars = ax.barh(y_pos, values, color='#4285f4')
ax.set_yticks(y_pos)
ax.set_yticklabels(trends[::-1]) # ์—ญ์ˆœ์œผ๋กœ ํ‘œ์‹œ
ax.set_xlabel('๊ฒ€์ƒ‰ ๊ด€์‹ฌ๋„')
ax.set_title(f'{region} ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด')
# ๊ทธ๋ž˜ํ”„๋ฅผ ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜
buffer = BytesIO()
plt.savefig(buffer, format='png', dpi=100, bbox_inches='tight',
facecolor='white', edgecolor='none')
buffer.seek(0)
screenshot_b64 = base64.b64encode(buffer.getvalue()).decode()
plt.close(fig)
return screenshot_b64
except Exception as e:
print(f"๊ฐ€์งœ ์Šคํฌ๋ฆฐ์ƒท ์ƒ์„ฑ ์‹คํŒจ: {e}")
# ๋นˆ ์ด๋ฏธ์ง€ ๋ฐ˜ํ™˜
return ""
def cleanup_playwright(self):
"""Playwright ์ •๋ฆฌ"""
try:
if hasattr(self, 'page') and self.page:
self.page.close()
if hasattr(self, 'browser') and self.browser:
self.browser.close()
if hasattr(self, 'playwright') and self.playwright:
self.playwright.stop()
except:
pass
def cleanup_selenium(self):
"""Selenium ์ •๋ฆฌ"""
try:
if hasattr(self, 'driver') and self.driver:
self.driver.quit()
except:
pass
def cleanup(self):
"""๋ชจ๋“  ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ"""
self.cleanup_playwright()
self.cleanup_selenium()
self.is_ready = False
# ๊ธ€๋กœ๋ฒŒ ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค
automator = SmartGoogleTrendsAutomator()
def analyze_with_claude(screenshot_b64: str, region: str, period: str, category: str) -> str:
"""Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„"""
try:
if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
return generate_fallback_analysis(region, period, category)
claude_client = create_claude_client()
if claude_client is None:
return generate_fallback_analysis(region, period, category)
# ๋นˆ ์Šคํฌ๋ฆฐ์ƒท์ธ ๊ฒฝ์šฐ ํ…์ŠคํŠธ ๋ถ„์„๋งŒ
if not screenshot_b64:
return generate_fallback_analysis(region, period, category)
prompt = f"""
์ด Google Trends ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
์„ค์ •: {region} | {period} | {category}
๋‹ค์Œ์„ HTML ํ‘œ ํ˜•ํƒœ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ •๋ฆฌํ•ด์ฃผ์„ธ์š”:
1. ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ์ƒ์œ„ 10๊ฐœ (์ˆœ์œ„, ๊ฒ€์ƒ‰์–ด, ๊ฒ€์ƒ‰๋Ÿ‰, ์ƒ์Šน๋ฅ )
2. ์ฃผ์š” ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ 3-5๊ฐœ์˜ ํŠน์ง• ์„ค๋ช…
3. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํŠน์ด์‚ฌํ•ญ (์žˆ๋Š” ๊ฒฝ์šฐ)
HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์‹œ๊ฐ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
ํ•œ๊ธ€ ํ…์ŠคํŠธ๋ฅผ ์ •ํ™•ํžˆ ์ฝ์–ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
"""
message = claude_client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": prompt
},
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": screenshot_b64
}
}
]
}
]
)
return message.content[0].text
except Exception as e:
return generate_fallback_analysis(region, period, category, str(e))
def generate_fallback_analysis(region: str, period: str, category: str, error: str = None) -> str:
"""๋Œ€์ฒด ๋ถ„์„ ๊ฒฐ๊ณผ ์ƒ์„ฑ"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
html = f"""
<div style="font-family: 'Segoe UI', sans-serif; max-width: 1000px; margin: 0 auto;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px;">
<h2>{region} ์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ (๋Œ€์ฒด ๋ชจ๋“œ)</h2>
<p>๋ถ„์„ ์‹œ๊ฐ: {timestamp}</p>
<p>์„ค์ •: {region} | {period} | {category}</p>
</div>
<div style="background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin-bottom: 20px;">
<h4>์•Œ๋ฆผ: ๋Œ€์ฒด ๋ถ„์„ ๋ชจ๋“œ</h4>
<p>๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ์บก์ฒ˜๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜์—ฌ ๋Œ€์ฒด ๋ฐฉ๋ฒ•์œผ๋กœ ๋ถ„์„์„ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.</p>
{f"<p><strong>์˜ค๋ฅ˜ ์ •๋ณด:</strong> {error}</p>" if error else ""}
</div>
<h3>์˜ˆ์ƒ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด (์ฐธ๊ณ ์šฉ)</h3>
<table style="width: 100%; border-collapse: collapse; margin-bottom: 30px;">
<thead>
<tr style="background-color: #f8f9fa;">
<th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">์ˆœ์œ„</th>
<th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">๊ฒ€์ƒ‰์–ด</th>
<th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">์˜ˆ์ƒ ๊ด€์‹ฌ๋„</th>
<th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">์นดํ…Œ๊ณ ๋ฆฌ</th>
</tr>
</thead>
<tbody>
"""
# ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
sample_trends = [
("๋‰ด์Šค ์ด์Šˆ", "๋†’์Œ", "๋‰ด์Šค"),
("์—ฐ์˜ˆ๊ณ„ ์†Œ์‹", "๋†’์Œ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ"),
("์Šคํฌ์ธ  ๊ฒฝ๊ธฐ", "์ค‘๊ฐ„", "์Šคํฌ์ธ "),
("๊ธฐ์ˆ  ๋‰ด์Šค", "์ค‘๊ฐ„", "๊ธฐ์ˆ "),
("๊ฑด๊ฐ• ์ •๋ณด", "๋‚ฎ์Œ", "๊ฑด๊ฐ•"),
]
for i, (keyword, interest, cat) in enumerate(sample_trends, 1):
html += f"""
<tr>
<td style="border: 1px solid #dee2e6; padding: 12px;">{i}</td>
<td style="border: 1px solid #dee2e6; padding: 12px; font-weight: bold;">{keyword}</td>
<td style="border: 1px solid #dee2e6; padding: 12px;">{interest}</td>
<td style="border: 1px solid #dee2e6; padding: 12px;">{cat}</td>
</tr>
"""
html += """
</tbody>
</table>
<h3>๋ถ„์„ ์ฐธ๊ณ ์‚ฌํ•ญ</h3>
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px;">
<ul>
<li><strong>๋ฐ์ดํ„ฐ ํ•œ๊ณ„:</strong> ์‹ค์ œ Google Trends ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ ์˜ˆ์ƒ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.</li>
<li><strong>๋ธŒ๋ผ์šฐ์ € ์ด์Šˆ:</strong> ์‹œ์Šคํ…œ ํ™˜๊ฒฝ์—์„œ ๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰์ด ์ œํ•œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</li>
<li><strong>๊ถŒ์žฅ์‚ฌํ•ญ:</strong> ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ด๋ณด์„ธ์š”.</li>
</ul>
</div>
</div>
"""
return html
def smart_analysis(region: str, period: str, category: str):
"""์Šค๋งˆํŠธ ๋ถ„์„ ํ•จ์ˆ˜ (์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ• ์‹œ๋„)"""
start_time = time.time()
yield f"""
<div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
<h3>์Šค๋งˆํŠธ Google Trends ๋ถ„์„</h3>
<p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
<p>์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•˜์—ฌ ์ตœ์ ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค</p>
</div>
"""
try:
# 1๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”
yield f"""
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>1๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘...</h4>
<p>Playwright โ†’ Selenium โ†’ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์ˆœ์„œ๋กœ ์‹œ๋„</p>
</div>
"""
# ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”
init_success = automator.initialize_browser()
init_time = time.time() - start_time
if init_success:
yield f"""
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: {automator.method.upper()} ์ดˆ๊ธฐํ™” ์„ฑ๊ณต ({init_time:.1f}์ดˆ)</h4>
<p>์„ ํƒ๋œ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค</p>
</div>
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์ค‘...</h4>
<p>Google Trends์—์„œ {region} ์ง€์—ญ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค</p>
</div>
"""
else:
yield f"""
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FFA500 0%, #FF8C00 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>1๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์‹คํŒจ ({init_time:.1f}์ดˆ)</h4>
<p>๋Œ€์ฒด ๋ฐฉ๋ฒ•์œผ๋กœ ๋ถ„์„์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค</p>
</div>
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>2๋‹จ๊ณ„: ๋Œ€์ฒด ๋ถ„์„ ์ค‘...</h4>
<p>๋ธŒ๋ผ์šฐ์ € ์—†์ด ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„์—์„œ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค</p>
</div>
"""
# 2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ์บก์ฒ˜
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(
automator.capture_screenshot,
region, period, category
)
# ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
while not future.done():
elapsed = time.time() - start_time
yield f"""
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: {automator.method.upper() if automator.method else '๋Œ€์ฒด๋ฐฉ๋ฒ•'} ({init_time:.1f}์ดˆ)</h4>
</div>
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์ค‘... ({elapsed:.1f}์ดˆ)</h4>
<p>ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค</p>
</div>
"""
time.sleep(0.5)
screenshot_b64, success, status_msg = future.result()
capture_time = time.time() - start_time
# 3๋‹จ๊ณ„: AI ๋ถ„์„
yield f"""
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>2๋‹จ๊ณ„ ์™„๋ฃŒ: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ {"์„ฑ๊ณต" if success else "๋ถ€๋ถ„ ์„ฑ๊ณต"} ({capture_time:.1f}์ดˆ)</h4>
<p>{status_msg}</p>
</div>
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #9C27B0 0%, #8E24AA 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ค‘...</h4>
<p>Claude AI๊ฐ€ ์ˆ˜์ง‘๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค</p>
</div>
"""
# AI ๋ถ„์„ ์‹คํ–‰
ai_start_time = time.time()
with ThreadPoolExecutor(max_workers=1) as executor:
ai_future = executor.submit(
analyze_with_claude,
screenshot_b64, region, period, category
)
while not ai_future.done():
ai_elapsed = time.time() - ai_start_time
total_elapsed = time.time() - start_time
yield f"""
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>2๋‹จ๊ณ„ ์™„๋ฃŒ: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ {"์„ฑ๊ณต" if success else "๋ถ€๋ถ„ ์„ฑ๊ณต"} ({capture_time:.1f}์ดˆ)</h4>
<p>{status_msg}</p>
</div>
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #9C27B0 0%, #8E24AA 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
<h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ค‘... ({ai_elapsed:.1f}์ดˆ)</h4>
<p>์ด ๊ฒฝ๊ณผ ์‹œ๊ฐ„: {total_elapsed:.1f}์ดˆ</p>
</div>
"""
time.sleep(0.5)
analysis_result = ai_future.result()
total_time = time.time() - start_time
# ์ตœ์ข… ๊ฒฐ๊ณผ
yield f"""
<div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
<h3>์Šค๋งˆํŠธ ๋ถ„์„ ์™„๋ฃŒ ({total_time:.1f}์ดˆ)</h3>
<p><strong>์‚ฌ์šฉ ๋ฐฉ๋ฒ•:</strong> {automator.method.upper() if automator.method else '๋Œ€์ฒด๋ฐฉ๋ฒ•'} | <strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
</div>
{analysis_result}
<div style="margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center;">
<p style="margin: 0; font-size: 14px;">
<strong>์Šค๋งˆํŠธ ๋ถ„์„์˜ ์žฅ์ </strong><br>
์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์ž๋™์œผ๋กœ ์‹œ๋„ํ•˜์—ฌ ํ™˜๊ฒฝ์— ๊ด€๊ณ„์—†์ด ์ตœ์ ์˜ ๊ฒฐ๊ณผ ์ œ๊ณต
</p>
</div>
<div style="margin-top: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 5px; font-size: 12px; color: #666; text-align: center;">
<p style="margin: 5px 0;"><strong>์‚ฌ์šฉ๋œ ๋ฐฉ๋ฒ•:</strong> {automator.method.upper() if automator.method else '๋Œ€์ฒด๋ฐฉ๋ฒ•'}</p>
<p style="margin: 5px 0;"><strong>์ด ๋ถ„์„ ์‹œ๊ฐ„:</strong> {total_time:.1f}์ดˆ</p>
<p style="margin: 5px 0;"><strong>ํ™˜๊ฒฝ:</strong> {"Hugging Face Space" if IS_HUGGINGFACE else "๋กœ์ปฌ"}</p>
</div>
"""
print(f"์Šค๋งˆํŠธ ๋ถ„์„ ์™„๋ฃŒ: {total_time:.1f}์ดˆ, ๋ฐฉ๋ฒ•: {automator.method}")
except Exception as e:
error_details = traceback.format_exc()
print(f"์Šค๋งˆํŠธ ๋ถ„์„ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
yield f"""
<div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
<h3>์Šค๋งˆํŠธ ๋ถ„์„ ์˜ค๋ฅ˜</h3>
<p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
<details>
<summary>์ƒ์„ธ ์˜ค๋ฅ˜ ์ •๋ณด</summary>
<pre style="background-color: #f5f5f5; padding: 10px; margin-top: 10px; overflow-x: auto; font-size: 11px;">
{error_details}
</pre>
</details>
<p><strong>ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:</strong> ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</p>
</div>
"""
def create_smart_interface():
"""์Šค๋งˆํŠธ Gradio ์ธํ„ฐํŽ˜์ด์Šค"""
with gr.Blocks(
theme=gr.themes.Soft(),
title="์Šค๋งˆํŠธ Google Trends ๋ถ„์„๊ธฐ",
css="""
.gradio-container {
max-width: 1200px !important;
}
.output-html {
max-height: none !important;
}
"""
) as interface:
gr.HTML("""
<div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
<h1>์Šค๋งˆํŠธ Google Trends ๋ถ„์„๊ธฐ</h1>
<p><strong>๋‹ค์ค‘ ๋ฐฉ๋ฒ• ์ง€์›:</strong> Playwright โ†’ Selenium โ†’ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์ž๋™ ์„ ํƒ</p>
<p style="font-size: 14px; opacity: 0.9;">ํ™˜๊ฒฝ์— ๊ด€๊ณ„์—†์ด ์ตœ์ ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค</p>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
region_input = gr.Dropdown(
choices=["๋Œ€ํ•œ๋ฏผ๊ตญ", "์ „์„ธ๊ณ„", "๋ฏธ๊ตญ", "์ผ๋ณธ", "์ค‘๊ตญ"],
value="๋Œ€ํ•œ๋ฏผ๊ตญ",
label="์ง€์—ญ ์„ ํƒ"
)
with gr.Column(scale=1):
period_input = gr.Dropdown(
choices=["์ง€๋‚œ 24์‹œ๊ฐ„", "์ง€๋‚œ 1์‹œ๊ฐ„", "์ง€๋‚œ 4์‹œ๊ฐ„", "์ง€๋‚œ 1์ผ", "์ง€๋‚œ 7์ผ"],
value="์ง€๋‚œ 7์ผ",
label="๊ธฐ๊ฐ„ ์„ ํƒ"
)
with gr.Column(scale=1):
category_input = gr.Dropdown(
choices=["๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ", "๊ฒŒ์ž„", "๊ฑด๊ฐ•", "๊ธฐ์ˆ ", "์Šคํฌ์ธ ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "๋‰ด์Šค", "๋น„์ฆˆ๋‹ˆ์Šค"],
value="๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ",
label="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ"
)
analyze_btn = gr.Button(
"์Šค๋งˆํŠธ ๋ถ„์„ ์‹œ์ž‘ (์ž๋™ ๋ฐฉ๋ฒ• ์„ ํƒ)",
variant="primary",
size="lg"
)
output = gr.HTML(
label="์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ฒฐ๊ณผ",
value="""
<div style="text-align: center; padding: 20px; border: 2px dashed #ccc; border-radius: 10px; color: #666;">
<h3>์Šค๋งˆํŠธ ๋ถ„์„๊ธฐ ๋Œ€๊ธฐ ์ค‘</h3>
<p><strong>์ž๋™ ๋ฐฉ๋ฒ• ์„ ํƒ:</strong> ํ™˜๊ฒฝ์— ๋งž๋Š” ์ตœ์ ์˜ ๋ฐฉ๋ฒ•์„ ์ž๋™์œผ๋กœ ์„ ํƒ</p>
<p><strong>์ง€์› ๋ฐฉ๋ฒ•:</strong> Playwright, Selenium, ๋Œ€์ฒด ๋ฐฉ๋ฒ•</p>
<p style="font-size: 14px;">์œ„์˜ ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!</p>
</div>
"""
)
analyze_btn.click(
fn=smart_analysis,
inputs=[region_input, period_input, category_input],
outputs=output,
show_progress="hidden"
)
gr.HTML("""
<div style="margin-top: 30px; padding: 20px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px;">
<h3 style="margin-top: 0; color: #333;">์Šค๋งˆํŠธ ๋ถ„์„๊ธฐ์˜ ํŠน์ง•</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: #555;">
<div>
<h4>์ž๋™ ๋ฐฉ๋ฒ• ์„ ํƒ</h4>
<ul style="margin: 0; padding-left: 20px;">
<li>Playwright ์šฐ์„  ์‹œ๋„</li>
<li>์‹คํŒจ์‹œ Selenium ์‹œ๋„</li>
<li>์ตœ์ข…์ ์œผ๋กœ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‚ฌ์šฉ</li>
</ul>
</div>
<div>
<h4>ํ™˜๊ฒฝ ํ˜ธํ™˜์„ฑ</h4>
<ul style="margin: 0; padding-left: 20px;">
<li>Hugging Face Space ์™„๋ฒฝ ์ง€์›</li>
<li>๋กœ์ปฌ ํ™˜๊ฒฝ ์ตœ์ ํ™”</li>
<li>์‹œ์Šคํ…œ ์ œ์•ฝ ์ž๋™ ํ•ด๊ฒฐ</li>
</ul>
</div>
</div>
</div>
""")
return interface
# ์•ฑ ์ข…๋ฃŒ์‹œ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ
import atexit
def cleanup_on_exit():
"""์•ฑ ์ข…๋ฃŒ์‹œ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ"""
global automator
if automator:
automator.cleanup()
print("์•ฑ ์ข…๋ฃŒ: ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ € ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ ์™„๋ฃŒ")
atexit.register(cleanup_on_exit)
if __name__ == "__main__":
print("=" * 50)
print("์Šค๋งˆํŠธ Google Trends ๋ถ„์„๊ธฐ ์‹œ์ž‘")
print("=" * 50)
if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
print("ANTHROPIC_API_KEY ๋ฏธ์„ค์ • - ๋Œ€์ฒด ๋ถ„์„ ๋ชจ๋“œ")
else:
print("Claude API ํ‚ค ํ™•์ธ๋จ")
print("์Šค๋งˆํŠธ Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
app = create_smart_interface()
if os.getenv("SPACE_ID"):
print("Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰")
app.launch(
server_name="0.0.0.0",
server_port=7860,
show_error=True,
show_api=False
)
else:
print("๋กœ์ปฌ ์„œ๋ฒ„ ์‹œ์ž‘: http://localhost:7860")
app.launch(
server_name="127.0.0.1",
server_port=7860,
show_error=True,
show_api=False,
share=False,
inbrowser=True
)