| import os |
| import time |
| import base64 |
| import io |
| import threading |
| import atexit |
| import traceback |
| import asyncio |
| import concurrent.futures |
| from datetime import datetime |
| from typing import Optional, Dict, Any, Tuple |
|
|
| import gradio as gr |
| import anthropic |
| from playwright.sync_api import sync_playwright |
| from PIL import Image |
| from dotenv import load_dotenv |
|
|
| |
| load_dotenv() |
|
|
| |
| analyzed_data = {} |
| last_update_time = None |
| update_thread = None |
| is_updating = False |
|
|
| def get_claude_client(): |
| """Claude API ν΄λΌμ΄μΈνΈ μ΄κΈ°ν""" |
| api_key = os.getenv("ANTHROPIC_API_KEY") |
| if not api_key: |
| raise ValueError("ANTHROPIC_API_KEY νκ²½λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
| |
| return anthropic.Anthropic( |
| api_key=api_key, |
| max_retries=3, |
| timeout=60.0 |
| ) |
|
|
| def install_browsers(): |
| """Playwright λΈλΌμ°μ μ€μΉ""" |
| try: |
| print("Playwright λΈλΌμ°μ μ€μΉ μ€...") |
| os.system("playwright install chromium --force") |
| print("λΈλΌμ°μ μ€μΉ μλ£!") |
| return True |
| except Exception as e: |
| print(f"λΈλΌμ°μ μ€μΉ μ€ν¨: {e}") |
| return False |
|
|
| def create_browser_for_thread(): |
| """μ€λ λλ³ λ
립μ μΈ λΈλΌμ°μ μΈμ€ν΄μ€ μμ±""" |
| try: |
| print(f"[Thread {threading.current_thread().name}] λΈλΌμ°μ μΈμ€ν΄μ€ μμ± μ€...") |
| |
| playwright = sync_playwright().start() |
| |
| |
| browser_args = [ |
| '--no-sandbox', |
| '--disable-dev-shm-usage', |
| '--disable-gpu', |
| '--disable-extensions', |
| '--disable-web-security', |
| '--disable-blink-features=AutomationControlled', |
| '--disable-background-timer-throttling', |
| '--disable-backgrounding-occluded-windows', |
| '--disable-renderer-backgrounding', |
| '--disable-features=TranslateUI', |
| '--disable-ipc-flooding-protection' |
| ] |
| |
| browser = playwright.chromium.launch( |
| headless=True, |
| args=browser_args |
| ) |
| |
| print(f"[Thread {threading.current_thread().name}] λΈλΌμ°μ μΈμ€ν΄μ€ μμ± μλ£") |
| return playwright, browser |
| |
| except Exception as e: |
| print(f"[Thread {threading.current_thread().name}] λΈλΌμ°μ μΈμ€ν΄μ€ μμ± μ€ν¨: {str(e)}") |
| print(f"μμΈ μ€λ₯: {traceback.format_exc()}") |
| return None, None |
|
|
| def capture_google_trends() -> Optional[Image.Image]: |
| """Google Trends μ€ν¬λ¦°μ· μΊ‘μ² (λ
립 λΈλΌμ°μ μΈμ€ν΄μ€ μ¬μ©)""" |
| playwright = None |
| browser = None |
| page = None |
| |
| try: |
| print("Google Trends μ€ν¬λ¦°μ· μμ...") |
| |
| |
| playwright, browser = create_browser_for_thread() |
| if not browser: |
| print("λΈλΌμ°μ μ΄κΈ°ν μ€ν¨") |
| return None |
| |
| page = browser.new_page() |
| page.set_viewport_size({"width": 1400, "height": 900}) |
| page.set_extra_http_headers({ |
| "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8", |
| "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", |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" |
| }) |
| |
| url = "https://trends.google.co.kr/trending?geo=KR" |
| print(f"νμ΄μ§ μ μ: {url}") |
| |
| |
| try: |
| page.goto(url, wait_until="networkidle", timeout=45000) |
| print("νμ΄μ§ λ‘λ© μλ£") |
| except: |
| |
| page.goto(url, wait_until="domcontentloaded", timeout=30000) |
| print("κΈ°λ³Έ νμ΄μ§ λ‘λ© μλ£") |
| |
| |
| time.sleep(5) |
| |
| |
| try: |
| accept_button = page.locator('button:has-text("λͺ¨λ μλ½"), button:has-text("Accept all"), button:has-text("μλ½")') |
| if accept_button.count() > 0: |
| accept_button.first.click(timeout=3000) |
| print("μΏ ν€ νμ
μ²λ¦¬λ¨") |
| time.sleep(3) |
| except Exception as e: |
| print(f"μΏ ν€ νμ
μ²λ¦¬ μ€ν¨ (무μ): {e}") |
| |
| |
| try: |
| |
| page.wait_for_selector("main, .trending-content, .trends-wrapper, [data-topic]", timeout=15000) |
| print("νΈλ λ 컨ν
μΈ λ‘λ© νμΈλ¨") |
| except: |
| print("νΈλ λ μμλ₯Ό μ°Ύμ μ μμ, κ³μ μ§ν") |
| |
| |
| time.sleep(5) |
| |
| |
| print("μ€ν¬λ¦°μ· μΊ‘μ² μμ...") |
| try: |
| main_content = page.locator('main, .main-content, [data-ve-type="main"], .trends-wrapper') |
| if main_content.count() > 0 and main_content.first.is_visible(): |
| screenshot_bytes = main_content.first.screenshot(timeout=15000) |
| print("λ©μΈ 컨ν
μΈ μ€ν¬λ¦°μ· μΊ‘μ²λ¨") |
| else: |
| screenshot_bytes = page.screenshot(timeout=20000) |
| print("μ 체 νμ΄μ§ μ€ν¬λ¦°μ· μΊ‘μ²λ¨") |
| except Exception as e: |
| print(f"μ€ν¬λ¦°μ· μΊ‘μ² μ¬μλ: {e}") |
| screenshot_bytes = page.screenshot(timeout=20000) |
| print("μ¬μλ μ€ν¬λ¦°μ· μΊ‘μ² μλ£") |
| |
| screenshot = Image.open(io.BytesIO(screenshot_bytes)) |
| print("Google Trends μ€ν¬λ¦°μ· μλ£") |
| return screenshot |
| |
| except Exception as e: |
| print(f"Google Trends μ€ν¬λ¦°μ· μ€ν¨: {str(e)}") |
| print(f"μμΈ μ€λ₯: {traceback.format_exc()}") |
| return None |
| finally: |
| |
| if page: |
| try: |
| page.close() |
| except: |
| pass |
| if browser: |
| try: |
| browser.close() |
| except: |
| pass |
| if playwright: |
| try: |
| playwright.stop() |
| except: |
| pass |
|
|
| def capture_ezme_trends() -> Optional[Image.Image]: |
| """rank.ezme.net μ€ν¬λ¦°μ· μΊ‘μ² (λ
립 λΈλΌμ°μ μΈμ€ν΄μ€ μ¬μ©)""" |
| playwright = None |
| browser = None |
| page = None |
| |
| try: |
| print("rank.ezme.net μ€ν¬λ¦°μ· μμ...") |
| |
| |
| playwright, browser = create_browser_for_thread() |
| if not browser: |
| print("λΈλΌμ°μ μ΄κΈ°ν μ€ν¨") |
| return None |
| |
| page = browser.new_page() |
| page.set_viewport_size({"width": 1400, "height": 1200}) |
| page.set_extra_http_headers({ |
| "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8", |
| "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", |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" |
| }) |
| |
| url = "https://rank.ezme.net/diff/" |
| print(f"νμ΄μ§ μ μ: {url}") |
| |
| |
| try: |
| page.goto(url, wait_until="networkidle", timeout=45000) |
| print("νμ΄μ§ λ‘λ© μλ£") |
| except: |
| |
| page.goto(url, wait_until="domcontentloaded", timeout=30000) |
| print("κΈ°λ³Έ νμ΄μ§ λ‘λ© μλ£") |
| |
| |
| print("λμ 컨ν
μΈ λ‘λ© λκΈ°μ€...") |
| time.sleep(8) |
| |
| |
| try: |
| |
| page.wait_for_selector("table, .table, td", timeout=10000) |
| print("ν
μ΄λΈ λ°μ΄ν° λ‘λ© νμΈλ¨") |
| except: |
| print("ν
μ΄λΈ μμλ₯Ό μ°Ύμ μ μμ, κ³μ μ§ν") |
| |
| |
| try: |
| page.evaluate("window.scrollTo(0, document.body.scrollHeight * 0.4)") |
| time.sleep(3) |
| print("μ€ν¬λ‘€ μλ£") |
| except Exception as e: |
| print(f"μ€ν¬λ‘€ μ€ν¨: {e}") |
| |
| |
| print("μ€ν¬λ¦°μ· μΊ‘μ² μμ...") |
| screenshot_bytes = page.screenshot(full_page=True, timeout=20000) |
| screenshot = Image.open(io.BytesIO(screenshot_bytes)) |
| print("rank.ezme.net μ€ν¬λ¦°μ· μλ£") |
| return screenshot |
| |
| except Exception as e: |
| print(f"rank.ezme.net μ€ν¬λ¦°μ· μ€ν¨: {str(e)}") |
| print(f"μμΈ μ€λ₯: {traceback.format_exc()}") |
| return None |
| finally: |
| |
| if page: |
| try: |
| page.close() |
| except: |
| pass |
| if browser: |
| try: |
| browser.close() |
| except: |
| pass |
| if playwright: |
| try: |
| playwright.stop() |
| except: |
| pass |
|
|
| def analyze_google_trends_with_claude(image: Image.Image) -> str: |
| """Google Trends μ΄λ―Έμ§ Claude λΆμ""" |
| if image is None: |
| return "Google Trends λΆμν μ΄λ―Έμ§κ° μμ΅λλ€." |
| |
| try: |
| client = get_claude_client() |
| |
| buffer = io.BytesIO() |
| image.save(buffer, format="PNG") |
| image_base64 = base64.b64encode(buffer.getvalue()).decode() |
| |
| prompt = """Google Trends μ€μκ° μΈκΈ° κ²μμ΄ μ€ν¬λ¦°μ·μ λΆμν΄μ£ΌμΈμ. |
| |
| λ€μ νλͺ©λ€μ μ€μ¬μΌλ‘ νκ΅μ΄λ‘ λΆμν΄μ£ΌμΈμ: |
| |
| ### A. μμ κ²μμ΄ μμ (κ²μλ κΈ°μ€) |
| κ²μλμ΄ λμ μμλλ‘ μ λ ¬νμ¬ λμ΄: |
| **1μ.** κ²μμ΄λͺ
(κ²μλ) |
| **2μ.** κ²μμ΄λͺ
(κ²μλ) |
| **3μ.** κ²μμ΄λͺ
(κ²μλ) |
| (μ΄λ° μμΌλ‘ 10μκΉμ§) |
| |
| ### B. μ£Όμ νΈλ λ ν€μλ |
| - νΉν μ£Όλͺ©ν λ§ν κ²μμ΄λ€ |
| - κ²μλ μ¦κ° ν¨ν΄ |
| |
| ### C. μΉ΄ν
κ³ λ¦¬λ³ νΉμ§ |
| - μν°ν
μΈλ¨ΌνΈ, λ΄μ€, μ€ν¬μΈ λ± λΆμΌλ³ λν₯ |
| |
| ### D. νΉμ΄μ¬ν λ° λΆμ |
| - κΈμμΉ ν€μλ |
| - μκΈ°μ /μ¬νμ λ°°κ²½ μΆμΈ‘ |
| - μ 체μ μΈ κ΄μ¬μ¬ ν¨ν΄ |
| |
| **μ€μ μ§μμ¬ν:** |
| - λ§ν¬λ€μ΄ νμμ μ¬μ©νμ¬ **κ΅΅μ κΈμ¨**, *κΈ°μΈμ*, `μ½λ` λ±μΌλ‘ μ€μν λ΄μ©μ κ°μ‘°ν΄μ£ΌμΈμ |
| - κ²μμ΄λͺ
, μμΉ, μΉ΄ν
κ³ λ¦¬λͺ
λ± ν΅μ¬ μ 보λ **κ΅΅μ κΈμ¨**λ‘ νμ |
| - κΈμμΉλ₯ μ΄λ νΉλ³ν μμΉλ `λ°±ν±`μΌλ‘ κ°μΈμ κ°μ‘° |
| - μμ κ²μμ΄λ λ°λμ κ²μλ μμλλ‘ 1μλΆν° 10μκΉμ§ μ λ ¬""" |
|
|
| response = client.messages.create( |
| model="claude-3-5-sonnet-20241022", |
| max_tokens=1200, |
| messages=[ |
| { |
| "role": "user", |
| "content": [ |
| { |
| "type": "text", |
| "text": prompt |
| }, |
| { |
| "type": "image", |
| "source": { |
| "type": "base64", |
| "media_type": "image/png", |
| "data": image_base64 |
| } |
| } |
| ] |
| } |
| ] |
| ) |
| |
| return response.content[0].text |
| |
| except Exception as e: |
| return f"Google Trends Claude λΆμ μ€ν¨: {str(e)}" |
|
|
| def analyze_ezme_trends_with_claude(image: Image.Image) -> str: |
| """rank.ezme.net μ΄λ―Έμ§ Claude λΆμ""" |
| if image is None: |
| return "rank.ezme.net λΆμν μ΄λ―Έμ§κ° μμ΅λλ€." |
| |
| try: |
| client = get_claude_client() |
| |
| buffer = io.BytesIO() |
| image.save(buffer, format="PNG") |
| image_base64 = base64.b64encode(buffer.getvalue()).decode() |
| |
| prompt = """νκ΅ ν¬νΈμ¬μ΄νΈλ³ μκ°λ³ μ€μκ° κ²μμ΄ λ°μ΄ν°λ₯Ό λΆμν΄μ£ΌμΈμ. |
| |
| λ€μ νλͺ©λ€μ μ€μ¬μΌλ‘ νκ΅μ΄λ‘ λΆμν΄μ£ΌμΈμ: |
| |
| ### A. λλ©μΈλ³ μ£Όμ κ²μμ΄ |
| **Zum (μ€) μ£Όμ κ²μμ΄:** |
| - μμ κ²μμ΄μ μ§μ
νμ |
| - μ£Όμ κ΄μ¬ λΆμΌ |
| |
| **Nate (λ€μ΄νΈ) μ£Όμ κ²μμ΄:** |
| - μμ κ²μμ΄μ μ§μ
νμ |
| - μ£Όμ κ΄μ¬ λΆμΌ |
| |
| **Google νκ΅ μ£Όμ κ²μμ΄:** |
| - μμ κ²μμ΄μ μ§μ
νμ |
| - μ£Όμ κ΄μ¬ λΆμΌ |
| |
| ### B. μκ°λλ³ νΈλ λ λ³ν |
| - μ΅κ·Ό λͺ μκ° λμμ λ³ν ν¨ν΄ |
| - μκ°λλ³ μ£Όμ μ΄μ λ³ν |
| |
| ### C. κ²μμ΄ μ§μ
νμ λΆμ |
| - κ°μ₯ λμ μ§μ
νμλ₯Ό κΈ°λ‘ν κ²μμ΄λ€ |
| - μ§μμ μΌλ‘ μμκΆμ μ μ§νλ κ²μμ΄λ€ |
| |
| ### D. ν¬νΈλ³ κ΄μ¬μ¬ μ°¨μ΄μ |
| - κ° ν¬νΈμ¬μ΄νΈλ³ νΉμ± λΆμ |
| - μ¬μ©μμΈ΅λ³ κ΄μ¬μ¬ μ°¨μ΄ |
| |
| **μ€μ μ§μμ¬ν:** |
| - λ§ν¬λ€μ΄ νμμ μ¬μ©νμ¬ **κ΅΅μ κΈμ¨**λ‘ κ²μμ΄λͺ
κ°μ‘° |
| - μ§μ
νμλ `λ°±ν±`μΌλ‘ κ°μΈμ νμ (μ: `μ§μ
νμ: 15ν`) |
| - μκ°λ³ λ°μ΄ν°κ° μλ€λ©΄ ꡬ체μ μΌλ‘ μΈκΈ |
| - κ° ν¬νΈμ νΉμ§μ λͺ
νν ꡬλΆνμ¬ λΆμ""" |
|
|
| response = client.messages.create( |
| model="claude-3-5-sonnet-20241022", |
| max_tokens=1200, |
| messages=[ |
| { |
| "role": "user", |
| "content": [ |
| { |
| "type": "text", |
| "text": prompt |
| }, |
| { |
| "type": "image", |
| "source": { |
| "type": "base64", |
| "media_type": "image/png", |
| "data": image_base64 |
| } |
| } |
| ] |
| } |
| ] |
| ) |
| |
| return response.content[0].text |
| |
| except Exception as e: |
| return f"rank.ezme.net Claude λΆμ μ€ν¨: {str(e)}" |
|
|
| def combine_analysis_results(google_analysis: str, ezme_analysis: str) -> str: |
| """Google Trendsμ ezme λΆμ κ²°κ³Ό ν΅ν© (μ€λ₯ μν© λμ)""" |
| timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
| |
| |
| google_failed = "λΆμν μ΄λ―Έμ§κ° μμ΅λλ€" in google_analysis or "μΊ‘μ²μ μ€ν¨" in google_analysis |
| ezme_failed = "λΆμν μ΄λ―Έμ§κ° μμ΅λλ€" in ezme_analysis or "μΊ‘μ²μ μ€ν¨" in ezme_analysis |
| |
| |
| if google_failed and ezme_failed: |
| status_msg = "**λͺ¨λ λ°μ΄ν° μμ€μ μμ§μ μ€ν¨νμ΅λλ€.** μ μ ν λ€μ μλν΄μ£ΌμΈμ." |
| elif google_failed: |
| status_msg = "**Google Trends λ°μ΄ν° μμ§μ μ€ν¨νμ΅λλ€.** νκ΅ ν¬νΈ λ°μ΄ν°λ§ μ 곡λ©λλ€." |
| elif ezme_failed: |
| status_msg = "**νκ΅ ν¬νΈ λ°μ΄ν° μμ§μ μ€ν¨νμ΅λλ€.** Google Trends λ°μ΄ν°λ§ μ 곡λ©λλ€." |
| else: |
| status_msg = "**λͺ¨λ λ°μ΄ν° μμ€λ₯Ό μ±κ³΅μ μΌλ‘ μμ§νμ΅λλ€.**" |
| |
| combined_result = f"""# μ€μκ° κ²μμ΄ ν΅ν© λΆμ |
| |
| **μ΅μ’
μ
λ°μ΄νΈ:** {timestamp} |
| |
| {status_msg} |
| |
| --- |
| |
| ## Google Trends κΈλ‘λ² λν₯ |
| |
| {google_analysis} |
| |
| --- |
| |
| ## νκ΅ ν¬νΈ μ€μκ° κ²μμ΄ |
| |
| {ezme_analysis} |
| """ |
| |
| return combined_result |
|
|
| def perform_parallel_analysis() -> Tuple[bool, str]: |
| """λ³λ ¬ μ²λ¦¬λ‘ ν΅ν© λΆμ μν (κ°μ λ μ€λ₯ μ²λ¦¬)""" |
| global analyzed_data, last_update_time, is_updating |
| |
| is_updating = True |
| print(f"\n{'='*60}") |
| print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] ν΅ν© μ€μκ° νΈλ λ λΆμ μμ") |
| print(f"{'='*60}") |
| |
| try: |
| |
| print("1. λΈλΌμ°μ μ€μΉ νμΈ...") |
| install_browsers() |
| |
| |
| print("2. λ³λ ¬ μ€ν¬λ¦°μ· μΊ‘μ² μμ...") |
| |
| with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: |
| print(" - Google Trends μ€ν¬λ¦°μ· μμ
μμ") |
| google_future = executor.submit(capture_google_trends) |
| |
| print(" - rank.ezme.net μ€ν¬λ¦°μ· μμ
μμ") |
| ezme_future = executor.submit(capture_ezme_trends) |
| |
| print(" - λ μμ
μλ£ λκΈ° μ€...") |
| google_screenshot = google_future.result() |
| ezme_screenshot = ezme_future.result() |
| |
| |
| print("3. μ€ν¬λ¦°μ· κ²°κ³Ό νμΈ...") |
| google_success = google_screenshot is not None |
| ezme_success = ezme_screenshot is not None |
| |
| print(f" - Google Trends μ€ν¬λ¦°μ·: {'μ±κ³΅' if google_success else 'μ€ν¨'}") |
| print(f" - rank.ezme.net μ€ν¬λ¦°μ·: {'μ±κ³΅' if ezme_success else 'μ€ν¨'}") |
| |
| if not google_success and not ezme_success: |
| error_msg = "λͺ¨λ μ€ν¬λ¦°μ· μΊ‘μ²μ μ€ν¨νμ΅λλ€." |
| print(f" ERROR: {error_msg}") |
| return False, error_msg |
| |
| print("4. Claude λΆμ μμ...") |
| |
| |
| if google_success: |
| print(" - Google Trends λΆμ μ€...") |
| google_analysis = analyze_google_trends_with_claude(google_screenshot) |
| print(" - Google Trends λΆμ μλ£") |
| else: |
| google_analysis = "Google Trends μ€ν¬λ¦°μ· μΊ‘μ²μ μ€ν¨νμ¬ λΆμν μ μμ΅λλ€." |
| |
| if ezme_success: |
| print(" - rank.ezme.net λΆμ μ€...") |
| ezme_analysis = analyze_ezme_trends_with_claude(ezme_screenshot) |
| print(" - rank.ezme.net λΆμ μλ£") |
| else: |
| ezme_analysis = "rank.ezme.net μ€ν¬λ¦°μ· μΊ‘μ²μ μ€ν¨νμ¬ λΆμν μ μμ΅λλ€." |
| |
| print("5. ν΅ν© λΆμ κ²°κ³Ό μμ±...") |
| |
| |
| combined_analysis = combine_analysis_results(google_analysis, ezme_analysis) |
| |
| |
| analyzed_data["ν΅ν©μ€μκ°"] = { |
| 'analysis': combined_analysis, |
| 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
| 'google_analysis': google_analysis, |
| 'ezme_analysis': ezme_analysis, |
| 'google_success': google_success, |
| 'ezme_success': ezme_success |
| } |
| |
| last_update_time = datetime.now() |
| |
| success_count = sum([google_success, ezme_success]) |
| print(f"ν΅ν© λΆμ μλ£ - {last_update_time.strftime('%Y-%m-%d %H:%M:%S')}") |
| print(f"μ±κ³΅λ₯ : {success_count}/2 μμ€") |
| |
| return True, combined_analysis |
| |
| except Exception as e: |
| error_msg = f"ν΅ν© λΆμ μ€ μμμΉ λͺ»ν μ€λ₯: {str(e)}" |
| print(f"ERROR: {error_msg}") |
| print(f"μμΈ μ€λ₯: {traceback.format_exc()}") |
| return False, error_msg |
| finally: |
| is_updating = False |
| print(f"{'='*60}\n") |
|
|
| def auto_update_scheduler(): |
| """1μκ°λ§λ€ μλ μ
λ°μ΄νΈ""" |
| while True: |
| try: |
| print(f"[μ€μΌμ€λ¬] λ€μ μ
λ°μ΄νΈκΉμ§ 1μκ° λκΈ° μ€...") |
| time.sleep(3600) |
| |
| if not is_updating: |
| print(f"[μ€μΌμ€λ¬] μλ μ
λ°μ΄νΈ μμ") |
| perform_parallel_analysis() |
| else: |
| print(f"[μ€μΌμ€λ¬] μ΄λ―Έ μ
λ°μ΄νΈ μ€μ΄λ―λ‘ κ±΄λλ") |
| |
| except Exception as e: |
| print(f"[μ€μΌμ€λ¬] μ€λ₯: {e}") |
| time.sleep(300) |
|
|
| def get_cached_analysis_result() -> tuple: |
| """μ μ₯λ ν΅ν© λΆμ κ²°κ³Ό λ°ν""" |
| global analyzed_data, last_update_time |
| |
| if "ν΅ν©μ€μκ°" in analyzed_data: |
| data = analyzed_data["ν΅ν©μ€μκ°"] |
| status = f"μ΅μ λ°μ΄ν° (μ
λ°μ΄νΈ: {data['timestamp']})" |
| return data['analysis'], status |
| else: |
| if last_update_time: |
| status = f"λ°μ΄ν° μμ (λ§μ§λ§ μ
λ°μ΄νΈ: {last_update_time.strftime('%H:%M:%S')})" |
| else: |
| status = "μμ§ λΆμ λ°μ΄ν°κ° μ€λΉλμ§ μμμ΅λλ€." |
| |
| return "**λΆμ λ°μ΄ν°κ° μμ§ μ€λΉλμ§ μμμ΅λλ€.**\n\nμ μ ν λ€μ μλν΄μ£ΌμΈμ.", status |
|
|
| def create_interface(): |
| """Gradio μΈν°νμ΄μ€ μμ±""" |
| |
| def show_cached_analysis(): |
| """μ μ₯λ ν΅ν© λΆμ κ²°κ³Ό νμ""" |
| if "ν΅ν©μ€μκ°" in analyzed_data: |
| data = analyzed_data["ν΅ν©μ€μκ°"] |
| status = f"μΊμλ ν΅ν© λΆμ κ²°κ³Ό (μ
λ°μ΄νΈ: {data['timestamp']})" |
| return data['analysis'], status |
| else: |
| if last_update_time: |
| status = f"μ μ₯λ λ°μ΄ν° μμ (λ§μ§λ§ μ
λ°μ΄νΈ: {last_update_time.strftime('%H:%M:%S')})" |
| else: |
| status = "μμ§ λΆμ λ°μ΄ν°κ° μ€λΉλμ§ μμμ΅λλ€. μ μ ν λ€μ μλν΄μ£ΌμΈμ." |
| |
| return "**μ μ₯λ ν΅ν© λΆμ λ°μ΄ν°κ° μμ΅λλ€.**\n\n'μ€μκ° μ
λ°μ΄νΈ' λ²νΌμ λλ¬μ£ΌμΈμ.", status |
| |
| def perform_live_update(): |
| """μ€μκ° μ
λ°μ΄νΈ μν""" |
| if is_updating: |
| return "**νμ¬ μ
λ°μ΄νΈκ° μ§ν μ€μ
λλ€.**\n\nμ μ ν λ€μ μλν΄μ£ΌμΈμ.", "μ
λ°μ΄νΈ μ§ν μ€..." |
| |
| print("μ€μκ° ν΅ν© μ
λ°μ΄νΈ μμ²λ¨") |
| success, result = perform_parallel_analysis() |
| |
| if success and "ν΅ν©μ€μκ°" in analyzed_data: |
| data = analyzed_data["ν΅ν©μ€μκ°"] |
| return data['analysis'], f"μ€μκ° μ
λ°μ΄νΈ μλ£ - {datetime.now().strftime('%H:%M:%S')}" |
| else: |
| return "**μ
λ°μ΄νΈμ μ€ν¨νμ΅λλ€.**\n\nμ μ ν λ€μ μλν΄μ£ΌμΈμ.", f"μ
λ°μ΄νΈ μ€ν¨ - {datetime.now().strftime('%H:%M:%S')}" |
| |
| |
| with gr.Blocks( |
| title="ν΅ν© μ€μκ° κ²μμ΄ λΆμ", |
| theme=gr.themes.Soft(), |
| css=""" |
| @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap'); |
| |
| * { |
| font-family: 'Noto Sans KR', sans-serif !important; |
| } |
| |
| .markdown-content { |
| font-size: 14px; |
| line-height: 1.6; |
| background: #f8f9fa; |
| padding: 20px; |
| border-radius: 8px; |
| border: 1px solid #e9ecef; |
| } |
| |
| .markdown-content h1 { |
| color: #1d4ed8; |
| margin-bottom: 15px; |
| } |
| |
| .markdown-content h2 { |
| color: #2563eb; |
| margin-top: 25px; |
| margin-bottom: 12px; |
| } |
| |
| .markdown-content h3 { |
| color: #3b82f6; |
| margin-top: 20px; |
| margin-bottom: 10px; |
| } |
| |
| .markdown-content strong { |
| color: #1d4ed8; |
| font-weight: 600; |
| } |
| """ |
| ) as interface: |
| |
| gr.Markdown("# ν΅ν© μ€μκ° κ²μμ΄ λΆμ") |
| gr.Markdown("Google Trendsμ νκ΅ ν¬νΈμ¬μ΄νΈ κ²μμ΄λ₯Ό ν΅ν© λΆμν©λλ€.") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### λΆμ μ΅μ
") |
| |
| with gr.Column(): |
| cached_btn = gr.Button("ν΅ν© κ²μμ΄ λΆμ", variant="primary", size="lg") |
| gr.Markdown("μ μ₯λ ν΅ν© λΆμ κ²°κ³Όλ₯Ό μ¦μ νμν©λλ€.") |
| |
| gr.Markdown("---") |
| |
| with gr.Column(): |
| live_btn = gr.Button("μ€μκ° μ
λ°μ΄νΈ", variant="secondary", size="lg") |
| gr.Markdown("Google Trends + νκ΅ ν¬νΈ μ΅μ λ°μ΄ν°λ‘ μ
λ°μ΄νΈν©λλ€. (μ½ 1λΆ μμ)") |
| |
| gr.Markdown("---") |
| |
| status_output = gr.Textbox( |
| label="μν", |
| value="λΆμν μ΅μ
μ μ νν΄μ£ΌμΈμ.", |
| interactive=False |
| ) |
| |
| |
| try: |
| gr.Image( |
| value="apeach.png", |
| label=None, |
| show_label=False, |
| interactive=False, |
| height=200, |
| width=200 |
| ) |
| except: |
| pass |
| |
| with gr.Column(scale=2): |
| analysis_output = gr.Markdown( |
| value="**λΆμ λ²νΌμ λλ¬μ£ΌμΈμ.**\n\n- **ν΅ν© κ²μμ΄ λΆμ**: μ μ₯λ κ²°κ³Ό μ¦μ νμΈ\n- **μ€μκ° μ
λ°μ΄νΈ**: μ΅μ λ°μ΄ν°λ‘ μλ‘ λΆμ", |
| elem_classes=["markdown-content"], |
| height=700 |
| ) |
| |
| |
| cached_btn.click( |
| fn=show_cached_analysis, |
| outputs=[analysis_output, status_output] |
| ) |
| |
| live_btn.click( |
| fn=perform_live_update, |
| outputs=[analysis_output, status_output] |
| ) |
| |
| return interface |
|
|
| def main(): |
| """λ©μΈ ν¨μ""" |
| print("=" * 60) |
| print("ν΅ν© μ€μκ° κ²μμ΄ λΆμ μλΉμ€ μμ!") |
| print("=" * 60) |
| |
| |
| api_key = os.getenv("ANTHROPIC_API_KEY") |
| if not api_key: |
| print("ANTHROPIC_API_KEY νκ²½λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
| print(" .env νμΌμ ANTHROPIC_API_KEY=your_key_here λ₯Ό μΆκ°νμΈμ.") |
| return |
| else: |
| print("Claude API ν€ νμΈλ¨") |
| |
| |
| print("μ΄κΈ° ν΅ν© λΆμ μμ...") |
| initial_success, _ = perform_parallel_analysis() |
| |
| if initial_success: |
| print("μ΄κΈ° ν΅ν© λΆμ μλ£") |
| else: |
| print("μ΄κΈ° λΆμ μ€ν¨, μλΉμ€λ κ³μ μ§νλ©λλ€.") |
| |
| |
| print("μλ μ
λ°μ΄νΈ μ€μΌμ€λ¬ μμ (1μκ° κ°κ²©)") |
| global update_thread |
| update_thread = threading.Thread(target=auto_update_scheduler, daemon=True) |
| update_thread.start() |
| |
| print("λͺ¨λ μ€λΉ μλ£!") |
| print("=" * 60) |
| |
| |
| try: |
| interface = create_interface() |
| interface.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=False, |
| show_error=True, |
| quiet=False |
| ) |
| except Exception as e: |
| print(f"μΈν°νμ΄μ€ μμ μ€ν¨: {e}") |
| print(f"μμΈ μ€λ₯: {traceback.format_exc()}") |
|
|
| if __name__ == "__main__": |
| main() |