| 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 |
|
|
| |
| IS_HUGGINGFACE = os.getenv("SPACE_ID") is not None |
| print(f'IS_HUGGINGFACE = {IS_HUGGINGFACE}') |
|
|
| |
| 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}") |
| |
| |
| 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 ์ค์น ์์...") |
| |
| |
| 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 |
| |
| |
| if self.try_playwright(): |
| self.method = "playwright" |
| self.is_ready = True |
| return True |
| |
| |
| if self.try_selenium(): |
| self.method = "selenium" |
| self.is_ready = True |
| return True |
| |
| |
| 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 |
| |
| |
| 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_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 |
| |
| |
| 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}") |
| |
| 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 |
| |
| |
| 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() |
| |
| |
| 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_url = f"https://trends.google.com/trending/rss?geo=KR" |
| |
| response = self.session.get(rss_url, timeout=10) |
| response.raise_for_status() |
| |
| |
| 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 |
| |
| |
| 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: |
| |
| 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> |
| """ |
| |
| |
| 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 |
| |
| |
| 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_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 |
| ) |