HanJun's picture
Update app.py
16cf3af verified
raw
history blame
15.5 kB
import os
import time
import base64
import io
import traceback
import threading
import atexit
from datetime import datetime
from typing import Optional, Tuple
import gradio as gr
import anthropic
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, WebDriverException
from webdriver_manager.chrome import ChromeDriverManager
from PIL import Image
import requests
from dotenv import load_dotenv
# ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()
# ์ „์—ญ ๋ณ€์ˆ˜
driver = None
driver_lock = threading.Lock()
is_browser_ready = False
# 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 init_browser():
"""๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  Google Trends ํŽ˜์ด์ง€๋กœ ์ด๋™"""
global driver, is_browser_ready
try:
print("Chrome ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘...")
# Chrome ์˜ต์…˜ ์„ค์ • (Hugging Face Space ์ตœ์ ํ™”)
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-web-security")
chrome_options.add_argument("--allow-running-insecure-content")
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_argument("--disable-features=VizDisplayCompositor")
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--disable-background-timer-throttling")
chrome_options.add_argument("--disable-backgrounding-occluded-windows")
chrome_options.add_argument("--disable-renderer-backgrounding")
chrome_options.add_argument("--disable-features=TranslateUI")
chrome_options.add_argument("--disable-ipc-flooding-protection")
chrome_options.add_argument("--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")
# ChromeDriver ์„œ๋น„์Šค ์„ค์ •
service = Service(ChromeDriverManager().install())
# ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
driver = webdriver.Chrome(service=service, options=chrome_options)
driver.set_page_load_timeout(30)
driver.implicitly_wait(10)
print("Google Trends ํŽ˜์ด์ง€๋กœ ์ด๋™ ์ค‘...")
driver.get("https://trends.google.com/trending?geo=KR")
# ํŽ˜์ด์ง€ ๋กœ๋”ฉ ๋Œ€๊ธฐ
WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
# ์ถ”๊ฐ€ ๋กœ๋”ฉ ์‹œ๊ฐ„
time.sleep(5)
is_browser_ready = True
print("๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!")
except Exception as e:
print(f"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
print(traceback.format_exc())
is_browser_ready = False
if driver:
try:
driver.quit()
except:
pass
driver = None
def cleanup_browser():
"""๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
global driver
if driver:
try:
driver.quit()
print("๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์™„๋ฃŒ")
except:
pass
driver = None
def apply_filters(geo: str, time_range: str, category: str) -> bool:
"""Google Trends ํ•„ํ„ฐ ์ ์šฉ"""
global driver
if not driver or not is_browser_ready:
return False
try:
with driver_lock:
# ํ•„ํ„ฐ ๋ฒ„ํŠผ๋“ค์„ ์ฐพ๊ณ  ํด๋ฆญ
try:
# ์ง€์—ญ ์„ค์ •
if geo != "KR": # ๊ธฐ๋ณธ๊ฐ’์ด ์•„๋‹Œ ๊ฒฝ์šฐ
geo_elements = driver.find_elements(By.XPATH, f"//div[contains(text(), '{geo}')]")
if geo_elements:
driver.execute_script("arguments[0].click();", geo_elements[0])
time.sleep(1)
# ์‹œ๊ฐ„ ๋ฒ”์œ„ ์„ค์ •
if time_range != "24์‹œ๊ฐ„":
time_elements = driver.find_elements(By.XPATH, f"//div[contains(text(), '{time_range}')]")
if time_elements:
driver.execute_script("arguments[0].click();", time_elements[0])
time.sleep(1)
# ์นดํ…Œ๊ณ ๋ฆฌ ์„ค์ •
if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
category_elements = driver.find_elements(By.XPATH, f"//div[contains(text(), '{category}')]")
if category_elements:
driver.execute_script("arguments[0].click();", category_elements[0])
time.sleep(1)
# ํ•„ํ„ฐ ์ ์šฉ ํ›„ ๋กœ๋”ฉ ๋Œ€๊ธฐ
time.sleep(3)
return True
except Exception as filter_error:
print(f"ํ•„ํ„ฐ ์ ์šฉ ์ค‘ ์˜ค๋ฅ˜: {filter_error}")
return True # ํ•„ํ„ฐ ์ ์šฉ ์‹คํŒจํ•ด๋„ ์Šคํฌ๋ฆฐ์ƒท์€ ์ฐ์„ ์ˆ˜ ์žˆ์Œ
except Exception as e:
print(f"ํ•„ํ„ฐ ์ ์šฉ ์‹คํŒจ: {str(e)}")
return False
def capture_screenshot() -> Optional[Image.Image]:
"""ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜"""
global driver
if not driver or not is_browser_ready:
return None
try:
with driver_lock:
# ํŽ˜์ด์ง€๊ฐ€ ์™„์ „ํžˆ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
time.sleep(2)
# ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
screenshot_data = driver.get_screenshot_as_png()
screenshot = Image.open(io.BytesIO(screenshot_data))
return screenshot
except Exception as e:
print(f"์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์‹คํŒจ: {str(e)}")
return None
def analyze_with_claude(image: Image.Image, user_prompt: str = "") -> 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 ํ˜ธ์ถœ์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
default_prompt = """
์ด Google Trends ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
๋‹ค์Œ ๋‚ด์šฉ์„ ํฌํ•จํ•ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”:
1. ํ˜„์žฌ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ๊ฒ€์ƒ‰์–ด๋“ค๊ณผ ๊ทธ ์ˆœ์œ„
2. ๊ฐ ๊ฒ€์ƒ‰์–ด์˜ ์ฆ๊ฐ€ ์ถ”์„ธ๋‚˜ ํŠน์ด์‚ฌํ•ญ
3. ๊ฒ€์ƒ‰์–ด๋“ค์—์„œ ๋ฐœ๊ฒฌ๋˜๋Š” ์ฃผ์š” ํŠธ๋ Œ๋“œ๋‚˜ ํŒจํ„ด
4. ์‹œ์‚ฌ์ /๋ฌธํ™”์  ๋งฅ๋ฝ์—์„œ์˜ ํ•ด์„
ํ•œ๊ตญ์–ด๋กœ ์ƒ์„ธํ•˜๊ณ  ๊ตฌ์ฒด์ ์œผ๋กœ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
"""
prompt = user_prompt if user_prompt.strip() else default_prompt
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 driver, 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 driver
if not driver or not is_browser_ready:
return "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
try:
with driver_lock:
driver.refresh()
time.sleep(3)
return "ํŽ˜์ด์ง€๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
except Exception as e:
return f"ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ: {str(e)}"
def main_process(geo: str, time_range: str, category: str, user_prompt: str) -> Tuple[Optional[Image.Image], str]:
"""๋ฉ”์ธ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜"""
if not is_browser_ready:
return None, "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด์ฃผ์„ธ์š”."
try:
# 1. ํ•„ํ„ฐ ์ ์šฉ
print(f"ํ•„ํ„ฐ ์ ์šฉ ์ค‘: ์ง€์—ญ={geo}, ์‹œ๊ฐ„={time_range}, ์นดํ…Œ๊ณ ๋ฆฌ={category}")
filter_success = apply_filters(geo, time_range, category)
if not filter_success:
return None, "ํ•„ํ„ฐ ์ ์šฉ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
# 2. ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
print("์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
screenshot = capture_screenshot()
if screenshot is None:
return None, "์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
# 3. Claude API๋กœ ๋ถ„์„
print("Claude API๋กœ ๋ถ„์„ ์ค‘...")
analysis_result = analyze_with_claude(screenshot, user_prompt)
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, driver
if not driver:
return "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
if not is_browser_ready:
return "๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘์ž…๋‹ˆ๋‹ค..."
try:
with driver_lock:
current_url = driver.current_url
return f"๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ์™„๋ฃŒ - ํ˜„์žฌ URL: {current_url}"
except:
return "๋ธŒ๋ผ์šฐ์ € ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค."
# 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๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.")
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="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ"
)
user_prompt = gr.Textbox(
label="๋ถ„์„ ์š”์ฒญ์‚ฌํ•ญ (์„ ํƒ์‚ฌํ•ญ)",
placeholder="ํŠน๋ณ„ํžˆ ๋ถ„์„ํ•˜๊ณ  ์‹ถ์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ์ž…๋ ฅํ•˜์„ธ์š”...",
lines=3
)
analyze_btn = gr.Button("๋ถ„์„ ์‹œ์ž‘", variant="primary")
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
)
status_output = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€")
# ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
analyze_btn.click(
fn=main_process,
inputs=[geo_dropdown, time_dropdown, category_dropdown, user_prompt],
outputs=[screenshot_output, 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 ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์ค‘...")
# ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” (๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ)
init_thread = threading.Thread(target=init_browser, daemon=True)
init_thread.start()
# ์ข…๋ฃŒ ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ
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()