hanjunjung commited on
Commit
2c476a3
ยท
1 Parent(s): 45cf348
Files changed (1) hide show
  1. app.py +561 -556
app.py CHANGED
@@ -3,20 +3,23 @@ import time
3
  import base64
4
  import json
5
  import traceback
6
- import threading
7
- import subprocess
8
- import sys
9
  from typing import Tuple, Optional
10
  import gradio as gr
11
  import anthropic
 
 
 
 
 
 
12
  from concurrent.futures import ThreadPoolExecutor
13
- from datetime import datetime
14
 
15
  # Hugging Face Space ํ™˜๊ฒฝ ํ™•์ธ
16
  IS_HUGGINGFACE = os.getenv("SPACE_ID") is not None
17
  print(f'IS_HUGGINGFACE = {IS_HUGGINGFACE}')
18
 
19
- # Claude API ์„ค์ •
20
  ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
21
 
22
  if not ANTHROPIC_API_KEY:
@@ -24,6 +27,7 @@ if not ANTHROPIC_API_KEY:
24
  else:
25
  print("Claude API ํ‚ค ํ™•์ธ ์™„๋ฃŒ")
26
 
 
27
  def create_claude_client():
28
  try:
29
  client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
@@ -33,438 +37,439 @@ def create_claude_client():
33
  print(f"Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์‹คํŒจ: {e}")
34
  return None
35
 
36
- def install_system_dependencies():
37
- """์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„"""
38
- try:
39
- print("์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„ ์ค‘...")
40
-
41
- # ํ•„์ˆ˜ ํŒจํ‚ค์ง€ ๋ชฉ๋ก
42
- packages = [
43
- "libnss3",
44
- "libnspr4",
45
- "libatk1.0-0",
46
- "libatk-bridge2.0-0",
47
- "libcups2",
48
- "libatspi2.0-0",
49
- "libxcomposite1",
50
- "libxdamage1",
51
- "libxrandr2",
52
- "libgconf-2-4",
53
- "libxss1",
54
- "libgtk-3-0",
55
- "libasound2"
56
- ]
57
-
58
- # ํŒจํ‚ค์ง€ ์„ค์น˜ ์‹œ๋„ (๊ถŒํ•œ์ด ์žˆ๋Š” ๊ฒฝ์šฐ)
59
- if not IS_HUGGINGFACE:
60
- try:
61
- subprocess.run(["sudo", "apt-get", "update"], check=True, capture_output=True)
62
- subprocess.run(["sudo", "apt-get", "install", "-y"] + packages, check=True, capture_output=True)
63
- print("์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์™„๋ฃŒ")
64
- return True
65
- except subprocess.CalledProcessError as e:
66
- print(f"์‹œ์Šคํ…œ ํŒจํ‚ค์ง€ ์„ค์น˜ ์‹คํŒจ: {e}")
67
 
68
- # Hugging Face Space์—์„œ๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์‹œ๋„
69
  if IS_HUGGINGFACE:
70
- print("Hugging Face Space: ์‹œ์Šคํ…œ ํŒจํ‚ค์ง€ ์„ค์น˜ ๊ถŒํ•œ ์—†์Œ")
71
- return False
72
-
73
- except Exception as e:
74
- print(f"์˜์กด์„ฑ ์„ค์น˜ ์ค‘ ์˜ค๋ฅ˜: {e}")
75
- return False
76
-
77
- def install_playwright_with_deps():
78
- """Playwright์™€ ์˜์กด์„ฑ ์„ค์น˜"""
79
- try:
80
- print("Playwright ์„ค์น˜ ์‹œ์ž‘...")
81
-
82
- # Playwright ์„ค์น˜
83
- result = subprocess.run([
84
- sys.executable, "-m", "pip", "install", "playwright==1.40.0"
85
- ], capture_output=True, text=True, timeout=300)
86
 
87
- if result.returncode != 0:
88
- print(f"Playwright pip ์„ค์น˜ ์‹คํŒจ: {result.stderr}")
89
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- # ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜
92
- result = subprocess.run([
93
- sys.executable, "-m", "playwright", "install", "chromium"
94
- ], capture_output=True, text=True, timeout=300)
95
 
96
- if result.returncode != 0:
97
- print(f"Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜ ์‹คํŒจ: {result.stderr}")
98
- return False
99
 
100
- # ์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„
101
  try:
