HanJun's picture
Update app.py
37ff9f3 verified
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}")
# ํŽ˜์ด์ง€ ๋กœ๋”ฉ (networkidle๋กœ JavaScript ์™„๋ฃŒ๊นŒ์ง€ ๋Œ€๊ธฐ)
try:
page.goto(url, wait_until="networkidle", timeout=45000)
print("ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ")
except:
# networkidle ์‹คํŒจ์‹œ domcontentloaded๋กœ ์žฌ์‹œ๋„
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}")
# ํŽ˜์ด์ง€ ๋กœ๋”ฉ (networkidle ๋Œ€๊ธฐ๋กœ JavaScript ๋กœ๋”ฉ ์™„๋ฃŒ๊นŒ์ง€ ๋Œ€๊ธฐ)
try:
page.goto(url, wait_until="networkidle", timeout=45000)
print("ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ")
except:
# networkidle ์‹คํŒจ์‹œ domcontentloaded๋กœ ์žฌ์‹œ๋„
page.goto(url, wait_until="domcontentloaded", timeout=30000)
print("๊ธฐ๋ณธ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ")
# JavaScript ์‹คํ–‰ ๋ฐ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋Œ€๊ธฐ
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 ๋ถ„์„ ์‹œ์ž‘...")
# Claude ๋ถ„์„ (์ˆœ์ฐจ ์ฒ˜๋ฆฌ - API ์ œํ•œ)
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) # 1์‹œ๊ฐ„ ๋Œ€๊ธฐ
if not is_updating:
print(f"[์Šค์ผ€์ค„๋Ÿฌ] ์ž๋™ ์—…๋ฐ์ดํŠธ ์‹œ์ž‘")
perform_parallel_analysis()
else:
print(f"[์Šค์ผ€์ค„๋Ÿฌ] ์ด๋ฏธ ์—…๋ฐ์ดํŠธ ์ค‘์ด๋ฏ€๋กœ ๊ฑด๋„ˆ๋œ€")
except Exception as e:
print(f"[์Šค์ผ€์ค„๋Ÿฌ] ์˜ค๋ฅ˜: {e}")
time.sleep(300) # 5๋ถ„ ํ›„ ์žฌ์‹œ๋„
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')}"
# Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ
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
)
# Apeach ์ด๋ฏธ์ง€ ํ‘œ์‹œ
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 ํ‚ค ํ™•์ธ
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)
# Gradio ์ธํ„ฐํŽ˜์ด์Šค ์‹คํ–‰
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()