HanJun's picture
Update app.py
0299c67 verified
raw
history blame
22.7 kB
import os
import time
import base64
import io
import traceback
import threading
import atexit
import asyncio
from datetime import datetime
from typing import Optional, Tuple
import gradio as gr
import anthropic
from playwright.async_api import async_playwright
from playwright.sync_api import sync_playwright, Browser, Page
from PIL import Image
import requests
from dotenv import load_dotenv
# ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()
# ์ „์—ญ ๋ณ€์ˆ˜
browser = None
page = None
playwright_instance = None
browser_lock = threading.Lock()
is_browser_ready = False
browser_thread_id = None
# Claude API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
def get_claude_client():
api_key = os.getenv("ANTHROPIC_API_KEY")
if not api_key:
raise ValueError("ANTHROPIC_API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
try:
# ๋” ์•ˆ์ „ํ•œ ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
client = anthropic.Anthropic(
api_key=api_key,
max_retries=2,
timeout=60.0
)
return client
except Exception as e:
print(f"Claude ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์˜ค๋ฅ˜: {e}")
# ๊ธฐ๋ณธ ์ดˆ๊ธฐํ™” ๋ฐฉ์‹์œผ๋กœ ์žฌ์‹œ๋„
return anthropic.Anthropic(api_key=api_key)
def install_browsers():
"""Playwright ๋ธŒ๋ผ์šฐ์ € ๋ฐ ํ•œ๊ธ€ ํฐํŠธ ์„ค์น˜"""
try:
print("Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜ ์ค‘...")
os.system("playwright install chromium")
print("ํ•œ๊ธ€ ํฐํŠธ ์„ค์น˜ ์ค‘...")
# ํ•œ๊ธ€ ํฐํŠธ ์„ค์น˜
os.system("apt-get update")
os.system("apt-get install -y fonts-nanum fonts-nanum-coding fonts-nanum-extra")
os.system("fc-cache -fv")
print("Playwright ๋ธŒ๋ผ์šฐ์ € ๋ฐ ํฐํŠธ ์„ค์น˜ ์™„๋ฃŒ!")
return True
except Exception as e:
print(f"๋ธŒ๋ผ์šฐ์ €/ํฐํŠธ ์„ค์น˜ ์‹คํŒจ: {e}")
return False
def init_browser():
"""Playwright ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  Google Trends ํŽ˜์ด์ง€๋กœ ์ด๋™"""
global browser, page, playwright_instance, is_browser_ready, browser_thread_id
try:
# ํ˜„์žฌ ์Šค๋ ˆ๋“œ ID ์ €์žฅ
browser_thread_id = threading.current_thread().ident
print(f"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์Šค๋ ˆ๋“œ ID: {browser_thread_id}")
print("Playwright ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘...")
# ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜
install_browsers()
# Playwright ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
playwright_instance = sync_playwright().start()
# ๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰ (Chromium ์‚ฌ์šฉ)
browser = playwright_instance.chromium.launch(
headless=True,
args=[
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--disable-extensions',
'--disable-web-security',
'--allow-running-insecure-content',
'--disable-features=VizDisplayCompositor',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-features=TranslateUI',
'--disable-ipc-flooding-protection',
'--disable-plugins',
'--disable-images',
'--force-device-scale-factor=1',
'--font-render-hinting=medium',
'--disable-font-subpixel-positioning'
]
)
# ์ƒˆ ํŽ˜์ด์ง€ ์ƒ์„ฑ
page = browser.new_page()
# ๋ทฐํฌํŠธ ์„ค์ •
page.set_viewport_size({"width": 1920, "height": 1080})
# User-Agent ์„ค์ •
page.set_extra_http_headers({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8"
})
# ํ•œ๊ธ€ ํฐํŠธ ์„ค์ •์„ ์œ„ํ•œ CSS ์ถ”๊ฐ€
page.add_style_tag(content="""
* {
font-family: 'Nanum Gothic', 'Malgun Gothic', '๋ง‘์€ ๊ณ ๋”•', 'Noto Sans CJK KR', 'Apple SD Gothic Neo', sans-serif !important;
}
""")
print("Google Trends ํŽ˜์ด์ง€๋กœ ์ด๋™ ์ค‘...")
page.goto("https://trends.google.com/trending?geo=KR", wait_until="networkidle", timeout=30000)
# ์ถ”๊ฐ€ ๋กœ๋”ฉ ์‹œ๊ฐ„
time.sleep(5)
is_browser_ready = True
print("๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!")
except Exception as e:
print(f"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
print(traceback.format_exc())
is_browser_ready = False
cleanup_browser()
def ensure_browser_ready():
"""๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ํ•„์š”์‹œ ์ดˆ๊ธฐํ™”"""
global is_browser_ready, browser, page, browser_thread_id
current_thread_id = threading.current_thread().ident
# ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์ƒ์„ฑ๋œ ๊ฒฝ์šฐ
if not is_browser_ready or browser_thread_id != current_thread_id or not browser or not page:
print(f"๋ธŒ๋ผ์šฐ์ € ์žฌ์ดˆ๊ธฐํ™” ํ•„์š” (ํ˜„์žฌ ์Šค๋ ˆ๋“œ: {current_thread_id}, ๋ธŒ๋ผ์šฐ์ € ์Šค๋ ˆ๋“œ: {browser_thread_id})")
# ๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ
cleanup_browser()
# ํ˜„์žฌ ์Šค๋ ˆ๋“œ์—์„œ ์ƒˆ๋กœ ์ดˆ๊ธฐํ™”
init_browser()
return is_browser_ready
def cleanup_browser():
"""๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
global browser, page, playwright_instance
try:
if page:
page.close()
page = None
if browser:
browser.close()
browser = None
if playwright_instance:
playwright_instance.stop()
playwright_instance = None
print("๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์™„๋ฃŒ")
except Exception as e:
print(f"๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {e}")
def apply_filters(geo: str, time_range: str, category: str) -> bool:
"""Google Trends ํ•„ํ„ฐ ์ ์šฉ"""
global page
# ๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ์ƒํƒœ ํ™•์ธ
if not ensure_browser_ready():
print("๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ์‹คํŒจ")
return False
try:
print(f"ํ•„ํ„ฐ ์ ์šฉ: ์ง€์—ญ={geo}")
# ์ง€์—ญ ์„ค์ •์„ ์œ„ํ•œ URL ๊ตฌ์„ฑ
geo_map = {
"KR": "KR", "US": "US", "JP": "JP",
"GB": "GB", "DE": "DE", "FR": "FR"
}
base_url = "https://trends.google.com/trending"
geo_param = geo_map.get(geo, "KR")
url = f"{base_url}?geo={geo_param}"
print(f"ํ•„ํ„ฐ ์ ์šฉ๋œ URL๋กœ ์ด๋™: {url}")
page.goto(url, wait_until="networkidle", timeout=30000)
# ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ ๋Œ€๊ธฐ
time.sleep(3)
# ์‹ค์ œ ํ•„ํ„ฐ ์š”์†Œ๋“ค์ด ์žˆ๋‹ค๋ฉด ํด๋ฆญ์œผ๋กœ ์„ค์ •
try:
# ์‹œ๊ฐ„ ๋ฒ”์œ„ ํ•„ํ„ฐ ์‹œ๋„
if time_range != "24์‹œ๊ฐ„":
time_selector = page.locator('[data-bucket="0"]').first
if time_selector.is_visible():
time_selector.click()
time.sleep(1)
# ์‹œ๊ฐ„ ์˜ต์…˜๋“ค ์ค‘ ์„ ํƒ
if time_range == "7์ผ":
page.locator('text="Past 7 days"').first.click()
elif time_range == "30์ผ":
page.locator('text="Past 30 days"').first.click()
time.sleep(2)
# ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ ์‹œ๋„
if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
category_selector = page.locator('[data-bucket="2"]').first
if category_selector.is_visible():
category_selector.click()
time.sleep(1)
# ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘
category_map = {
"์—”ํ„ฐํ…Œ์ธ๋จผํŠธ": "Entertainment",
"์Šคํฌ์ธ ": "Sports",
"๋น„์ฆˆ๋‹ˆ์Šค": "Business",
"๊ณผํ•™๊ธฐ์ˆ ": "Science & Tech",
"๊ฑด๊ฐ•": "Health"
}
if category in category_map:
page.locator(f'text="{category_map[category]}"').first.click()
time.sleep(2)
except Exception as filter_error:
print(f"ํ•„ํ„ฐ ์š”์†Œ ํด๋ฆญ ์‹คํŒจ (๊ณ„์† ์ง„ํ–‰): {filter_error}")
print("ํ•„ํ„ฐ ์ ์šฉ ์™„๋ฃŒ")
return True
except Exception as e:
print(f"ํ•„ํ„ฐ ์ ์šฉ ์‹คํŒจ: {str(e)}")
print(traceback.format_exc())
return False
def capture_screenshot() -> Optional[Image.Image]:
"""ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ (ํŠธ๋ Œ๋“œ ํ…Œ์ด๋ธ” ์˜์—ญ๋งŒ)"""
global page
# ๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ์ƒํƒœ ํ™•์ธ
if not ensure_browser_ready():
print("๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ์‹คํŒจ")
return None
try:
print("์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
# ํŽ˜์ด์ง€๊ฐ€ ์™„์ „ํžˆ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
time.sleep(2)
# ํŠธ๋ Œ๋“œ ํ…Œ์ด๋ธ” ์˜์—ญ ์ฐพ๊ธฐ
try:
# ์—ฌ๋Ÿฌ ๊ฐ€๋Šฅํ•œ ์…€๋ ‰ํ„ฐ ์‹œ๋„
selectors = [
'[data-module="TrendingSearches"]', # ๋ฉ”์ธ ํŠธ๋ Œ๋“œ ๋ชจ๋“ˆ
'.trends-table', # ํŠธ๋ Œ๋“œ ํ…Œ์ด๋ธ”
'.trending-searches', # ํŠธ๋ Œ๋“œ ๊ฒ€์ƒ‰์–ด ์˜์—ญ
'[role="main"]', # ๋ฉ”์ธ ์ฝ˜ํ…์ธ  ์˜์—ญ
'main' # ๊ธฐ๋ณธ ๋ฉ”์ธ ํƒœ๊ทธ
]
element = None
for selector in selectors:
try:
element = page.locator(selector).first
if element.is_visible():
print(f"์บก์ฒ˜ ์˜์—ญ ๋ฐœ๊ฒฌ: {selector}")
break
except:
continue
if element and element.is_visible():
# ํŠน์ • ์˜์—ญ๋งŒ ์Šคํฌ๋ฆฐ์ƒท
screenshot_bytes = element.screenshot()
print("ํŠธ๋ Œ๋“œ ํ…Œ์ด๋ธ” ์˜์—ญ ์บก์ฒ˜ ์™„๋ฃŒ")
else:
# ๋Œ€์•ˆ: ์ „์ฒด ํŽ˜์ด์ง€์—์„œ ์ƒ๋‹จ ๋ถ€๋ถ„๋งŒ ์บก์ฒ˜
print("ํŠน์ • ์˜์—ญ์„ ์ฐพ์ง€ ๋ชปํ•ด ํŽ˜์ด์ง€ ์ƒ๋‹จ ์˜์—ญ ์บก์ฒ˜")
screenshot_bytes = page.screenshot(clip={"x": 0, "y": 100, "width": 1920, "height": 800})
except Exception as selector_error:
print(f"์˜์—ญ ์„ ํƒ ์‹คํŒจ, ์ „์ฒด ํŽ˜์ด์ง€ ์บก์ฒ˜: {selector_error}")
screenshot_bytes = page.screenshot(full_page=True)
screenshot = Image.open(io.BytesIO(screenshot_bytes))
print("์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์™„๋ฃŒ")
return screenshot
except Exception as e:
print(f"์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์‹คํŒจ: {str(e)}")
print(traceback.format_exc())
return None
def analyze_with_claude(image: Image.Image) -> str:
"""Claude API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ๋ถ„์„"""
try:
client = get_claude_client()
# ์ด๋ฏธ์ง€๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ
buffer = io.BytesIO()
image.save(buffer, format="PNG")
image_base64 = base64.b64encode(buffer.getvalue()).decode()
# Claude API ํ˜ธ์ถœ์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
prompt = """
์ด Google Trends ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
๋‹ค์Œ ๋‚ด์šฉ์„ ํฌํ•จํ•ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”:
1. ํ˜„์žฌ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ๊ฒ€์ƒ‰์–ด๋“ค๊ณผ ๊ทธ ์ˆœ์œ„
2. ๊ฐ ๊ฒ€์ƒ‰์–ด์˜ ์ฆ๊ฐ€ ์ถ”์„ธ๋‚˜ ํŠน์ด์‚ฌํ•ญ
3. ๊ฒ€์ƒ‰์–ด๋“ค์—์„œ ๋ฐœ๊ฒฌ๋˜๋Š” ์ฃผ์š” ํŠธ๋ Œ๋“œ๋‚˜ ํŒจํ„ด
4. ์‹œ์‚ฌ์ /๋ฌธํ™”์  ๋งฅ๋ฝ์—์„œ์˜ ํ•ด์„
ํ•œ๊ตญ์–ด๋กœ ์ƒ์„ธํ•˜๊ณ  ๊ตฌ์ฒด์ ์œผ๋กœ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
"""
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1500,
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:
error_msg = f"Claude API ๋ถ„์„ ์‹คํŒจ: {str(e)}"
print(error_msg)
return error_msg
def reinit_browser():
"""๋ธŒ๋ผ์šฐ์ € ์žฌ์ดˆ๊ธฐํ™”"""
global is_browser_ready
print("๋ธŒ๋ผ์šฐ์ € ์žฌ์ดˆ๊ธฐํ™” ์‹œ์ž‘...")
# ๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ
cleanup_browser()
is_browser_ready = False
# ์ƒˆ๋กœ ์ดˆ๊ธฐํ™”
init_browser()
if is_browser_ready:
return "๋ธŒ๋ผ์šฐ์ € ์žฌ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!"
else:
return "๋ธŒ๋ผ์šฐ์ € ์žฌ์ดˆ๊ธฐํ™” ์‹คํŒจ. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”."
def refresh_trends_page():
"""Google Trends ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ"""
global page
if not ensure_browser_ready():
return "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
try:
page.reload(wait_until="networkidle", timeout=30000)
time.sleep(3)
return "ํŽ˜์ด์ง€๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
except Exception as e:
return f"ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ: {str(e)}"
def main_process_screenshot(geo: str, time_range: str, category: str) -> Tuple[Optional[Image.Image], str]:
"""์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜๋งŒ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜"""
try:
# 1. ๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ํ™•์ธ
if not ensure_browser_ready():
return None, "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. '๋ธŒ๋ผ์šฐ์ € ์žฌ์ดˆ๊ธฐํ™”' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์ฃผ์„ธ์š”."
# 2. ํ•„ํ„ฐ ์ ์šฉ
print(f"ํ•„ํ„ฐ ์ ์šฉ ์ค‘: ์ง€์—ญ={geo}, ์‹œ๊ฐ„={time_range}, ์นดํ…Œ๊ณ ๋ฆฌ={category}")
filter_success = apply_filters(geo, time_range, category)
if not filter_success:
return None, "ํ•„ํ„ฐ ์ ์šฉ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
# 3. ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
print("์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
screenshot = capture_screenshot()
if screenshot is None:
return None, "์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
return screenshot, "์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์™„๋ฃŒ! Claude ๋ถ„์„์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค..."
except Exception as e:
error_msg = f"์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(error_msg)
print(traceback.format_exc())
return None, error_msg
def analyze_screenshot_with_claude(screenshot: Image.Image) -> str:
"""Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„"""
if screenshot is None:
return "๋ถ„์„ํ•  ์Šคํฌ๋ฆฐ์ƒท์ด ์—†์Šต๋‹ˆ๋‹ค."
try:
print("Claude API๋กœ ๋ถ„์„ ์ค‘...")
analysis_result = analyze_with_claude(screenshot)
return analysis_result
except Exception as e:
error_msg = f"Claude ๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(error_msg)
return error_msg
def main_process(geo: str, time_range: str, category: str) -> Tuple[Optional[Image.Image], str]:
"""์ „์ฒด ํ”„๋กœ์„ธ์Šค (ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ์ง€)"""
try:
# 1. ๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ํ™•์ธ
if not ensure_browser_ready():
return None, "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. '๋ธŒ๋ผ์šฐ์ € ์žฌ์ดˆ๊ธฐํ™”' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์ฃผ์„ธ์š”."
# 2. ํ•„ํ„ฐ ์ ์šฉ
print(f"ํ•„ํ„ฐ ์ ์šฉ ์ค‘: ์ง€์—ญ={geo}, ์‹œ๊ฐ„={time_range}, ์นดํ…Œ๊ณ ๋ฆฌ={category}")
filter_success = apply_filters(geo, time_range, category)
if not filter_success:
return None, "ํ•„ํ„ฐ ์ ์šฉ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
# 3. ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
print("์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
screenshot = capture_screenshot()
if screenshot is None:
return None, "์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
# 4. Claude API๋กœ ๋ถ„์„
print("Claude API๋กœ ๋ถ„์„ ์ค‘...")
analysis_result = analyze_with_claude(screenshot)
return screenshot, analysis_result
except Exception as e:
error_msg = f"์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
print(error_msg)
print(traceback.format_exc())
return None, error_msg
def check_browser_status():
"""๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ"""
global is_browser_ready, page, browser_thread_id
current_thread_id = threading.current_thread().ident
if not page:
return "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
if not is_browser_ready:
return "๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘์ž…๋‹ˆ๋‹ค..."
try:
current_url = page.url
return f"๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ์™„๋ฃŒ - ํ˜„์žฌ URL: {current_url} (์Šค๋ ˆ๋“œ: {current_thread_id}, ๋ธŒ๋ผ์šฐ์ € ์Šค๋ ˆ๋“œ: {browser_thread_id})"
except Exception as e:
return f"๋ธŒ๋ผ์šฐ์ € ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
# Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
def create_interface():
"""Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ"""
with gr.Blocks(title="Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ", theme=gr.themes.Soft()) as interface:
gr.Markdown("# Google Trends ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ๋ถ„์„๊ธฐ")
gr.Markdown("Google Trends์˜ ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด๋ฅผ ์บก์ฒ˜ํ•˜๊ณ  Claude AI๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.")
# ์ƒํƒœ ์ €์žฅ์„ ์œ„ํ•œ State ์ปดํฌ๋„ŒํŠธ
screenshot_state = gr.State(None)
with gr.Row():
with gr.Column(scale=1):
geo_dropdown = gr.Dropdown(
choices=["KR", "US", "JP", "GB", "DE", "FR"],
value="KR",
label="์ง€์—ญ ์„ ํƒ"
)
time_dropdown = gr.Dropdown(
choices=["24์‹œ๊ฐ„", "7์ผ", "30์ผ", "90์ผ", "12๊ฐœ์›”"],
value="24์‹œ๊ฐ„",
label="๊ธฐ๊ฐ„ ์„ ํƒ"
)
category_dropdown = gr.Dropdown(
choices=["๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "์Šคํฌ์ธ ", "๋น„์ฆˆ๋‹ˆ์Šค", "๊ณผํ•™๊ธฐ์ˆ ", "๊ฑด๊ฐ•"],
value="๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ",
label="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ"
)
with gr.Row():
capture_btn = gr.Button("๐ŸŽฏ ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜", variant="primary")
analyze_btn = gr.Button("๐Ÿค– Claude ๋ถ„์„", variant="secondary", interactive=False)
with gr.Row():
refresh_btn = gr.Button("๐Ÿ”„ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
reinit_btn = gr.Button("โš™๏ธ ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”", variant="secondary")
status_btn = gr.Button("๐Ÿ“Š ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ", variant="secondary")
with gr.Column(scale=2):
screenshot_output = gr.Image(label="Google Trends ์Šคํฌ๋ฆฐ์ƒท")
analysis_output = gr.Textbox(
label="Claude AI ๋ถ„์„ ๊ฒฐ๊ณผ",
lines=10,
max_lines=20,
placeholder="์Šคํฌ๋ฆฐ์ƒท์„ ์บก์ฒ˜ํ•œ ํ›„ 'Claude ๋ถ„์„' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”."
)
status_output = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€")
# ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
# ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ๋ฒ„ํŠผ
def on_capture_click(geo, time_range, category):
screenshot, message = main_process_screenshot(geo, time_range, category)
if screenshot is not None:
# ๋ถ„์„ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
return screenshot, screenshot, message, gr.update(interactive=True)
else:
return None, None, message, gr.update(interactive=False)
capture_btn.click(
fn=on_capture_click,
inputs=[geo_dropdown, time_dropdown, category_dropdown],
outputs=[screenshot_output, screenshot_state, status_output, analyze_btn]
)
# Claude ๋ถ„์„ ๋ฒ„ํŠผ
def on_analyze_click(screenshot):
if screenshot is None:
return "๋ถ„์„ํ•  ์Šคํฌ๋ฆฐ์ƒท์ด ์—†์Šต๋‹ˆ๋‹ค. ๋จผ์ € '์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”."
analysis_result = analyze_screenshot_with_claude(screenshot)
return analysis_result
analyze_btn.click(
fn=on_analyze_click,
inputs=[screenshot_state],
outputs=[analysis_output]
)
# ๊ธฐํƒ€ ๋ฒ„ํŠผ๋“ค
refresh_btn.click(
fn=refresh_trends_page,
outputs=status_output
)
reinit_btn.click(
fn=reinit_browser,
outputs=status_output
)
status_btn.click(
fn=check_browser_status,
outputs=status_output
)
return interface
def main():
"""๋ฉ”์ธ ํ•จ์ˆ˜"""
print("=" * 50)
print("Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ ์‹œ์ž‘!")
print("=" * 50)
# API ํ‚ค ์กด์žฌ ์—ฌ๋ถ€๋งŒ ํ™•์ธ (์‹ค์ œ ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”๋Š” ๋‚˜์ค‘์—)
api_key = os.getenv("ANTHROPIC_API_KEY")
if not api_key:
print("โœ— ANTHROPIC_API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
return
else:
print("โœ“ Claude API ํ‚ค ํ™•์ธ๋จ")
print("โœ“ Gradio ์›น ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
# ํ™˜๊ฒฝ ํ™•์ธ
if os.getenv("SPACE_ID"):
print("โœ“ Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์ค‘...")
print("โœ“ ๋ธŒ๋ผ์šฐ์ €๋Š” ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ ์‹œ ์ž๋™์œผ๋กœ ์ดˆ๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค.")
# ์ข…๋ฃŒ ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ
atexit.register(cleanup_browser)
# Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ ๋ฐ ์‹คํ–‰
interface = create_interface()
# Hugging Face Space์—์„œ๋Š” share=False๋กœ ์‹คํ–‰
interface.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
quiet=False
)
if __name__ == "__main__":
main()