102
- result = subprocess.run([
103
- sys.executable, "-m", "playwright", "install-deps", "chromium"
104
- ], capture_output=True, text=True, timeout=300)
105
-
106
- if result.returncode == 0:
107
- print("Playwright ์˜์กด์„ฑ ์„ค์น˜ ์™„๋ฃŒ")
108
- return True
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  else:
110
- print(f"Playwright ์˜์กด์„ฑ ์„ค์น˜ ์‹คํŒจ: {result.stderr}")
111
- print("์ˆ˜๋™ ์˜์กด์„ฑ ์„ค์น˜ ์‹œ๋„...")
112
- return install_system_dependencies()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- except subprocess.CalledProcessError:
115
- print("Playwright install-deps ์‹คํŒจ, ์ˆ˜๋™ ์„ค์น˜ ์‹œ๋„")
116
- return install_system_dependencies()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- except Exception as e:
119
- print(f"Playwright ์„ค์น˜ ์ค‘ ์˜ค๋ฅ˜: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  return False
121
-
122
- class SmartGoogleTrendsAutomator:
123
- """์Šค๋งˆํŠธ Google Trends ์ž๋™ํ™” (์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ• ์‹œ๋„)"""
124
 
125
- def __init__(self):
126
- self.method = None
127
- self.browser = None
128
- self.page = None
129
- self.driver = None
130
- self.is_ready = False
131
- self.lock = threading.Lock()
 
132
 
133
- def initialize_browser(self):
134
- """๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” (์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ• ์‹œ๋„)"""
135
- with self.lock:
136
- if self.is_ready:
137
- return True
138
-
139
- # ๋ฐฉ๋ฒ• 1: Playwright ์‹œ๋„
140
- if self.try_playwright():
141
- self.method = "playwright"
142
- self.is_ready = True
143
- return True
144
-
145
- # ๋ฐฉ๋ฒ• 2: Selenium ์‹œ๋„
146
- if self.try_selenium():
147
- self.method = "selenium"
148
- self.is_ready = True
149
- return True
150
-
151
- # ๋ฐฉ๋ฒ• 3: ๋Œ€์ฒด ๋ฐฉ๋ฒ• (API ๊ธฐ๋ฐ˜)
152
- if self.try_alternative():
153
- self.method = "alternative"
154
- self.is_ready = True
155
- return True
156
-
157
- return False
 
 
 
 
 
158
 
159
- def try_playwright(self):
160
- """Playwright ์ดˆ๊ธฐํ™” ์‹œ๋„"""
161
  try:
162
- print("Playwright ๋ฐฉ๋ฒ• ์‹œ๋„ ์ค‘...")
 
 
 
 
163
 
164
- # ์˜์กด์„ฑ ์„ค์น˜
165
- if not install_playwright_with_deps():
166
- print("Playwright ์˜์กด์„ฑ ์„ค์น˜ ์‹คํŒจ")
167
  return False
168
 
169
- # Playwright ์ž„ํฌํŠธ
170
- from playwright.sync_api import sync_playwright
171
-
172
- # ๋ธŒ๋ผ์šฐ์ € ์‹œ์ž‘
173
- self.playwright = sync_playwright().start()
174
- self.browser = self.playwright.chromium.launch(
175
- headless=True,
176
- args=[
177
- '--no-sandbox',
178
- '--disable-dev-shm-usage',
179
- '--disable-gpu',
180
- '--disable-software-rasterizer',
181
- '--disable-web-security',
182
- '--disable-features=VizDisplayCompositor'
183
- ]
184
- )
185
 
186
- self.page = self.browser.new_page()
187
- self.page.set_user_agent(
188
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
189
- )
 
 
190
 
191
- # ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€ ๋กœ๋“œ
192
- self.page.goto("https://trends.google.com/trending?geo=KR&hl=ko", timeout=30000)
193
- self.page.wait_for_load_state('networkidle', timeout=15000)
 
 
 
 
 
 
194
 
195
- print("Playwright ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
196
- return True
197
 
198
  except Exception as e:
199
- print(f"Playwright ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
200
- self.cleanup_playwright()
201
  return False
202
 
203
- def try_selenium(self):
204
- """Selenium ์ดˆ๊ธฐํ™” ์‹œ๋„"""
205
  try:
206
- print("Selenium ๋ฐฉ๋ฒ• ์‹œ๋„ ์ค‘...")
207
-
208
- from selenium import webdriver
209
- from selenium.webdriver.chrome.options import Options
210
- from selenium.webdriver.common.by import By
211
- from selenium.webdriver.support.ui import WebDriverWait
212
- from selenium.webdriver.support import expected_conditions as EC
213
-
214
- options = Options()
215
- options.add_argument("--headless")
216
- options.add_argument("--no-sandbox")
217
- options.add_argument("--disable-dev-shm-usage")
218
- options.add_argument("--disable-gpu")
219
- options.add_argument("--disable-software-rasterizer")
220
-
221
- # Chrome ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฒฝ๋กœ ์ˆ˜๋™ ์„ค์ • ์‹œ๋„
222
- chrome_paths = [
223
- "/usr/bin/google-chrome",
224
- "/usr/bin/google-chrome-stable",
225
- "/usr/bin/chromium-browser",
226
- "/usr/bin/chromium",
227
- "/opt/google/chrome/google-chrome"
228
  ]
229
 
230
- for chrome_path in chrome_paths:
231
- if os.path.exists(chrome_path):
232
- options.binary_location = chrome_path
233
- print(f"Chrome ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐœ๊ฒฌ: {chrome_path}")
234
- break
235
-
236
- # webdriver-manager ์‹œ๋„
237
- try:
238
- from webdriver_manager.chrome import ChromeDriverManager
239
- from selenium.webdriver.chrome.service import Service
240
-
241
- service = Service(ChromeDriverManager().install())
242
- self.driver = webdriver.Chrome(service=service, options=options)
243
 
244
- except Exception as wm_error:
245
- print(f"webdriver-manager ์‹คํŒจ: {wm_error}")
246
- # ์‹œ์Šคํ…œ chromedriver ์‹œ๋„
247
- self.driver = webdriver.Chrome(options=options)
248
-
249
- # ํ…Œ์ŠคํŠธ ํŽ˜์ด์ง€ ๋กœ๋“œ
250
- self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
251
- WebDriverWait(self.driver, 10).until(
252
- EC.presence_of_element_located((By.TAG_NAME, "body"))
253
- )
 
 
 
 
 
 
 
 
254
 
255
- print("Selenium ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
256
- return True
257
 
258
  except Exception as e:
259
- print(f"Selenium ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
260
- self.cleanup_selenium()
261
  return False
262
 
263
- def try_alternative(self):
264
- """๋Œ€์ฒด ๋ฐฉ๋ฒ• (API/์Šคํฌ๋ž˜ํ•‘ ๊ธฐ๋ฐ˜)"""
265
  try:
266
- print("๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‹œ๋„ ์ค‘...")
267
-
268
- import requests
269
- from bs4 import BeautifulSoup
270
-
271
- # Google Trends RSS ์‹œ๋„
272
- rss_url = "https://trends.google.com/trending/rss?geo=KR"
273
-
274
- session = requests.Session()
275
- session.headers.update({
276
- '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'
277
- })
278
-
279
- response = session.get(rss_url, timeout=10)
280
- response.raise_for_status()
281
-
282
- # RSS ํŒŒ์‹ฑ ํ…Œ์ŠคํŠธ
283
- from xml.etree import ElementTree as ET
284
- root = ET.fromstring(response.content)
285
 
286
- # ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ ํ™•์ธ
287
- items = root.findall('.//item')
288
- if len(items) > 0:
289
- print(f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์„ฑ๊ณต: {len(items)}๊ฐœ ํŠธ๋ Œ๋“œ ๋ฐœ๊ฒฌ")
290
- self.session = session
291
- return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  return False
294
 
295
  except Exception as e:
296
- print(f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‹คํŒจ: {e}")
297
  return False
298
 
299
- def capture_screenshot(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
300
- """์„ ํƒ๋œ ๋ฐฉ๋ฒ•์œผ๋กœ ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜"""
301
-
302
- if not self.is_ready:
303
- if not self.initialize_browser():
304
- return None, False, "๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ๋ฐฉ๋ฒ• ์‹คํŒจ"
305
 
306
  try:
307
- if self.method == "playwright":
308
- return self.capture_with_playwright(region, period, category)
309
- elif self.method == "selenium":
310
- return self.capture_with_selenium(region, period, category)
311
- elif self.method == "alternative":
312
- return self.capture_with_alternative(region, period, category)
313
- else:
314
- return None, False, "์•Œ ์ˆ˜ ์—†๋Š” ๋ฐฉ๋ฒ•"
315
-
316
- except Exception as e:
317
- return None, False, f"์บก์ฒ˜ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
318
-
319
- def capture_with_playwright(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
320
- """Playwright๋กœ ์บก์ฒ˜"""
321
- try:
322
- # ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
323
- self.page.reload(wait_until='domcontentloaded')
324
- self.page.wait_for_load_state('networkidle', timeout=10000)
325
-
326
- # ์„ค์ • ๋ณ€๊ฒฝ (๊ฐ„๋‹จํ•œ ๋ฒ„์ „)
327
- time.sleep(2)
328
-
329
- # ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
330
- screenshot_bytes = self.page.screenshot(full_page=False)
331
- screenshot_b64 = base64.b64encode(screenshot_bytes).decode()
332
 
333
- return screenshot_b64, True, f"Playwright ์บก์ฒ˜ ์„ฑ๊ณต: {region}"
 
 
334
 
335
- except Exception as e:
336
- return None, False, f"Playwright ์บก์ฒ˜ ์‹คํŒจ: {str(e)}"
337
-
338
- def capture_with_selenium(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
339
- """Selenium์œผ๋กœ ์บก์ฒ˜"""
340
- try:
341
- # ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
342
- self.driver.refresh()
343
- time.sleep(3)
344
-
345
- # ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
346
- screenshot = self.driver.get_screenshot_as_png()
347
- screenshot_b64 = base64.b64encode(screenshot).decode()
348
 
349
- return screenshot_b64, True, f"Selenium ์บก์ฒ˜ ์„ฑ๊ณต: {region}"
350
-
351
- except Exception as e:
352
- return None, False, f"Selenium ์บก์ฒ˜ ์‹คํŒจ: {str(e)}"
353
-
354
- def capture_with_alternative(self, region: str, period: str, category: str) -> Tuple[Optional[str], bool, str]:
355
- """๋Œ€์ฒด ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘"""
356
- try:
357
- # RSS ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘
358
- rss_url = f"https://trends.google.com/trending/rss?geo=KR"
359
 
360
- response = self.session.get(rss_url, timeout=10)
361
- response.raise_for_status()
362
 
363
- # ๊ฐ€์งœ ์Šคํฌ๋ฆฐ์ƒท ์ƒ์„ฑ (์‹ค์ œ๋กœ๋Š” HTML ๊ธฐ๋ฐ˜ ์‹œ๊ฐํ™”)
364
- fake_screenshot = self.generate_fake_screenshot(response.content, region)
365
 
366
- return fake_screenshot, True, f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์„ฑ๊ณต: {region}"
 
 
 
 
 
 
367
 
368
- except Exception as e:
369
- return None, False, f"๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‹คํŒจ: {str(e)}"
370
-
371
- def generate_fake_screenshot(self, rss_content: bytes, region: str) -> str:
372
- """RSS ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ€์งœ ์Šคํฌ๋ฆฐ์ƒท ์ƒ์„ฑ"""
373
- try:
374
- from xml.etree import ElementTree as ET
375
- import matplotlib.pyplot as plt
376
- import matplotlib.font_manager as fm
377
- from io import BytesIO
378
 
379
- # RSS ํŒŒ์‹ฑ
380
- root = ET.fromstring(rss_content)
381
- trends = []
 
 
 
 
 
382
 
383
- for item in root.findall('.//item')[:10]:
384
- title = item.find('title')
385
- if title is not None:
386
- trends.append(title.text)
387
 
388
- # ์ฐจํŠธ ์ƒ์„ฑ
389
- fig, ax = plt.subplots(figsize=(12, 8))
390
- fig.patch.set_facecolor('white')
391
 
392
- # ํ•œ๊ธ€ ํฐํŠธ ์„ค์ • ์‹œ๋„
393
- try:
394
- plt.rcParams['font.family'] = 'DejaVu Sans'
395
- except:
396
- pass
397
 
398
- # ํŠธ๋ Œ๋“œ ์ฐจํŠธ
399
- y_pos = range(len(trends))
400
- values = [100 - i*10 for i in range(len(trends))]
401
-
402
- bars = ax.barh(y_pos, values, color='#4285f4')
403
- ax.set_yticks(y_pos)
404
- ax.set_yticklabels(trends[::-1]) # ์—ญ์ˆœ์œผ๋กœ ํ‘œ์‹œ
405
- ax.set_xlabel('๊ฒ€์ƒ‰ ๊ด€์‹ฌ๋„')
406
- ax.set_title(f'{region} ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด')
407
-
408
- # ๊ทธ๋ž˜ํ”„๋ฅผ ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜
409
- buffer = BytesIO()
410
- plt.savefig(buffer, format='png', dpi=100, bbox_inches='tight',
411
- facecolor='white', edgecolor='none')
412
- buffer.seek(0)
413
 
414
- screenshot_b64 = base64.b64encode(buffer.getvalue()).decode()
415
 
416
- plt.close(fig)
417
- return screenshot_b64
418
 
419
  except Exception as e:
420
- print(f"๊ฐ€์งœ ์Šคํฌ๋ฆฐ์ƒท ์ƒ์„ฑ ์‹คํŒจ: {e}")
421
- # ๋นˆ ์ด๋ฏธ์ง€ ๋ฐ˜ํ™˜
422
- return ""
423
-
424
- def cleanup_playwright(self):
425
- """Playwright ์ •๋ฆฌ"""
426
- try:
427
- if hasattr(self, 'page') and self.page:
428
- self.page.close()
429
- if hasattr(self, 'browser') and self.browser:
430
- self.browser.close()
431
- if hasattr(self, 'playwright') and self.playwright:
432
- self.playwright.stop()
433
- except:
434
- pass
435
-
436
- def cleanup_selenium(self):
437
- """Selenium ์ •๋ฆฌ"""
438
- try:
439
- if hasattr(self, 'driver') and self.driver:
440
  self.driver.quit()
441
- except:
442
- pass
443
-
444
- def cleanup(self):
445
- """๋ชจ๋“  ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ"""
446
- self.cleanup_playwright()
447
- self.cleanup_selenium()
448
- self.is_ready = False
449
-
450
- # ๊ธ€๋กœ๋ฒŒ ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค
451
- automator = SmartGoogleTrendsAutomator()
452
 
 
453
  def analyze_with_claude(screenshot_b64: str, region: str, period: str, category: str) -> str:
454
- """Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„"""
455
  try:
 
456
  if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
457
- return generate_fallback_analysis(region, period, category)
 
 
 
 
 
458
 
 
459
  claude_client = create_claude_client()
460
 
461
  if claude_client is None:
462
- return generate_fallback_analysis(region, period, category)
463
-
464
- # ๋นˆ ์Šคํฌ๋ฆฐ์ƒท์ธ ๊ฒฝ์šฐ ํ…์ŠคํŠธ ๋ถ„์„๋งŒ
465
- if not screenshot_b64:
466
- return generate_fallback_analysis(region, period, category)
 
467
 
 
468
  prompt = f"""
469
  ์ด Google Trends ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
470
  ์„ค์ •: {region} | {period} | {category}
@@ -474,13 +479,14 @@ def analyze_with_claude(screenshot_b64: str, region: str, period: str, category:
474
  2. ์ฃผ์š” ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ 3-5๊ฐœ์˜ ํŠน์ง• ์„ค๋ช…
475
  3. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํŠน์ด์‚ฌํ•ญ (์žˆ๋Š” ๊ฒฝ์šฐ)
476
 
477
- HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์‹œ๊ฐ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
478
  ํ•œ๊ธ€ ํ…์ŠคํŠธ๋ฅผ ์ •ํ™•ํžˆ ์ฝ์–ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
479
  """
480
 
 
481
  message = claude_client.messages.create(
482
  model="claude-3-5-sonnet-20241022",
483
- max_tokens=1000,
484
  messages=[
485
  {
486
  "role": "user",
@@ -505,230 +511,204 @@ HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์‹œ๊ฐ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
505
  return message.content[0].text
506
 
507
  except Exception as e:
508
- return generate_fallback_analysis(region, period, category, str(e))
509
-
510
- def generate_fallback_analysis(region: str, period: str, category: str, error: str = None) -> str:
511
- """๋Œ€์ฒด ๋ถ„์„ ๊ฒฐ๊ณผ ์ƒ์„ฑ"""
512
-
513
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
514
-
515
- html = f"""
516
- <div style="font-family: 'Segoe UI', sans-serif; max-width: 1000px; margin: 0 auto;">
517
- <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px;">
518
- <h2>{region} ์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ (๋Œ€์ฒด ๋ชจ๋“œ)</h2>
519
- <p>๋ถ„์„ ์‹œ๊ฐ: {timestamp}</p>
520
- <p>์„ค์ •: {region} | {period} | {category}</p>
521
- </div>
522
-
523
- <div style="background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin-bottom: 20px;">
524
- <h4>์•Œ๋ฆผ: ๋Œ€์ฒด ๋ถ„์„ ๋ชจ๋“œ</h4>
525
- <p>๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ์บก์ฒ˜๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜์—ฌ ๋Œ€์ฒด ๋ฐฉ๋ฒ•์œผ๋กœ ๋ถ„์„์„ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.</p>
526
- {f"<p><strong>์˜ค๋ฅ˜ ์ •๋ณด:</strong> {error}</p>" if error else ""}
527
  </div>
528
-
529
- <h3>์˜ˆ์ƒ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด (์ฐธ๊ณ ์šฉ)</h3>
530
- <table style="width: 100%; border-collapse: collapse; margin-bottom: 30px;">
531
- <thead>
532
- <tr style="background-color: #f8f9fa;">
533
- <th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">์ˆœ์œ„</th>
534
- <th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">๊ฒ€์ƒ‰์–ด</th>
535
- <th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">์˜ˆ์ƒ ๊ด€์‹ฌ๋„</th>
536
- <th style="border: 1px solid #dee2e6; padding: 12px; text-align: left;">์นดํ…Œ๊ณ ๋ฆฌ</th>
537
- </tr>
538
- </thead>
539
- <tbody>
540
- """
541
-
542
- # ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
543
- sample_trends = [
544
- ("๋‰ด์Šค ์ด์Šˆ", "๋†’์Œ", "๋‰ด์Šค"),
545
- ("์—ฐ์˜ˆ๊ณ„ ์†Œ์‹", "๋†’์Œ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ"),
546
- ("์Šคํฌ์ธ  ๊ฒฝ๊ธฐ", "์ค‘๊ฐ„", "์Šคํฌ์ธ "),
547
- ("๊ธฐ์ˆ  ๋‰ด์Šค", "์ค‘๊ฐ„", "๊ธฐ์ˆ "),
548
- ("๊ฑด๊ฐ• ์ •๋ณด", "๋‚ฎ์Œ", "๊ฑด๊ฐ•"),
549
- ]
550
-
551
- for i, (keyword, interest, cat) in enumerate(sample_trends, 1):
552
- html += f"""
553
- <tr>
554
- <td style="border: 1px solid #dee2e6; padding: 12px;">{i}</td>
555
- <td style="border: 1px solid #dee2e6; padding: 12px; font-weight: bold;">{keyword}</td>
556
- <td style="border: 1px solid #dee2e6; padding: 12px;">{interest}</td>
557
- <td style="border: 1px solid #dee2e6; padding: 12px;">{cat}</td>
558
- </tr>
559
  """
560
-
561
- html += """
562
- </tbody>
563
- </table>
564
-
565
- <h3>๋ถ„์„ ์ฐธ๊ณ ์‚ฌํ•ญ</h3>
566
- <div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px;">
567
- <ul>
568
- <li><strong>๋ฐ์ดํ„ฐ ํ•œ๊ณ„:</strong> ์‹ค์ œ Google Trends ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ ์˜ˆ์ƒ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.</li>
569
- <li><strong>๋ธŒ๋ผ์šฐ์ € ์ด์Šˆ:</strong> ์‹œ์Šคํ…œ ํ™˜๊ฒฝ์—์„œ ๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰์ด ์ œํ•œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</li>
570
- <li><strong>๊ถŒ์žฅ์‚ฌํ•ญ:</strong> ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ด๋ณด์„ธ์š”.</li>
571
- </ul>
572
- </div>
573
- </div>
574
- """
575
-
576
- return html
577
 
578
- def smart_analysis(region: str, period: str, category: str):
579
- """์Šค๋งˆํŠธ ๋ถ„์„ ํ•จ์ˆ˜ (์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ• ์‹œ๋„)"""
580
 
581
  start_time = time.time()
582
 
 
583
  yield f"""
584
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
585
- <h3>์Šค๋งˆํŠธ Google Trends ๋ถ„์„</h3>
586
  <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
587
- <p>์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•˜์—ฌ ์ตœ์ ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค</p>
588
  </div>
589
  """
590
 
 
 
 
 
 
591
  try:
592
- # 1๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”
 
 
 
 
 
 
 
 
 
 
 
593
  yield f"""
594
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
595
- <h4>1๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘...</h4>
596
- <p>Playwright โ†’ Selenium โ†’ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์ˆœ์„œ๋กœ ์‹œ๋„</p>
 
 
 
 
597
  </div>
598
  """
599
 
600
- # ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”
601
- init_success = automator.initialize_browser()
602
- init_time = time.time() - start_time
603
 
604
- if init_success:
 
605
  yield f"""
606
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
607
- <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: {automator.method.upper()} ์ดˆ๊ธฐํ™” ์„ฑ๊ณต ({init_time:.1f}์ดˆ)</h4>
608
- <p>์„ ํƒ๋œ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค</p>
609
- </div>
610
-
611
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
612
- <h4>2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์ค‘...</h4>
613
- <p>Google Trends์—์„œ {region} ์ง€์—ญ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค</p>
614
- </div>
615
- """
616
- else:
617
- yield f"""
618
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FFA500 0%, #FF8C00 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
619
- <h4>1๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์‹คํŒจ ({init_time:.1f}์ดˆ)</h4>
620
- <p>๋Œ€์ฒด ๋ฐฉ๋ฒ•์œผ๋กœ ๋ถ„์„์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค</p>
621
  </div>
622
 
623
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
624
- <h4>2๋‹จ๊ณ„: ๋Œ€์ฒด ๋ถ„์„ ์ค‘...</h4>
625
- <p>๋ธŒ๋ผ์šฐ์ € ์—†์ด ๊ฐ€๋Šฅํ•œ ๋ฒ”์œ„์—์„œ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค</p>
 
 
 
 
626
  </div>
627
  """
628
 
629
- # 2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ์บก์ฒ˜
630
  with ThreadPoolExecutor(max_workers=1) as executor:
631
  future = executor.submit(
632
- automator.capture_screenshot,
633
- region, period, category
634
  )
635
 
636
- # ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
637
  while not future.done():
638
- elapsed = time.time() - start_time
639
- yield f"""
640
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
641
- <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: {automator.method.upper() if automator.method else '๋Œ€์ฒด๋ฐฉ๋ฒ•'} ({init_time:.1f}์ดˆ)</h4>
642
- </div>
643
-
644
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
645
- <h4>2๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์ค‘... ({elapsed:.1f}์ดˆ)</h4>
646
- <p>ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค</p>
647
- </div>
648
- """
 
 
 
649
  time.sleep(0.5)
650
 
 
651
  screenshot_b64, success, status_msg = future.result()
652
 
653
- capture_time = time.time() - start_time
 
 
 
 
 
 
 
 
 
 
 
 
654
 
655
- # 3๋‹จ๊ณ„: AI ๋ถ„์„
656
  yield f"""
657
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
658
- <h4>2๋‹จ๊ณ„ ์™„๋ฃŒ: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ {"์„ฑ๊ณต" if success else "๋ถ€๋ถ„ ์„ฑ๊ณต"} ({capture_time:.1f}์ดˆ)</h4>
659
- <p>{status_msg}</p>
660
  </div>
661
 
662
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #9C27B0 0%, #8E24AA 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
663
- <h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ค‘...</h4>
664
- <p>Claude AI๊ฐ€ ์ˆ˜์ง‘๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค</p>
 
665
  </div>
666
  """
667
 
668
- # AI ๋ถ„์„ ์‹คํ–‰
669
  ai_start_time = time.time()
 
670
 
 
671
  with ThreadPoolExecutor(max_workers=1) as executor:
672
  ai_future = executor.submit(
673
  analyze_with_claude,
674
  screenshot_b64, region, period, category
675
  )
676
 
 
677
  while not ai_future.done():
678
  ai_elapsed = time.time() - ai_start_time
679
  total_elapsed = time.time() - start_time
680
  yield f"""
681
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
682
- <h4>2๋‹จ๊ณ„ ์™„๋ฃŒ: ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ {"์„ฑ๊ณต" if success else "๋ถ€๋ถ„ ์„ฑ๊ณต"} ({capture_time:.1f}์ดˆ)</h4>
683
- <p>{status_msg}</p>
684
  </div>
685
 
686
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #9C27B0 0%, #8E24AA 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
687
- <h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ค‘... ({ai_elapsed:.1f}์ดˆ)</h4>
688
- <p>์ด ๊ฒฝ๊ณผ ์‹œ๊ฐ„: {total_elapsed:.1f}์ดˆ</p>
 
689
  </div>
690
  """
691
  time.sleep(0.5)
692
 
693
  analysis_result = ai_future.result()
694
 
 
695
  total_time = time.time() - start_time
696
 
697
- # ์ตœ์ข… ๊ฒฐ๊ณผ
698
  yield f"""
699
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
700
- <h3>์Šค๋งˆํŠธ ๋ถ„์„ ์™„๋ฃŒ ({total_time:.1f}์ดˆ)</h3>
701
- <p><strong>์‚ฌ์šฉ ๋ฐฉ๋ฒ•:</strong> {automator.method.upper() if automator.method else '๋Œ€์ฒด๋ฐฉ๋ฒ•'} | <strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
 
702
  </div>
703
 
704
  {analysis_result}
705
 
706
  <div style="margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center;">
707
  <p style="margin: 0; font-size: 14px;">
708
- <strong>์Šค๋งˆํŠธ ๋ถ„์„์˜ ์žฅ์ </strong><br>
709
- ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์ž๋™์œผ๋กœ ์‹œ๋„ํ•˜์—ฌ ํ™˜๊ฒฝ์— ๊ด€๊ณ„์—†์ด ์ตœ์ ์˜ ๊ฒฐ๊ณผ ์ œ๊ณต
710
  </p>
711
  </div>
712
 
713
  <div style="margin-top: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 5px; font-size: 12px; color: #666; text-align: center;">
714
- <p style="margin: 5px 0;"><strong>์‚ฌ์šฉ๋œ ๋ฐฉ๋ฒ•:</strong> {automator.method.upper() if automator.method else '๋Œ€์ฒด๋ฐฉ๋ฒ•'}</p>
715
- <p style="margin: 5px 0;"><strong>์ด ๋ถ„์„ ์‹œ๊ฐ„:</strong> {total_time:.1f}์ดˆ</p>
716
- <p style="margin: 5px 0;"><strong>ํ™˜๊ฒฝ:</strong> {"Hugging Face Space" if IS_HUGGINGFACE else "๋กœ์ปฌ"}</p>
717
  </div>
718
  """
719
 
720
- print(f"์Šค๋งˆํŠธ ๋ถ„์„ ์™„๋ฃŒ: {total_time:.1f}์ดˆ, ๋ฐฉ๋ฒ•: {automator.method}")
721
 
722
  except Exception as e:
723
  error_details = traceback.format_exc()
724
- print(f"์Šค๋งˆํŠธ ๋ถ„์„ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
725
 
726
  yield f"""
727
  <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
728
- <h3>์Šค๋งˆํŠธ ๋ถ„์„ ์˜ค๋ฅ˜</h3>
729
  <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
730
  <details>
731
- <summary>์ƒ์„ธ ์˜ค๋ฅ˜ ์ •๋ณด</summary>
732
  <pre style="background-color: #f5f5f5; padding: 10px; margin-top: 10px; overflow-x: auto; font-size: 11px;">
733
  {error_details}
734
  </pre>
@@ -737,12 +717,12 @@ def smart_analysis(region: str, period: str, category: str):
737
  </div>
738
  """
739
 
740
- def create_smart_interface():
741
- """์Šค๋งˆํŠธ Gradio ์ธํ„ฐํŽ˜์ด์Šค"""
742
 
743
  with gr.Blocks(
744
  theme=gr.themes.Soft(),
745
- title="์Šค๋งˆํŠธ Google Trends ๋ถ„์„๊ธฐ",
746
  css="""
747
  .gradio-container {
748
  max-width: 1200px !important;
@@ -753,114 +733,126 @@ def create_smart_interface():
753
  """
754
  ) as interface:
755
 
 
756
  gr.HTML("""
757
  <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
758
- <h1>์Šค๋งˆํŠธ Google Trends ๋ถ„์„๊ธฐ</h1>
759
- <p><strong>๋‹ค์ค‘ ๋ฐฉ๋ฒ• ์ง€์›:</strong> Playwright โ†’ Selenium โ†’ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์ž๋™ ์„ ํƒ</p>
760
- <p style="font-size: 14px; opacity: 0.9;">ํ™˜๊ฒฝ์— ๊ด€๊ณ„์—†์ด ์ตœ์ ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค</p>
761
  </div>
762
  """)
763
 
 
764
  with gr.Row():
765
  with gr.Column(scale=1):
766
  region_input = gr.Dropdown(
767
  choices=["๋Œ€ํ•œ๋ฏผ๊ตญ", "์ „์„ธ๊ณ„", "๋ฏธ๊ตญ", "์ผ๋ณธ", "์ค‘๊ตญ"],
768
  value="๋Œ€ํ•œ๋ฏผ๊ตญ",
769
- label="์ง€์—ญ ์„ ํƒ"
 
770
  )
771
 
772
  with gr.Column(scale=1):
773
  period_input = gr.Dropdown(
774
  choices=["์ง€๋‚œ 24์‹œ๊ฐ„", "์ง€๋‚œ 1์‹œ๊ฐ„", "์ง€๋‚œ 4์‹œ๊ฐ„", "์ง€๋‚œ 1์ผ", "์ง€๋‚œ 7์ผ"],
775
  value="์ง€๋‚œ 7์ผ",
776
- label="๊ธฐ๊ฐ„ ์„ ํƒ"
 
777
  )
778
 
779
  with gr.Column(scale=1):
780
  category_input = gr.Dropdown(
781
  choices=["๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ", "๊ฒŒ์ž„", "๊ฑด๊ฐ•", "๊ธฐ์ˆ ", "์Šคํฌ์ธ ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "๋‰ด์Šค", "๋น„์ฆˆ๋‹ˆ์Šค"],
782
  value="๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ",
783
- label="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ"
 
784
  )
785
 
 
786
  analyze_btn = gr.Button(
787
- "์Šค๋งˆํŠธ ๋ถ„์„ ์‹œ์ž‘ (์ž๋™ ๋ฐฉ๋ฒ• ์„ ํƒ)",
788
  variant="primary",
789
  size="lg"
790
  )
791
 
 
792
  output = gr.HTML(
793
  label="์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ฒฐ๊ณผ",
794
  value="""
795
  <div style="text-align: center; padding: 20px; border: 2px dashed #ccc; border-radius: 10px; color: #666;">
796
- <h3>์Šค๋งˆํŠธ ๋ถ„์„๊ธฐ ๋Œ€๊ธฐ ์ค‘</h3>
797
- <p><strong>์ž๋™ ๋ฐฉ๋ฒ• ์„ ํƒ:</strong> ํ™˜๊ฒฝ์— ๋งž๋Š” ์ตœ์ ์˜ ๋ฐฉ๋ฒ•์„ ์ž๋™์œผ๋กœ ์„ ํƒ</p>
798
- <p><strong>์ง€์› ๋ฐฉ๋ฒ•:</strong> Playwright, Selenium, ๋Œ€์ฒด ๋ฐฉ๋ฒ•</p>
799
- <p style="font-size: 14px;">์œ„์˜ ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!</p>
800
  </div>
801
  """
802
  )
803
 
 
804
  analyze_btn.click(
805
- fn=smart_analysis,
806
  inputs=[region_input, period_input, category_input],
807
  outputs=output,
808
- show_progress="hidden"
809
  )
810
 
 
811
  gr.HTML("""
812
  <div style="margin-top: 30px; padding: 20px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px;">
813
- <h3 style="margin-top: 0; color: #333;">์Šค๋งˆํŠธ ๋ถ„์„๊ธฐ์˜ ํŠน์ง•</h3>
814
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: #555;">
815
  <div>
816
- <h4>์ž๋™ ๋ฐฉ๋ฒ• ์„ ํƒ</h4>
817
  <ul style="margin: 0; padding-left: 20px;">
818
- <li>Playwright ์šฐ์„  ์‹œ๋„</li>
819
- <li>์‹คํŒจ์‹œ Selenium ์‹œ๋„</li>
820
- <li>์ตœ์ข…์ ์œผ๋กœ ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‚ฌ์šฉ</li>
821
  </ul>
822
  </div>
823
  <div>
824
- <h4>ํ™˜๊ฒฝ ํ˜ธํ™˜์„ฑ</h4>
825
  <ul style="margin: 0; padding-left: 20px;">
826
- <li>Hugging Face Space ์™„๋ฒฝ ์ง€์›</li>
827
- <li>๋กœ์ปฌ ํ™˜๊ฒฝ ์ตœ์ ํ™”</li>
828
- <li>์‹œ์Šคํ…œ ์ œ์•ฝ ์ž๋™ ํ•ด๊ฒฐ</li>
829
  </ul>
830
  </div>
831
  </div>
832
  </div>
833
  """)
 
 
 
 
 
 
 
 
 
834
 
835
  return interface
836
 
837
- # ์•ฑ ์ข…๋ฃŒ์‹œ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ
838
- import atexit
839
-
840
- def cleanup_on_exit():
841
- """์•ฑ ์ข…๋ฃŒ์‹œ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ"""
842
- global automator
843
- if automator:
844
- automator.cleanup()
845
- print("์•ฑ ์ข…๋ฃŒ: ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ € ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ ์™„๋ฃŒ")
846
-
847
- atexit.register(cleanup_on_exit)
848
-
849
  if __name__ == "__main__":
850
  print("=" * 50)
851
- print("์Šค๋งˆํŠธ Google Trends ๋ถ„์„๊ธฐ ์‹œ์ž‘")
852
  print("=" * 50)
853
 
 
854
  if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
855
- print("ANTHROPIC_API_KEY ๋ฏธ์„ค์ • - ๋Œ€์ฒด ๋ถ„์„ ๋ชจ๋“œ")
 
 
856
  else:
857
- print("Claude API ํ‚ค ํ™•์ธ๋จ")
858
 
859
- print("์Šค๋งˆํŠธ Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
860
- app = create_smart_interface()
 
861
 
 
862
  if os.getenv("SPACE_ID"):
863
- print("Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰")
 
864
  app.launch(
865
  server_name="0.0.0.0",
866
  server_port=7860,
@@ -868,12 +860,25 @@ if __name__ == "__main__":
868
  show_api=False
869
  )
870
  else:
871
- print("๋กœ์ปฌ ์„œ๋ฒ„ ์‹œ์ž‘: http://localhost:7860")
872
- app.launch(
873
- server_name="127.0.0.1",
874
- server_port=7860,
875
- show_error=True,
876
- show_api=False,
877
- share=False,
878
- inbrowser=True
879
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import base64
4
  import json
5
  import traceback
 
 
 
6
  from typing import Tuple, Optional
7
  import gradio as gr
8
  import anthropic
9
+ from selenium import webdriver
10
+ from selenium.webdriver.chrome.options import Options
11
+ from selenium.webdriver.common.by import By
12
+ from selenium.webdriver.support.ui import WebDriverWait
13
+ from selenium.webdriver.support import expected_conditions as EC
14
+ from selenium.common.exceptions import TimeoutException, NoSuchElementException
15
  from concurrent.futures import ThreadPoolExecutor
16
+ import threading
17
 
18
  # Hugging Face Space ํ™˜๊ฒฝ ํ™•์ธ
19
  IS_HUGGINGFACE = os.getenv("SPACE_ID") is not None
20
  print(f'IS_HUGGINGFACE = {IS_HUGGINGFACE}')
21
 
22
+ # Claude API ์„ค์ • (Hugging Face Secrets์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ)
23
  ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
24
 
25
  if not ANTHROPIC_API_KEY:
 
27
  else:
28
  print("Claude API ํ‚ค ํ™•์ธ ์™„๋ฃŒ")
29
 
30
+ # Claude ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑ
31
  def create_claude_client():
32
  try:
33
  client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
 
37
  print(f"Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์‹คํŒจ: {e}")
38
  return None
39
 
40
+ # Google Trends ์ž๋™ํ™” ํด๋ž˜์Šค
41
+ class GoogleTrendsAutomator:
42
+ def __init__(self):
43
+ self.driver = None
44
+
45
+ # Chrome ๋“œ๋ผ์ด๋ฒ„ ์„ค์ •
46
+ def setup_driver(self):
47
+ options = Options()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ # Hugging Face Space ํ™˜๊ฒฝ ์„ค์ •
50
  if IS_HUGGINGFACE:
51
+ print("Hugging Face Space ํ™˜๊ฒฝ, ํ—ค๋“œ๋ฆฌ์Šค ๋ชจ๋“œ๋กœ ์‹คํ–‰")
52
+ options.add_argument("--headless=new") # ์ƒˆ๋กœ์šด ํ—ค๋“œ๋ฆฌ์Šค ๋ชจ๋“œ
53
+ options.add_argument("--no-sandbox")
54
+ options.add_argument("--disable-dev-shm-usage")
55
+ options.add_argument("--disable-gpu")
56
+ options.add_argument("--disable-software-rasterizer")
57
+ options.add_argument("--remote-debugging-port=9222")
58
+ else:
59
+ options.add_argument("--headless")
 
 
 
 
 
 
 
60
 
61
+ # # ํ™˜๊ฒฝ๋ณ„ ์„ค์ •
62
+ # if os.getenv("SPACE_ID"): # Hugging Face Space
63
+ # options.add_argument("--headless")
64
+ # options.add_argument("--no-sandbox")
65
+ # options.add_argument("--disable-dev-shm-usage")
66
+ # options.add_argument("--disable-gpu")
67
+ # else: # ๋กœ์ปฌ ํ™˜๊ฒฝ
68
+ # options.add_argument("--headless")
69
+
70
+ # ์†๋„ ์ตœ์ ํ™” ์„ค์ •
71
+ options.add_argument("--window-size=1280,720") # ํ•ด์ƒ๋„ ์ค„์ž„
72
+ options.add_argument("--disable-blink-features=AutomationControlled")
73
+ options.add_argument("--disable-extensions")
74
+ options.add_argument("--disable-web-security")
75
+ options.add_argument("--allow-running-insecure-content")
76
+ options.add_argument("--disable-background-timer-throttling")
77
+ options.add_argument("--disable-backgrounding-occluded-windows")
78
+ options.add_argument("--disable-renderer-backgrounding")
79
+ options.add_argument("--disable-features=TranslateUI")
80
+ options.add_argument("--disable-ipc-flooding-protection")
81
+
82
+ # ๋„คํŠธ์›Œํฌ ์ตœ์ ํ™”
83
+ options.add_argument("--aggressive-cache-discard")
84
+ options.add_argument("--disable-background-networking")
85
+
86
+ # ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”
87
+ options.add_argument("--memory-pressure-off")
88
+ # options.add_argument("--max_old_space_size=4096")
89
+ options.add_argument("--max_old_space_size=2048")# ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ
90
+
91
+ # ์ด๋ฏธ์ง€/CSS ๋น„ํ™œ์„ฑํ™”๋กœ ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ
92
+ prefs = {
93
+ "profile.managed_default_content_settings.images": 2, # ์ด๋ฏธ์ง€ ์ฐจ๋‹จ
94
+ "profile.default_content_setting_values.notifications": 2, # ์•Œ๋ฆผ ์ฐจ๋‹จ
95
+ }
96
+ options.add_experimental_option("prefs", prefs)
97
 
98
+ # ๋ด‡ ๊ฐ์ง€ ํšŒํ”ผ
99
+ options.add_argument("--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")
100
+ options.add_experimental_option("excludeSwitches", ["enable-automation"])
101
+ options.add_experimental_option('useAutomationExtension', False)
102
 
103
+ # ์–ธ์–ด ์„ค์ •
104
+ options.add_argument("--lang=ko")
105
+ options.add_argument("--accept-lang=ko-KR,ko;q=0.9,en;q=0.8")
106
 
 
107
  try:
108
+ # Hugging Face ํ™˜๊ฒฝ์—์„œ๋Š” webdriver-manager ์šฐ์„  ์‚ฌ์šฉ
109
+ if IS_HUGGINGFACE:
110
+ try:
111
+ from webdriver_manager.chrome import ChromeDriverManager
112
+ from selenium.webdriver.chrome.service import Service
113
+
114
+ print("ChromeDriver ์ž๋™ ์„ค์น˜ ์ค‘...")
115
+ service = Service(ChromeDriverManager().install())
116
+ self.driver = webdriver.Chrome(service=service, options=options)
117
+ print("ChromeDriver ์„ค์น˜ ๋ฐ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
118
+
119
+ except Exception as e:
120
+ print(f"webdriver-manager ์„ค์น˜ ์‹คํŒจ: {e}")
121
+ # ์‹œ์Šคํ…œ ChromeDriver ์‹œ๋„
122
+ try:
123
+ self.driver = webdriver.Chrome(options=options)
124
+ print("์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ ์„ฑ๊ณต")
125
+ except Exception as sys_e:
126
+ print(f"์‹œ์Šคํ…œ ChromeDriver๋„ ์‹คํŒจ: {sys_e}")
127
+ return False
128
  else:
129
+ # ๋กœ์ปฌ ํ™˜๊ฒฝ
130
+ try:
131
+ from webdriver_manager.chrome import ChromeDriverManager
132
+ from selenium.webdriver.chrome.service import Service
133
+
134
+ service = Service(ChromeDriverManager().install())
135
+ self.driver = webdriver.Chrome(service=service, options=options)
136
+ print("๋กœ์ปฌ ChromeDriver ์„ค์น˜ ์„ฑ๊ณต")
137
+ except:
138
+ self.driver = webdriver.Chrome(options=options)
139
+ print("๋กœ์ปฌ ์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ")
140
+ # # ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์œผ๋กœ ChromeDriver ์‹œ๋„
141
+ # driver_created = False
142
+
143
+ # # ๋ฐฉ๋ฒ• 1: webdriver-manager ์‚ฌ์šฉ
144
+ # try:
145
+ # from webdriver_manager.chrome import ChromeDriverManager
146
+ # from selenium.webdriver.chrome.service import Service
147
+
148
+ # service = Service(ChromeDriverManager().install())
149
+ # self.driver = webdriver.Chrome(service=service, options=options)
150
+ # print("webdriver-manager๋กœ ChromeDriver ์ž๋™ ์„ค์น˜ ์„ฑ๊ณต")
151
+ # driver_created = True
152
 
153
+ # except Exception as wm_error:
154
+ # print(f"webdriver-manager ์‹คํŒจ: {wm_error}")
155
+
156
+ # # ๋ฐฉ๋ฒ• 2: ์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ
157
+ # if not driver_created:
158
+ # try:
159
+ # self.driver = webdriver.Chrome(options=options)
160
+ # print("์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ ๏ฟฝ๏ฟฝ๏ฟฝ๊ณต")
161
+ # driver_created = True
162
+ # except Exception as sys_error:
163
+ # print(f"์‹œ์Šคํ…œ ChromeDriver ์‹คํŒจ: {sys_error}")
164
+
165
+ # if not driver_created:
166
+ # print("๋ชจ๋“  ChromeDriver ์ดˆ๊ธฐํ™” ๋ฐฉ๋ฒ• ์‹คํŒจ")
167
+ # return False
168
+
169
+ # ํƒ€์ž„์•„์›ƒ ์„ค์ • (์†๋„ ์ตœ์ ํ™”)
170
+ self.driver.set_page_load_timeout(15) # ํŽ˜์ด์ง€ ๋กœ๋”ฉ ํƒ€์ž„์•„์›ƒ 15์ดˆ
171
+ self.driver.implicitly_wait(2) # ์š”์†Œ ๋Œ€๊ธฐ ์‹œ๊ฐ„ 2์ดˆ๋กœ ๋‹จ์ถ•
172
+
173
+ # ๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ
174
+ try:
175
+ self.driver.execute_script("""
176
+ Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
177
+ Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
178
+ Object.defineProperty(navigator, 'languages', {get: () => ['ko-KR', 'ko', 'en']});
179
+ """)
180
+ except Exception as script_error:
181
+ print(f"๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ ์‹คํŒจ (๋ฌด์‹œ): {script_error}")
182
 
183
+ return True
184
+
185
+ except Exception as e:
186
+ print(f"๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์ตœ์ข… ์‹คํŒจ: {e}")
187
+ return False
188
+
189
+ # ๋น ๋ฅด๊ฒŒ Element ์ฐพ๊ธฐ, Timeout ๋‹จ์ถ• ๊ณ ๋ ค
190
+ def safe_find_element(self, selectors: list, timeout: int = 3):
191
+ for selector_type, selector in selectors:
192
+ try:
193
+ if selector_type == "xpath":
194
+ element = WebDriverWait(self.driver, timeout).until(
195
+ EC.presence_of_element_located((By.XPATH, selector))
196
+ )
197
+ elif selector_type == "css":
198
+ element = WebDriverWait(self.driver, timeout).until(
199
+ EC.presence_of_element_located((By.CSS_SELECTOR, selector))
200
+ )
201
+ elif selector_type == "text":
202
+ element = WebDriverWait(self.driver, timeout).until(
203
+ EC.presence_of_element_located((By.XPATH, f"//*[contains(text(), '{selector}')]"))
204
+ )
205
+
206
+ if element:
207
+ return element
208
+ except:
209
+ continue
210
+ return None
211
+
212
+ # ์ฆ‰์‹œ ํด๋ฆญ
213
+ def safe_click(self, selectors: list, timeout: int = 3) -> bool:
214
+ element = self.safe_find_element(selectors, timeout)
215
+ if element:
216
+ try:
217
+ # JavaScript ํด๋ฆญ ์šฐ์„  ์‹œ๋„ (๋” ๋น ๋ฆ„)
218
+ self.driver.execute_script("arguments[0].click();", element)
219
+ return True
220
+ except:
221
+ try:
222
+ element.click()
223
+ return True
224
+ except:
225
+ return False
226
  return False
 
 
 
227
 
228
+ # ๋ณ‘๋ ฌ๋กœ ์„ค์ • ๋ณ€๊ฒฝ ์‹œ๋„
229
+ def parallel_change_settings(self, region: str, period: str, category: str, progress_callback=None) -> dict:
230
+ results = {
231
+ 'region': False,
232
+ 'period': False,
233
+ 'category': False,
234
+ 'errors': []
235
+ }
236
 
237
+ # ์ˆœ์ฐจ์ ์œผ๋กœ ๋ณ€๊ฒฝ (๋ณ‘๋ ฌ์€ selenium์—์„œ ์•ˆ์ „ํ•˜์ง€ ์•Š์Œ)
238
+ try:
239
+ if region != "๋Œ€ํ•œ๋ฏผ๊ตญ":
240
+ if progress_callback:
241
+ progress_callback("์ง€์—ญ ์„ค์ • ๋ณ€๊ฒฝ ์ค‘...")
242
+ results['region'] = self.change_region(region)
243
+ time.sleep(0.5) # ์ตœ์†Œ ๋Œ€๊ธฐ
244
+ else:
245
+ results['region'] = True
246
+
247
+ if period != "์ง€๋‚œ 24์‹œ๊ฐ„":
248
+ if progress_callback:
249
+ progress_callback("๊ธฐ๊ฐ„ ์„ค์ • ๋ณ€๊ฒฝ ์ค‘...")
250
+ results['period'] = self.change_time_period(period)
251
+ time.sleep(0.5) # ์ตœ์†Œ ๋Œ€๊ธฐ
252
+ else:
253
+ results['period'] = True
254
+
255
+ if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
256
+ if progress_callback:
257
+ progress_callback("์นดํ…Œ๊ณ ๋ฆฌ ์„ค์ • ๋ณ€๊ฒฝ ์ค‘...")
258
+ results['category'] = self.change_category(category)
259
+ time.sleep(0.5) # ์ตœ์†Œ ๋Œ€๊ธฐ
260
+ else:
261
+ results['category'] = True
262
+
263
+ except Exception as e:
264
+ results['errors'].append(str(e))
265
+
266
+ return results
267
 
268
+ # ์ง€์—ญ ๋ณ€๊ฒฝ
269
+ def change_region(self, target_region: str) -> bool:
270
  try:
271
+ region_selectors = [
272
+ ("xpath", "//button[@aria-label='๋Œ€ํ•œ๋ฏผ๊ตญ, ์œ„์น˜ ์„ ํƒ']"),
273
+ ("xpath", "//button[contains(@aria-label, '์œ„์น˜ ์„ ํƒ')]"),
274
+ ("xpath", "//span[contains(text(), '๋Œ€ํ•œ๋ฏผ๊ตญ')]//parent::button"),
275
+ ]
276
 
277
+ if not self.safe_click(region_selectors):
 
 
278
  return False
279
 
280
+ time.sleep(1) # ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ•
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
+ region_mapping = {
283
+ "์ „์„ธ๊ณ„": ["์ „ ์„ธ๊ณ„", "Worldwide"],
284
+ "๋ฏธ๊ตญ": ["๋ฏธ๊ตญ", "United States"],
285
+ "์ผ๋ณธ": ["์ผ๋ณธ", "Japan"],
286
+ "์ค‘๊ตญ": ["์ค‘๊ตญ", "China"]
287
+ }
288
 
289
+ if target_region in region_mapping:
290
+ for region_text in region_mapping[target_region]:
291
+ region_option_selectors = [
292
+ ("xpath", f"//span[contains(text(), '{region_text}')]"),
293
+ ("xpath", f"//*[contains(text(), '{region_text}')]"),
294
+ ]
295
+
296
+ if self.safe_click(region_option_selectors, timeout=2):
297
+ return True
298
 
299
+ return False
 
300
 
301
  except Exception as e:
302
+ print(f"์ง€์—ญ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
 
303
  return False
304
 
305
+ # ๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ
306
+ def change_time_period(self, target_period: str) -> bool:
307
  try:
308
+ period_selectors = [
309
+ ("xpath", "//button[contains(@aria-label, '๊ธฐ๊ฐ„ ์„ ํƒ')]"),
310
+ ("xpath", "//span[contains(text(), '์ง€๋‚œ 24์‹œ๊ฐ„')]//parent::button"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  ]
312
 
313
+ if not self.safe_click(period_selectors):
314
+ return False
 
 
 
 
 
 
 
 
 
 
 
315
 
316
+ time.sleep(1) # ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ•
317
+
318
+ period_mapping = {
319
+ "์ง€๋‚œ 1์‹œ๊ฐ„": ["์ง€๋‚œ 1์‹œ๊ฐ„"],
320
+ "์ง€๋‚œ 4์‹œ๊ฐ„": ["์ง€๋‚œ 4์‹œ๊ฐ„"],
321
+ "์ง€๋‚œ 1์ผ": ["์ง€๋‚œ 1์ผ"],
322
+ "์ง€๋‚œ 7์ผ": ["์ง€๋‚œ 7์ผ", "์ง€๋‚œ ์ฃผ"]
323
+ }
324
+
325
+ if target_period in period_mapping:
326
+ for period_text in period_mapping[target_period]:
327
+ period_option_selectors = [
328
+ ("xpath", f"//span[contains(text(), '{period_text}')]"),
329
+ ("xpath", f"//*[contains(text(), '{period_text}')]"),
330
+ ]
331
+
332
+ if self.safe_click(period_option_selectors, timeout=2):
333
+ return True
334
 
335
+ return False
 
336
 
337
  except Exception as e:
338
+ print(f"๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
 
339
  return False
340
 
341
+ # ์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ
342
+ def change_category(self, target_category: str) -> bool:
343
  try:
344
+ category_selectors = [
345
+ ("xpath", "//button[contains(@aria-label, '์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ')]"),
346
+ ("xpath", "//span[contains(text(), '๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ')]//parent::button"),
347
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
+ if not self.safe_click(category_selectors):
350
+ return False
351
+
352
+ time.sleep(1) # ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ•
353
+
354
+ category_mapping = {
355
+ "๊ฒŒ์ž„": ["๊ฒŒ์ž„"],
356
+ "๊ฑด๊ฐ•": ["๊ฑด๊ฐ•"],
357
+ "๊ธฐ์ˆ ": ["์ปดํ“จํ„ฐ ๋ฐ ์ „์ž์ œํ’ˆ", "๊ธฐ์ˆ "],
358
+ "์Šคํฌ์ธ ": ["์Šคํฌ์ธ "],
359
+ "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ": ["์˜ˆ์ˆ  ๋ฐ ์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ"],
360
+ "๋‰ด์Šค": ["๋‰ด์Šค"],
361
+ "๋น„์ฆˆ๋‹ˆ์Šค": ["๋น„์ฆˆ๋‹ˆ์Šค"]
362
+ }
363
+
364
+ if target_category in category_mapping:
365
+ for category_text in category_mapping[target_category]:
366
+ category_option_selectors = [
367
+ ("xpath", f"//span[contains(text(), '{category_text}')]"),
368
+ ("xpath", f"//*[contains(text(), '{category_text}')]"),
369
+ ]
370
+
371
+ if self.safe_click(category_option_selectors, timeout=2):
372
+ return True
373
 
374
  return False
375
 
376
  except Exception as e:
377
+ print(f"์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
378
  return False
379
 
380
+ # Google Trends ์บก์ฒ˜
381
+ def capture_trends(self, region: str, period: str, category: str, progress_callback=None) -> Tuple[Optional[str], bool, str]:
382
+ error_msg = ""
 
 
 
383
 
384
  try:
385
+ if progress_callback:
386
+ progress_callback("๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์ดˆ๊ธฐํ™” ์ค‘...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
 
388
+ # ๋“œ๋ผ์ด๋ฒ„ ์„ค์ •
389
+ if not self.setup_driver():
390
+ return None, False, "๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์‹คํŒจ"
391
 
392
+ if progress_callback:
393
+ progress_callback("Google Trends ํŽ˜์ด์ง€ ์ ‘์† ์ค‘...")
 
 
 
 
 
 
 
 
 
 
 
394
 
395
+ # Google Trends ์ ‘์† (ํ•œ๊ตญ์–ด ์ง์ ‘ URL)
396
+ self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
 
 
 
 
 
 
 
 
397
 
398
+ # ์ตœ์†Œํ•œ์˜ ๋กœ๋”ฉ ๋Œ€๊ธฐ (1์ดˆ๋กœ ๋‹จ์ถ•)
399
+ time.sleep(1)
400
 
401
+ if progress_callback:
402
+ progress_callback("ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ, ์„ค์ • ๋ณ€๊ฒฝ ์‹œ์ž‘...")
403
 
404
+ # ํŽ˜์ด์ง€ ์ค€๋น„ ํ™•์ธ (๋น ๋ฅธ ์ฒดํฌ)
405
+ try:
406
+ WebDriverWait(self.driver, 5).until(
407
+ EC.presence_of_element_located((By.TAG_NAME, "button"))
408
+ )
409
+ except TimeoutException:
410
+ pass # ๊ณ„์† ์ง„ํ–‰
411
 
412
+ # ์„ค์ • ๋ณ€๊ฒฝ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ)
413
+ settings_result = self.parallel_change_settings(region, period, category, progress_callback)
 
 
 
 
 
 
 
 
414
 
415
+ # ๊ฒฐ๊ณผ ํ™•์ธ
416
+ changes_made = []
417
+ if settings_result['region']:
418
+ changes_made.append(f"์ง€์—ญ: {region}")
419
+ if settings_result['period']:
420
+ changes_made.append(f"๊ธฐ๊ฐ„: {period}")
421
+ if settings_result['category']:
422
+ changes_made.append(f"์นดํ…Œ๊ณ ๋ฆฌ: {category}")
423
 
424
+ if settings_result['errors']:
425
+ error_msg = "\n".join(settings_result['errors'])
 
 
426
 
427
+ if progress_callback:
428
+ progress_callback("์„ค์ • ๋ณ€๊ฒฝ ์™„๋ฃŒ, ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๋Š”์ค‘...")
 
429
 
430
+ # ์ตœ์ข… ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋Œ€๊ธฐ (1์ดˆ๋กœ ๋‹จ์ถ•)
431
+ time.sleep(1)
 
 
 
432
 
433
+ # ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜ (ํ•„์š”ํ•œ ์˜์—ญ๋งŒ)
434
+ screenshot = self.driver.get_screenshot_as_png()
435
+ screenshot_b64 = base64.b64encode(screenshot).decode()
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
+ success_msg = f"์„ค์ • ์™„๋ฃŒ: {', '.join(changes_made) if changes_made else '๊ธฐ๋ณธ ์„ค์ • ์‚ฌ์šฉ'}"
438
 
439
+ return screenshot_b64, True, success_msg + ("\n" + error_msg if error_msg else "")
 
440
 
441
  except Exception as e:
442
+ error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
443
+ return None, False, error_msg
444
+
445
+ finally:
446
+ if self.driver:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  self.driver.quit()
 
 
 
 
 
 
 
 
 
 
 
448
 
449
+ # Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„
450
  def analyze_with_claude(screenshot_b64: str, region: str, period: str, category: str) -> str:
 
451
  try:
452
+ # API ํ‚ค ์ฒดํฌ
453
  if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
454
+ return """
455
+ <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
456
+ <h3>API ํ‚ค ์˜ค๋ฅ˜</h3>
457
+ <p><strong>ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!</strong></p>
458
+ </div>
459
+ """
460
 
461
+ # Claude ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
462
  claude_client = create_claude_client()
463
 
464
  if claude_client is None:
465
+ return """
466
+ <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
467
+ <h3>Claude API ์—ฐ๊ฒฐ ์‹คํŒจ</h3>
468
+ <p>API ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.</p>
469
+ </div>
470
+ """
471
 
472
+ # ๊ฐ„๊ฒฐํ•œ ํ”„๋กฌํ”„ํŠธ (์†๏ฟฝ๏ฟฝ๏ฟฝ ์ตœ์ ํ™”)
473
  prompt = f"""
474
  ์ด Google Trends ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
475
  ์„ค์ •: {region} | {period} | {category}
 
479
  2. ์ฃผ์š” ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ 3-5๊ฐœ์˜ ํŠน์ง• ์„ค๋ช…
480
  3. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํŠน์ด์‚ฌํ•ญ (์žˆ๋Š” ๊ฒฝ์šฐ)
481
 
482
+ HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์ด๋ชจ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์‹œ๊ฐ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
483
  ํ•œ๊ธ€ ํ…์ŠคํŠธ๋ฅผ ์ •ํ™•ํžˆ ์ฝ์–ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
484
  """
485
 
486
+ # Claude API ํ˜ธ์ถœ (๋น ๋ฅธ ์„ค์ •)
487
  message = claude_client.messages.create(
488
  model="claude-3-5-sonnet-20241022",
489
+ max_tokens=1000, # ํ† ํฐ ์ˆ˜ ์ค„์—ฌ์„œ ์†๋„ ํ–ฅ์ƒ
490
  messages=[
491
  {
492
  "role": "user",
 
511
  return message.content[0].text
512
 
513
  except Exception as e:
514
+ return f"""
515
+ <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
516
+ <h3>Claude API ๋ถ„์„ ์‹คํŒจ</h3>
517
+ <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
 
521
+ def main_analysis(region: str, period: str, category: str):
522
+ """์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ถ„์„ ํ•จ์ˆ˜ (Generator)"""
523
 
524
  start_time = time.time()
525
 
526
+ # 1๋‹จ๊ณ„: ์‹œ์ž‘ ๋ฉ”์‹œ์ง€
527
  yield f"""
528
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
529
+ <h3>Google Trends ๋ถ„์„ ์‹œ์ž‘!</h3>
530
  <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
531
+ <p>์‹ค์‹œ๊ฐ„์œผ๋กœ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค...</p>
532
  </div>
533
  """
534
 
535
+ progress_status = {"current": ""}
536
+
537
+ def progress_callback(message):
538
+ progress_status["current"] = message
539
+
540
  try:
541
+ # 2๋‹จ๊ณ„: API ํ‚ค ํ™•์ธ
542
+ if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
543
+ yield """
544
+ <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
545
+ <h3> API ํ‚ค ๋ˆ„๋ฝ</h3>
546
+ <p><strong>ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!</strong></p>
547
+ <p><strong>ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:</strong> .env ํŒŒ์ผ์— API ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ณ  ์•ฑ์„ ์žฌ์‹œ์ž‘ํ•˜์„ธ์š”.</p>
548
+ </div>
549
+ """
550
+ return
551
+
552
+ # 3๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰ ์•Œ๋ฆผ
553
  yield f"""
554
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
555
+ <h4> 1๋‹จ๊ณ„: ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค! ์ž…๋ ฅํ•˜์‹  ์กฐ๊ฑด์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค...</h4>
556
+ <p>Connect Start...</p>
557
+ </div>
558
+
559
+ <div style="padding: 15px; background-color: #f8f9fa; border-radius: 5px; margin-bottom: 15px;">
560
+ <p> <strong>์ง„ํ–‰ ์ค‘:</strong> ๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ๋ฐ ํŽ˜์ด์ง€ ๋กœ๋”ฉ...</p>
561
  </div>
562
  """
563
 
564
+ # 4๋‹จ๊ณ„: Google Trends ์ž๋™ํ™” ์‹คํ–‰
565
+ print(f" ๋ถ„์„ ์‹œ์ž‘: {region} | {period} | {category}")
566
+ automator = GoogleTrendsAutomator()
567
 
568
+ # 5๋‹จ๊ณ„: ์„ค์ • ๋ณ€๊ฒฝ ์•Œ๋ฆผ
569
+ if region != "๋Œ€ํ•œ๋ฏผ๊ตญ" or period != "์ง€๋‚œ 24์‹œ๊ฐ„" or category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
570
  yield f"""
571
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
572
+ <h4>์ž๋™ํ™” ์‹œ์ž‘</h4>
573
+ <p>In Running...</p>
 
 
 
 
 
 
 
 
 
 
 
 
574
  </div>
575
 
576
+ <div style="padding: 15px; background-color: #e3f2fd; border-radius: 5px; margin-bottom: 15px;">
577
+ <p><strong>์„ค์ • ๋ณ€๊ฒฝ ์ค‘:</strong></p>
578
+ <ul style="margin: 10px 0;">
579
+ {"<li>์ง€์—ญ: " + region + "</li>" if region != "๋Œ€ํ•œ๋ฏผ๊ตญ" else ""}
580
+ {"<li>๊ธฐ๊ฐ„: " + period + "</li>" if period != "์ง€๋‚œ 24์‹œ๊ฐ„" else ""}
581
+ {"<li>์นดํ…Œ๊ณ ๋ฆฌ: " + category + "</li>" if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ" else ""}
582
+ </ul>
583
  </div>
584
  """
585
 
586
+ # ๋ธŒ๋ผ์šฐ์ € ์ž‘์—…์„ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
587
  with ThreadPoolExecutor(max_workers=1) as executor:
588
  future = executor.submit(
589
+ automator.capture_trends,
590
+ region, period, category, progress_callback
591
  )
592
 
593
+ # ์ง„ํ–‰ ์ƒํ™ฉ ์—…๋ฐ์ดํŠธ
594
  while not future.done():
595
+ current_progress = progress_status["current"]
596
+ if current_progress:
597
+ elapsed = time.time() - start_time
598
+ yield f"""
599
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
600
+ <h4>์ž๋™ํ™” ์‹œ์ž‘</h4>
601
+ <p>In Running...</p>
602
+ </div>
603
+
604
+ <div style="padding: 15px; background-color: #f8f9fa; border-radius: 5px; margin-bottom: 15px;">
605
+ <p><strong>์ง„ํ–‰ ์ค‘:</strong> {current_progress}</p>
606
+ <p><strong>๊ฒฝ๊ณผ ์‹œ๊ฐ„:</strong> {elapsed:.1f}์ดˆ</p>
607
+ </div>
608
+ """
609
  time.sleep(0.5)
610
 
611
+ # ๊ฒฐ๊ณผ ๋ฐ›๊ธฐ
612
  screenshot_b64, success, status_msg = future.result()
613
 
614
+ browser_time = time.time() - start_time
615
+
616
+ if not success:
617
+ yield f"""
618
+ <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
619
+ <h3>Google Trends ์ ‘์† ์‹คํŒจ</h3>
620
+ <pre style="background-color: #fff5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
621
+ {status_msg}
622
+ </pre>
623
+ <p><strong>ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:</strong> Chrome ๋ธŒ๋ผ์šฐ์ €์™€ ChromeDriver๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</p>
624
+ </div>
625
+ """
626
+ return
627
 
628
+ # 6๋‹จ๊ณ„: ์Šคํฌ๋ฆฐ์ƒท ์™„๋ฃŒ ๋ฐ AI ๋ถ„์„ ์‹œ์ž‘
629
  yield f"""
630
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
631
+ <h4>2๋‹จ๊ณ„: ์ž…๋ ฅํ•˜์‹  ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ–ˆ๊ณ , ์กฐ๊ฑด์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค...</h4>
632
+ <p>Google Trends ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์บก์ฒ˜ํ–ˆ์Šต๋‹ˆ๋‹ค.</p>
633
  </div>
634
 
635
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
636
+ <h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ง„ํ–‰ ์ค‘...</h4>
637
+ <p>Claude AI๊ฐ€ ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.</p>
638
+ <p style="font-size: 14px; opacity: 0.9;">์•ฝ 10-20์ดˆ ์†Œ์š” ์˜ˆ์ƒ</p>
639
  </div>
640
  """
641
 
642
+ # 7๋‹จ๊ณ„: Claude API ๋ถ„์„
643
  ai_start_time = time.time()
644
+ print("์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๊ณ , Claude ๋ถ„์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค...")
645
 
646
+ # AI ๋ถ„์„์„ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
647
  with ThreadPoolExecutor(max_workers=1) as executor:
648
  ai_future = executor.submit(
649
  analyze_with_claude,
650
  screenshot_b64, region, period, category
651
  )
652
 
653
+ # AI ๋ถ„์„ ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
654
  while not ai_future.done():
655
  ai_elapsed = time.time() - ai_start_time
656
  total_elapsed = time.time() - start_time
657
  yield f"""
658
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
659
+ <h4>๐Ÿ“ธ 2๋‹จ๊ณ„: ์ž…๋ ฅํ•˜์‹  ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ–ˆ๊ณ , ์กฐ๊ฑด์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค...</h4>
660
+ <p>Google Trends ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์บก์ฒ˜ํ–ˆ์Šต๋‹ˆ๋‹ค.</p>
661
  </div>
662
 
663
+ <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
664
+ <h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ง„ํ–‰ ์ค‘...</h4>
665
+ <p>Claude AI๊ฐ€ ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ({ai_elapsed:.1f}์ดˆ)</p>
666
+ <p style="font-size: 14px; opacity: 0.9;">์ด ๊ฒฝ๊ณผ ์‹œ๊ฐ„: {total_elapsed:.1f}์ดˆ</p>
667
  </div>
668
  """
669
  time.sleep(0.5)
670
 
671
  analysis_result = ai_future.result()
672
 
673
+ ai_time = time.time() - ai_start_time
674
  total_time = time.time() - start_time
675
 
676
+ # 8๋‹จ๊ณ„: ์ตœ์ข… ๊ฒฐ๊ณผ ์ถœ๋ ฅ
677
  yield f"""
678
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
679
+ <h3> ๋ถ„์„ ์™„๋ฃŒ!</h3>
680
+ <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
681
+ <p>๋ชจ๋“  ๋‹จ๊ณ„๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.</p>
682
  </div>
683
 
684
  {analysis_result}
685
 
686
  <div style="margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center;">
687
  <p style="margin: 0; font-size: 14px;">
688
+ <strong>๋‹ค๋ฅธ ์„ค์ •์œผ๋กœ ๋‹ค์‹œ ๋ถ„์„ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?</strong><br>
689
+ ์œ„์˜ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‹ค์‹œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!
690
  </p>
691
  </div>
692
 
693
  <div style="margin-top: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 5px; font-size: 12px; color: #666; text-align: center;">
694
+ <p style="margin: 5px 0;"> <strong>๋ถ„์„ ์™„๋ฃŒ ์‹œ๊ฐ„:</strong> ์•ฝ 15-30์ดˆ</p>
695
+ <p style="margin: 5px 0;"> <strong>AI ์—”์ง„:</strong> Claude 3.5 Sonnet</p>
696
+ <p style="margin: 5px 0;"> <strong>๋ฐ์ดํ„ฐ ์ถœ์ฒ˜:</strong> Google Trends ์‹ค์‹œ๊ฐ„ ์Šคํฌ๋ฆฐ์ƒท</p>
697
  </div>
698
  """
699
 
700
+ print("์ „์ฒด ๋ถ„์„ ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ!")
701
 
702
  except Exception as e:
703
  error_details = traceback.format_exc()
704
+ print(f"๋ฉ”์ธ ๋ถ„์„ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
705
 
706
  yield f"""
707
  <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
708
+ <h3>์˜ค๋ฅ˜ ๋ฐœ์ƒ</h3>
709
  <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
710
  <details>
711
+ <summary>์ƒ์„ธ ์˜ค๋ฅ˜ ์ •๋ณด (ํด๋ฆญํ•˜์—ฌ ํŽผ์น˜๊ธฐ)</summary>
712
  <pre style="background-color: #f5f5f5; padding: 10px; margin-top: 10px; overflow-x: auto; font-size: 11px;">
713
  {error_details}
714
  </pre>
 
717
  </div>
718
  """
719
 
720
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค (์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ ํ˜•ํƒœ๋กœ)
721
+ def create_interface():
722
 
723
  with gr.Blocks(
724
  theme=gr.themes.Soft(),
725
+ title="Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ",
726
  css="""
727
  .gradio-container {
728
  max-width: 1200px !important;
 
733
  """
734
  ) as interface:
735
 
736
+ # Header
737
  gr.HTML("""
738
  <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
739
+ <h1>Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ</h1>
740
+ <p>Google Trends์˜ ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด๋ฅผ AI๊ฐ€ ์ž๋™์œผ๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค</p>
741
+ <p style="font-size: 14px; opacity: 0.9;">๊ฐœ์„ ๋œ ๋ฐฉ์‹์œผ๋กœ ๋‹จ๊ณ„๋ณ„ ์ง„ํ–‰์ƒํ™ฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</p>
742
  </div>
743
  """)
744
 
745
+ # ์ž…๋ ฅ ์„น์…˜
746
  with gr.Row():
747
  with gr.Column(scale=1):
748
  region_input = gr.Dropdown(
749
  choices=["๋Œ€ํ•œ๋ฏผ๊ตญ", "์ „์„ธ๊ณ„", "๋ฏธ๊ตญ", "์ผ๋ณธ", "์ค‘๊ตญ"],
750
  value="๋Œ€ํ•œ๋ฏผ๊ตญ",
751
+ label="์ง€์—ญ ์„ ํƒ",
752
+ info="๋ถ„์„ํ•  ์ง€์—ญ์„ ์„ ํƒํ•˜์„ธ์š”"
753
  )
754
 
755
  with gr.Column(scale=1):
756
  period_input = gr.Dropdown(
757
  choices=["์ง€๋‚œ 24์‹œ๊ฐ„", "์ง€๋‚œ 1์‹œ๊ฐ„", "์ง€๋‚œ 4์‹œ๊ฐ„", "์ง€๋‚œ 1์ผ", "์ง€๋‚œ 7์ผ"],
758
  value="์ง€๋‚œ 7์ผ",
759
+ label="๊ธฐ๊ฐ„ ์„ ํƒ",
760
+ info="ํŠธ๋ Œ๋“œ ๋ถ„์„ ๊ธฐ๊ฐ„์„ ์„ ํƒํ•˜์„ธ์š”"
761
  )
762
 
763
  with gr.Column(scale=1):
764
  category_input = gr.Dropdown(
765
  choices=["๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ", "๊ฒŒ์ž„", "๊ฑด๊ฐ•", "๊ธฐ์ˆ ", "์Šคํฌ์ธ ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "๋‰ด์Šค", "๋น„์ฆˆ๋‹ˆ์Šค"],
766
  value="๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ",
767
+ label="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ",
768
+ info="๊ด€์‹ฌ ๋ถ„์•ผ๋ฅผ ์„ ํƒํ•˜์„ธ์š”"
769
  )
770
 
771
+ # ๋ถ„์„ ๋ฒ„ํŠผ
772
  analyze_btn = gr.Button(
773
+ "์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ ์‹œ์ž‘ (๋น ๋ฅธ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ชจ๋“œ)",
774
  variant="primary",
775
  size="lg"
776
  )
777
 
778
+ # ์ง„ํ–‰ ์ƒํ™ฉ ๋ฐ ๊ฒฐ๊ณผ ์ถœ๋ ฅ
779
  output = gr.HTML(
780
  label="์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ฒฐ๊ณผ",
781
  value="""
782
  <div style="text-align: center; padding: 20px; border: 2px dashed #ccc; border-radius: 10px; color: #666;">
783
+ <h3>๋ถ„์„ ๋Œ€๊ธฐ ์ค‘</h3>
784
+ <p>์œ„์˜ ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  '์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ ์‹œ์ž‘' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!</p>
785
+ <p style="font-size: 14px;">๊ฐœ์„ ๋œ ๋ฐฉ์‹์œผ๋กœ <strong>15-30์ดˆ</strong>๋งŒ์— ๋ถ„์„์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.</p>
 
786
  </div>
787
  """
788
  )
789
 
790
+ # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ (Generator ํ•จ์ˆ˜๋กœ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ)
791
  analyze_btn.click(
792
+ fn=main_analysis,
793
  inputs=[region_input, period_input, category_input],
794
  outputs=output,
795
+ show_progress="hidden" # ๋‚ด์žฅ ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ ์ˆจ๊น€ (์ž์ฒด ์ŠคํŠธ๋ฆฌ๋ฐ ์‚ฌ์šฉ)
796
  )
797
 
798
+ # ์‚ฌ์šฉ๋ฒ• ์•ˆ๋‚ด
799
  gr.HTML("""
800
  <div style="margin-top: 30px; padding: 20px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px;">
801
+ <h3 style="margin-top: 0; color: #333;"> ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ!</h3>
802
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: #555;">
803
  <div>
804
+ <h4> ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ</h4>
805
  <ul style="margin: 0; padding-left: 20px;">
806
+ <li>๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ</li>
807
+ <li>์„ค์ • ๋ณ€๊ฒฝ ๊ณผ์ • ๋‹จ๊ณ„๋ณ„ ํ™•์ธ</li>
808
+ <li>AI ๋ถ„์„ ์ง„ํ–‰์ƒํ™ฉ ํ‘œ์‹œ</li>
809
  </ul>
810
  </div>
811
  <div>
812
+ <h4> ์†๋„ ์ตœ์ ํ™”</h4>
813
  <ul style="margin: 0; padding-left: 20px;">
814
+ <li>๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ• </li>
815
+ <li>๋ถˆํ•„์š”ํ•œ ๋กœ๋”ฉ ์‹œ๊ฐ„ ์ œ๊ฑฐ</li>
816
+ <li>ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ</li>
817
  </ul>
818
  </div>
819
  </div>
820
  </div>
821
  """)
822
+
823
+ # footer
824
+ gr.HTML("""
825
+ <div style="text-align: center; margin-top: 20px; padding: 15px; color: #666; border-top: 1px solid #eee;">
826
+ <p> <strong>์‚ฌ์šฉ๋ฒ•:</strong> ์›ํ•˜๋Š” ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  ๋ถ„์„ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”</p>
827
+ <p> <strong>์†๋„:</strong> ์ ์ฐจ์ ์œผ๋กœ ๊ฐœ์„  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.</p>
828
+ <p> <strong>Powered by:</strong> Google Trends + Claude AI + ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ</p>
829
+ </div>
830
+ """)
831
 
832
  return interface
833
 
834
+ # ๋ฉ”์ธ ์‹คํ–‰
 
 
 
 
 
 
 
 
 
 
 
835
  if __name__ == "__main__":
836
  print("=" * 50)
837
+ print("Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ ์‹œ์ž‘!")
838
  print("=" * 50)
839
 
840
+ # API ํ‚ค ํ™•์ธ
841
  if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
842
+ print(" ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ")
843
+ print("\n ์ด ์ƒํƒœ๋กœ๋„ ์•ฑ์€ ์‹คํ–‰๋˜์ง€๋งŒ, ๋ถ„์„ ๊ธฐ๋Šฅ์€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค.")
844
+ print("=" * 50)
845
  else:
846
+ print(" Claude API ํ‚ค ํ™•์ธ๋จ")
847
 
848
+ # ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ ๋ฐ ์‹คํ–‰
849
+ print(" Gradio ์›น ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
850
+ app = create_interface()
851
 
852
+ # ํ™˜๊ฒฝ๋ณ„ ์‹คํ–‰ ์„ค์ •
853
  if os.getenv("SPACE_ID"):
854
+ # Hugging Face Space ํ™˜๊ฒฝ
855
+ print(" Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์ค‘...")
856
  app.launch(
857
  server_name="0.0.0.0",
858
  server_port=7860,
 
860
  show_api=False
861
  )
862
  else:
863
+ # ๋กœ์ปฌ ํ™˜๊ฒฝ
864
+ print(" ๋กœ์ปฌ ์„œ๋ฒ„ ์‹œ์ž‘ ์ค‘...")
865
+ print(" ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:7860 ์œผ๋กœ ์ ‘์†")
866
+ print(" ์ข…๋ฃŒํ•˜๋ ค๋ฉด Ctrl+C")
867
+ print("=" * 50)
868
+
869
+ try:
870
+ app.launch(
871
+ server_name="127.0.0.1",
872
+ server_port=7860,
873
+ show_error=True,
874
+ show_api=False,
875
+ share=False, # ๋กœ์ปฌ์—์„œ๋Š” ๊ณต๊ฐœ ๋งํฌ ๋น„ํ™œ์„ฑํ™”
876
+ inbrowser=True, # ์ž๋™์œผ๋กœ ๋ธŒ๋ผ์šฐ์ € ์—ด๊ธฐ
877
+ quiet=False # ์ƒ์„ธ ๋กœ๊ทธ ํ‘œ์‹œ
878
+ )
879
+ except KeyboardInterrupt:
880
+ print("\n\nGoogle Trends ๋ถ„์„๊ธฐ๊ฐ€ ์ข…๋ฃŒ")
881
+ print("๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ")
882
+ except Exception as e:
883
+ print(f"\n ์„œ๋ฒ„ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
884
+ print("ํฌํŠธ 7860์ด ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ์ง€ ํ™•์ธ")