HanJun commited on
Commit
349ae2c
ยท
verified ยท
1 Parent(s): 0d409f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +294 -742
app.py CHANGED
@@ -1,449 +1,197 @@
1
  import os
2
  import time
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
- # .env ํŒŒ์ผ ๋กœ๋“œ (์žˆ๋Š” ๊ฒฝ์šฐ)
19
- try:
20
- from dotenv import load_dotenv
21
- load_dotenv()
22
- print(".env ํŒŒ์ผ ๋กœ๋“œ ์™„๋ฃŒ")
23
- except ImportError:
24
- print("python-dotenv๊ฐ€ ์„ค์น˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ˆ˜๋™์œผ๋กœ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.")
25
- except Exception as e:
26
- print(f".env ํŒŒ์ผ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {e}")
27
 
28
- # Claude API ์„ค์ •
29
- ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
30
-
31
- def create_claude_client():
32
- """Claude ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜ (๋น ๋ฅธ ๋ฒ„์ „)"""
33
- try:
34
- client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
35
- print("Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์„ฑ๊ณต")
36
- return client
37
- except Exception as e:
38
- print(f"Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์‹คํŒจ: {e}")
39
- return None
40
 
41
- class GoogleTrendsAutomator:
42
- """์ตœ์ ํ™”๋œ Google Trends ์ž๋™ํ™” ํด๋ž˜์Šค"""
 
43
 
44
- def __init__(self):
45
- self.driver = None
46
-
47
- def setup_driver(self):
48
- """์ดˆ๊ณ ์† Chrome ๋“œ๋ผ์ด๋ฒ„ ์„ค์ •"""
49
- options = Options()
50
-
51
- # ํ™˜๊ฒฝ๋ณ„ ์„ค์ •
52
- if os.getenv("SPACE_ID"): # Hugging Face Space
53
- options.add_argument("--headless")
54
- options.add_argument("--no-sandbox")
55
- options.add_argument("--disable-dev-shm-usage")
56
- options.add_argument("--disable-gpu")
57
- else: # ๋กœ์ปฌ ํ™˜๊ฒฝ
58
- options.add_argument("--headless")
59
-
60
- # ์†๋„ ์ตœ์ ํ™” ์„ค์ •
61
- options.add_argument("--window-size=1280,720") # ํ•ด์ƒ๋„ ์ค„์ž„
62
- options.add_argument("--disable-blink-features=AutomationControlled")
63
- options.add_argument("--disable-extensions")
64
- options.add_argument("--disable-web-security")
65
- options.add_argument("--allow-running-insecure-content")
66
- options.add_argument("--disable-background-timer-throttling")
67
- options.add_argument("--disable-backgrounding-occluded-windows")
68
- options.add_argument("--disable-renderer-backgrounding")
69
- options.add_argument("--disable-features=TranslateUI")
70
- options.add_argument("--disable-ipc-flooding-protection")
71
-
72
- # ๋„คํŠธ์›Œํฌ ์ตœ์ ํ™”
73
- options.add_argument("--aggressive-cache-discard")
74
- options.add_argument("--disable-background-networking")
75
-
76
- # ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”
77
- options.add_argument("--memory-pressure-off")
78
- options.add_argument("--max_old_space_size=4096")
79
-
80
- # ์ด๋ฏธ์ง€/CSS ๋น„ํ™œ์„ฑํ™”๋กœ ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ
81
- prefs = {
82
- "profile.managed_default_content_settings.images": 2, # ์ด๋ฏธ์ง€ ์ฐจ๋‹จ
83
- "profile.default_content_setting_values.notifications": 2, # ์•Œ๋ฆผ ์ฐจ๋‹จ
84
- }
85
- options.add_experimental_option("prefs", prefs)
86
 
87
- # ๋ด‡ ๊ฐ์ง€ ํšŒํ”ผ
88
- 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")
89
- options.add_experimental_option("excludeSwitches", ["enable-automation"])
90
- options.add_experimental_option('useAutomationExtension', False)
91
 
92
- # ์–ธ์–ด ์„ค์ •
93
- options.add_argument("--lang=ko")
94
- options.add_argument("--accept-lang=ko-KR,ko;q=0.9,en;q=0.8")
95
 
96
- try:
97
- # ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์œผ๋กœ ChromeDriver ์‹œ๋„
98
- driver_created = False
99
-
100
- # ๋ฐฉ๋ฒ• 1: webdriver-manager ์‚ฌ์šฉ
101
- try:
102
- from webdriver_manager.chrome import ChromeDriverManager
103
- from selenium.webdriver.chrome.service import Service
104
-
105
- service = Service(ChromeDriverManager().install())
106
- self.driver = webdriver.Chrome(service=service, options=options)
107
- print("webdriver-manager๋กœ ChromeDriver ์ž๋™ ์„ค์น˜ ์„ฑ๊ณต")
108
- driver_created = True
109
-
110
- except Exception as wm_error:
111
- print(f"webdriver-manager ์‹คํŒจ: {wm_error}")
112
-
113
- # ๋ฐฉ๋ฒ• 2: ์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ
114
- if not driver_created:
115
- try:
116
- self.driver = webdriver.Chrome(options=options)
117
- print("์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ ์„ฑ๊ณต")
118
- driver_created = True
119
- except Exception as sys_error:
120
- print(f"์‹œ์Šคํ…œ ChromeDriver ์‹คํŒจ: {sys_error}")
121
-
122
- if not driver_created:
123
- print("๋ชจ๋“  ChromeDriver ์ดˆ๊ธฐํ™” ๋ฐฉ๋ฒ• ์‹คํŒจ")
124
- return False
125
-
126
- # ํƒ€์ž„์•„์›ƒ ์„ค์ • (์†๋„ ์ตœ์ ํ™”)
127
- self.driver.set_page_load_timeout(15) # ํŽ˜์ด์ง€ ๋กœ๋”ฉ ํƒ€์ž„์•„์›ƒ 15์ดˆ
128
- self.driver.implicitly_wait(2) # ์š”์†Œ ๋Œ€๊ธฐ ์‹œ๊ฐ„ 2์ดˆ๋กœ ๋‹จ์ถ•
129
-
130
- # ๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ
131
- try:
132
- self.driver.execute_script("""
133
- Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
134
- Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
135
- Object.defineProperty(navigator, 'languages', {get: () => ['ko-KR', 'ko', 'en']});
136
- """)
137
- except Exception as script_error:
138
- print(f"๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ ์‹คํŒจ (๋ฌด์‹œ): {script_error}")
139
-
140
- return True
141
-
142
- except Exception as e:
143
- print(f"๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์ตœ์ข… ์‹คํŒจ: {e}")
144
- return False
145
-
146
- def safe_find_element(self, selectors: list, timeout: int = 3):
147
- """์ดˆ๊ณ ์† ์š”์†Œ ์ฐพ๊ธฐ (ํƒ€์ž„์•„์›ƒ ๋‹จ์ถ•)"""
148
- for selector_type, selector in selectors:
149
  try:
150
- if selector_type == "xpath":
151
- element = WebDriverWait(self.driver, timeout).until(
152
- EC.presence_of_element_located((By.XPATH, selector))
153
- )
154
- elif selector_type == "css":
155
- element = WebDriverWait(self.driver, timeout).until(
156
- EC.presence_of_element_located((By.CSS_SELECTOR, selector))
157
- )
158
- elif selector_type == "text":
159
- element = WebDriverWait(self.driver, timeout).until(
160
- EC.presence_of_element_located((By.XPATH, f"//*[contains(text(), '{selector}')]"))
161
- )
162
-
163
- if element:
164
- return element
165
  except:
166
- continue
167
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- def safe_click(self, selectors: list, timeout: int = 3) -> bool:
170
- """์ฆ‰์‹œ ํด๋ฆญ (๋Œ€๊ธฐ ์‹œ๊ฐ„ ์ตœ์†Œํ™”)"""
171
- element = self.safe_find_element(selectors, timeout)
172
- if element:
173
- try:
174
- # JavaScript ํด๋ฆญ ์šฐ์„  ์‹œ๋„ (๋” ๋น ๋ฆ„)
175
- self.driver.execute_script("arguments[0].click();", element)
176
- return True
177
- except:
178
- try:
179
- element.click()
180
- return True
181
- except:
182
- return False
183
  return False
184
-
185
- def parallel_change_settings(self, region: str, period: str, category: str, progress_callback=None) -> dict:
186
- """๋ณ‘๋ ฌ๋กœ ์„ค์ • ๋ณ€๊ฒฝ ์‹œ๋„ (์‹คํ—˜์ )"""
187
- results = {
188
- 'region': False,
189
- 'period': False,
190
- 'category': False,
191
- 'errors': []
192
- }
193
 
194
- # ์ˆœ์ฐจ์ ์œผ๋กœ ๋ณ€๊ฒฝ (๋ณ‘๋ ฌ์€ selenium์—์„œ ์•ˆ์ „ํ•˜์ง€ ์•Š์Œ)
195
- try:
196
- if region != "๋Œ€ํ•œ๋ฏผ๊ตญ":
197
- if progress_callback:
198
- progress_callback("์ง€์—ญ ์„ค์ • ๋ณ€๊ฒฝ ์ค‘...")
199
- results['region'] = self.change_region(region)
200
- time.sleep(0.5) # ์ตœ์†Œ ๋Œ€๊ธฐ
201
- else:
202
- results['region'] = True
 
203
 
204
- if period != "์ง€๋‚œ 24์‹œ๊ฐ„":
205
- if progress_callback:
206
- progress_callback("๊ธฐ๊ฐ„ ์„ค์ • ๋ณ€๊ฒฝ ์ค‘...")
207
- results['period'] = self.change_time_period(period)
208
- time.sleep(0.5) # ์ตœ์†Œ ๋Œ€๊ธฐ
209
- else:
210
- results['period'] = True
211
 
212
- if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
213
- if progress_callback:
214
- progress_callback("์นดํ…Œ๊ณ ๋ฆฌ ์„ค์ • ๋ณ€๊ฒฝ ์ค‘...")
215
- results['category'] = self.change_category(category)
216
- time.sleep(0.5) # ์ตœ์†Œ ๋Œ€๊ธฐ
217
- else:
218
- results['category'] = True
219
 
220
- except Exception as e:
221
- results['errors'].append(str(e))
222
-
223
- return results
224
-
225
- def change_region(self, target_region: str) -> bool:
226
- """์ดˆ๊ณ ์† ์ง€์—ญ ๋ณ€๊ฒฝ"""
227
- try:
228
- region_selectors = [
229
- ("xpath", "//button[@aria-label='๋Œ€ํ•œ๋ฏผ๊ตญ, ์œ„์น˜ ์„ ํƒ']"),
230
- ("xpath", "//button[contains(@aria-label, '์œ„์น˜ ์„ ํƒ')]"),
231
- ("xpath", "//span[contains(text(), '๋Œ€ํ•œ๋ฏผ๊ตญ')]//parent::button"),
232
- ]
233
-
234
- if not self.safe_click(region_selectors):
235
- return False
236
-
237
- time.sleep(1) # ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ•
238
-
239
- region_mapping = {
240
- "์ „์„ธ๊ณ„": ["์ „ ์„ธ๊ณ„", "Worldwide"],
241
- "๋ฏธ๊ตญ": ["๋ฏธ๊ตญ", "United States"],
242
- "์ผ๋ณธ": ["์ผ๋ณธ", "Japan"],
243
- "์ค‘๊ตญ": ["์ค‘๊ตญ", "China"]
244
- }
245
-
246
- if target_region in region_mapping:
247
- for region_text in region_mapping[target_region]:
248
- region_option_selectors = [
249
- ("xpath", f"//span[contains(text(), '{region_text}')]"),
250
- ("xpath", f"//*[contains(text(), '{region_text}')]"),
251
- ]
252
-
253
- if self.safe_click(region_option_selectors, timeout=2):
254
- return True
255
-
256
- return False
257
-
258
- except Exception as e:
259
- print(f"์ง€์—ญ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
260
- return False
261
-
262
- def change_time_period(self, target_period: str) -> bool:
263
- """์ดˆ๊ณ ์† ๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ"""
264
- try:
265
- period_selectors = [
266
- ("xpath", "//button[contains(@aria-label, '๊ธฐ๊ฐ„ ์„ ํƒ')]"),
267
- ("xpath", "//span[contains(text(), '์ง€๋‚œ 24์‹œ๊ฐ„')]//parent::button"),
268
- ]
269
-
270
- if not self.safe_click(period_selectors):
271
- return False
272
 
273
- time.sleep(1) # ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ•
274
-
275
- period_mapping = {
276
- "์ง€๋‚œ 1์‹œ๊ฐ„": ["์ง€๋‚œ 1์‹œ๊ฐ„"],
277
- "์ง€๋‚œ 4์‹œ๊ฐ„": ["์ง€๋‚œ 4์‹œ๊ฐ„"],
278
- "์ง€๋‚œ 1์ผ": ["์ง€๋‚œ 1์ผ"],
279
- "์ง€๋‚œ 7์ผ": ["์ง€๋‚œ 7์ผ", "์ง€๋‚œ ์ฃผ"]
280
- }
281
-
282
- if target_period in period_mapping:
283
- for period_text in period_mapping[target_period]:
284
- period_option_selectors = [
285
- ("xpath", f"//span[contains(text(), '{period_text}')]"),
286
- ("xpath", f"//*[contains(text(), '{period_text}')]"),
287
- ]
288
-
289
- if self.safe_click(period_option_selectors, timeout=2):
290
- return True
291
-
292
- return False
293
-
294
- except Exception as e:
295
- print(f"๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
296
- return False
297
-
298
- def change_category(self, target_category: str) -> bool:
299
- """์ดˆ๊ณ ์† ์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ"""
300
- try:
301
- category_selectors = [
302
- ("xpath", "//button[contains(@aria-label, '์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ')]"),
303
- ("xpath", "//span[contains(text(), '๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ')]//parent::button"),
304
- ]
305
-
306
- if not self.safe_click(category_selectors):
307
- return False
308
 
309
- time.sleep(1) # ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ•
310
-
311
- category_mapping = {
312
- "๊ฒŒ์ž„": ["๊ฒŒ์ž„"],
313
- "๊ฑด๊ฐ•": ["๊ฑด๊ฐ•"],
314
- "๊ธฐ์ˆ ": ["์ปดํ“จํ„ฐ ๋ฐ ์ „์ž์ œํ’ˆ", "๊ธฐ์ˆ "],
315
- "์Šคํฌ์ธ ": ["์Šคํฌ์ธ "],
316
- "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ": ["์˜ˆ์ˆ  ๋ฐ ์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ"],
317
- "๋‰ด์Šค": ["๋‰ด์Šค"],
318
- "๋น„์ฆˆ๋‹ˆ์Šค": ["๋น„์ฆˆ๋‹ˆ์Šค"]
319
- }
320
-
321
- if target_category in category_mapping:
322
- for category_text in category_mapping[target_category]:
323
- category_option_selectors = [
324
- ("xpath", f"//span[contains(text(), '{category_text}')]"),
325
- ("xpath", f"//*[contains(text(), '{category_text}')]"),
326
- ]
327
-
328
- if self.safe_click(category_option_selectors, timeout=2):
329
- return True
330
-
331
- return False
332
-
333
- except Exception as e:
334
- print(f"์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
335
- return False
336
 
337
- def capture_trends(self, region: str, period: str, category: str, progress_callback=None) -> Tuple[Optional[str], bool, str]:
338
- """Google Trends ์บก์ฒ˜ (์ตœ์ ํ™”๋œ ๋ฒ„์ „)"""
339
- error_msg = ""
340
 
341
- try:
342
- if progress_callback:
343
- progress_callback("๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์ดˆ๊ธฐํ™” ์ค‘...")
344
-
345
- # ๋“œ๋ผ์ด๋ฒ„ ์„ค์ •
346
- if not self.setup_driver():
347
- return None, False, "๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์‹คํŒจ"
348
-
349
- if progress_callback:
350
- progress_callback("Google Trends ํŽ˜์ด์ง€ ์ ‘์† ์ค‘...")
351
-
352
- # Google Trends ์ ‘์† (ํ•œ๊ตญ์–ด ์ง์ ‘ URL)
353
- self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
354
-
355
- # ์ตœ์†Œํ•œ์˜ ๋กœ๋”ฉ ๋Œ€๊ธฐ (1์ดˆ๋กœ ๋‹จ์ถ•)
356
- time.sleep(1)
357
-
358
- if progress_callback:
359
- progress_callback("ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ, ์„ค์ • ๋ณ€๊ฒฝ ์‹œ์ž‘...")
360
-
361
- # ํŽ˜์ด์ง€ ์ค€๋น„ ํ™•์ธ (๋น ๋ฅธ ์ฒดํฌ)
362
- try:
363
- WebDriverWait(self.driver, 5).until(
364
- EC.presence_of_element_located((By.TAG_NAME, "button"))
365
- )
366
- except TimeoutException:
367
- pass # ๊ณ„์† ์ง„ํ–‰
368
-
369
- # ์„ค์ • ๋ณ€๊ฒฝ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ)
370
- settings_result = self.parallel_change_settings(region, period, category, progress_callback)
371
-
372
- # ๊ฒฐ๊ณผ ํ™•์ธ
373
- changes_made = []
374
- if settings_result['region']:
375
- changes_made.append(f"์ง€์—ญ: {region}")
376
- if settings_result['period']:
377
- changes_made.append(f"๊ธฐ๊ฐ„: {period}")
378
- if settings_result['category']:
379
- changes_made.append(f"์นดํ…Œ๊ณ ๋ฆฌ: {category}")
380
-
381
- if settings_result['errors']:
382
- error_msg = "\n".join(settings_result['errors'])
383
-
384
- if progress_callback:
385
- progress_callback("์„ค์ • ๋ณ€๊ฒฝ ์™„๋ฃŒ, ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๋Š”์ค‘...")
386
-
387
- # ์ตœ์ข… ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋Œ€๊ธฐ (1์ดˆ๋กœ ๋‹จ์ถ•)
388
- time.sleep(1)
389
-
390
- # ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜ (ํ•„์š”ํ•œ ์˜์—ญ๋งŒ)
391
- screenshot = self.driver.get_screenshot_as_png()
392
- screenshot_b64 = base64.b64encode(screenshot).decode()
393
-
394
- success_msg = f"์„ค์ • ์™„๋ฃŒ: {', '.join(changes_made) if changes_made else '๊ธฐ๋ณธ ์„ค์ • ์‚ฌ์šฉ'}"
395
 
396
- return screenshot_b64, True, success_msg + ("\n" + error_msg if error_msg else "")
 
 
397
 
398
- except Exception as e:
399
- error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
400
- return None, False, error_msg
401
 
402
- finally:
403
- if self.driver:
404
- self.driver.quit()
405
 
406
- def analyze_with_claude(screenshot_b64: str, region: str, period: str, category: str) -> str:
407
- """Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„ (์ตœ์ ํ™”๋œ ๋ฒ„์ „)"""
408
  try:
409
- # API ํ‚ค ์ฒดํฌ
410
- if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
411
- return """
412
- <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
413
- <h3>API ํ‚ค ์˜ค๋ฅ˜</h3>
414
- <p><strong>ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!</strong></p>
415
- </div>
416
- """
417
 
418
- # Claude ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
419
- claude_client = create_claude_client()
 
 
420
 
421
- if claude_client is None:
422
- return """
423
- <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
424
- <h3>Claude API ์—ฐ๊ฒฐ ์‹คํŒจ</h3>
425
- <p>API ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.</p>
426
- </div>
427
- """
428
 
429
- # ๊ฐ„๊ฒฐํ•œ ํ”„๋กฌํ”„ํŠธ (์†๋„ ์ตœ์ ํ™”)
430
- prompt = f"""
431
- ์ด Google Trends ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
432
- ์„ค์ •: {region} | {period} | {category}
433
-
434
- ๋‹ค์Œ์„ HTML ํ‘œ ํ˜•ํƒœ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ •๋ฆฌํ•ด์ฃผ์„ธ์š”:
435
- 1. ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ์ƒ์œ„ 10๊ฐœ (์ˆœ์œ„, ๊ฒ€์ƒ‰์–ด, ๊ฒ€์ƒ‰๋Ÿ‰, ์ƒ์Šน๋ฅ )
436
- 2. ์ฃผ์š” ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ 3-5๊ฐœ์˜ ํŠน์ง• ์„ค๋ช…
437
- 3. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํŠน์ด์‚ฌํ•ญ (์žˆ๋Š” ๊ฒฝ์šฐ)
438
-
439
- HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์ด๋ชจ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์‹œ๊ฐ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
440
- ํ•œ๊ธ€ ํ…์ŠคํŠธ๋ฅผ ์ •ํ™•ํžˆ ์ฝ์–ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
441
- """
442
-
443
- # Claude API ํ˜ธ์ถœ (๋น ๋ฅธ ์„ค์ •)
444
- message = claude_client.messages.create(
445
- model="claude-3-5-sonnet-20241022",
446
- max_tokens=1000, # ํ† ํฐ ์ˆ˜ ์ค„์—ฌ์„œ ์†๋„ ํ–ฅ์ƒ
447
  messages=[
448
  {
449
  "role": "user",
@@ -457,7 +205,7 @@ HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์ด๋ชจ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์‹œ
457
  "source": {
458
  "type": "base64",
459
  "media_type": "image/png",
460
- "data": screenshot_b64
461
  }
462
  }
463
  ]
@@ -465,377 +213,181 @@ HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์ด๋ชจ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์‹œ
465
  ]
466
  )
467
 
468
- return message.content[0].text
469
 
470
  except Exception as e:
471
- return f"""
472
- <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
473
- <h3>Claude API ๋ถ„์„ ์‹คํŒจ</h3>
474
- <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
475
- </div>
476
- """
477
 
478
- def main_analysis(region: str, period: str, category: str):
479
- """์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ถ„์„ ํ•จ์ˆ˜ (Generator)"""
480
-
481
- start_time = time.time()
482
-
483
- # 1๋‹จ๊ณ„: ์‹œ์ž‘ ๋ฉ”์‹œ์ง€
484
- yield f"""
485
- <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
486
- <h3>Google Trends ๋ถ„์„ ์‹œ์ž‘!</h3>
487
- <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
488
- <p>์‹ค์‹œ๊ฐ„์œผ๋กœ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค...</p>
489
- </div>
490
- """
491
-
492
- progress_status = {"current": ""}
493
 
494
- def progress_callback(message):
495
- progress_status["current"] = message
 
 
 
 
 
 
 
 
 
 
 
 
 
496
 
497
  try:
498
- # 2๋‹จ๊ณ„: API ํ‚ค ํ™•์ธ
499
- if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
500
- yield """
501
- <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
502
- <h3> API ํ‚ค ๋ˆ„๋ฝ</h3>
503
- <p><strong>ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!</strong></p>
504
- <p><strong>ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:</strong> .env ํŒŒ์ผ์— API ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ณ  ์•ฑ์„ ์žฌ์‹œ์ž‘ํ•˜์„ธ์š”.</p>
505
- </div>
506
- """
507
- return
508
-
509
- # 3๋‹จ๊ณ„: ๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰ ์•Œ๋ฆผ
510
- yield f"""
511
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
512
- <h4> 1๋‹จ๊ณ„: ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค! ์ž…๋ ฅํ•˜์‹  ์กฐ๊ฑด์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค...</h4>
513
- <p>Connect Start...</p>
514
- </div>
515
-
516
- <div style="padding: 15px; background-color: #f8f9fa; border-radius: 5px; margin-bottom: 15px;">
517
- <p> <strong>์ง„ํ–‰ ์ค‘:</strong> ๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ๋ฐ ํŽ˜์ด์ง€ ๋กœ๋”ฉ...</p>
518
- </div>
519
- """
520
-
521
- # 4๋‹จ๊ณ„: Google Trends ์ž๋™ํ™” ์‹คํ–‰
522
- print(f" ๋ถ„์„ ์‹œ์ž‘: {region} | {period} | {category}")
523
- automator = GoogleTrendsAutomator()
524
-
525
- # 5๋‹จ๊ณ„: ์„ค์ • ๋ณ€๊ฒฝ ์•Œ๋ฆผ
526
- if region != "๋Œ€ํ•œ๋ฏผ๊ตญ" or period != "์ง€๋‚œ 24์‹œ๊ฐ„" or category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
527
- yield f"""
528
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
529
- <h4>์ž๋™ํ™” ์‹œ์ž‘</h4>
530
- <p>In Running...</p>
531
- </div>
532
-
533
- <div style="padding: 15px; background-color: #e3f2fd; border-radius: 5px; margin-bottom: 15px;">
534
- <p><strong>์„ค์ • ๋ณ€๊ฒฝ ์ค‘:</strong></p>
535
- <ul style="margin: 10px 0;">
536
- {"<li>์ง€์—ญ: " + region + "</li>" if region != "๋Œ€ํ•œ๋ฏผ๊ตญ" else ""}
537
- {"<li>๊ธฐ๊ฐ„: " + period + "</li>" if period != "์ง€๋‚œ 24์‹œ๊ฐ„" else ""}
538
- {"<li>์นดํ…Œ๊ณ ๋ฆฌ: " + category + "</li>" if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ" else ""}
539
- </ul>
540
- </div>
541
- """
542
-
543
- # ๋ธŒ๋ผ์šฐ์ € ์ž‘์—…์„ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
544
- with ThreadPoolExecutor(max_workers=1) as executor:
545
- future = executor.submit(
546
- automator.capture_trends,
547
- region, period, category, progress_callback
548
- )
549
-
550
- # ์ง„ํ–‰ ์ƒํ™ฉ ์—…๋ฐ์ดํŠธ
551
- while not future.done():
552
- current_progress = progress_status["current"]
553
- if current_progress:
554
- elapsed = time.time() - start_time
555
- yield f"""
556
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
557
- <h4>์ž๋™ํ™” ์‹œ์ž‘</h4>
558
- <p>In Running...</p>
559
- </div>
560
-
561
- <div style="padding: 15px; background-color: #f8f9fa; border-radius: 5px; margin-bottom: 15px;">
562
- <p><strong>์ง„ํ–‰ ์ค‘:</strong> {current_progress}</p>
563
- <p><strong>๊ฒฝ๊ณผ ์‹œ๊ฐ„:</strong> {elapsed:.1f}์ดˆ</p>
564
- </div>
565
- """
566
- time.sleep(0.5)
567
-
568
- # ๊ฒฐ๊ณผ ๋ฐ›๊ธฐ
569
- screenshot_b64, success, status_msg = future.result()
570
-
571
- browser_time = time.time() - start_time
572
-
573
- if not success:
574
- yield f"""
575
- <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
576
- <h3>Google Trends ์ ‘์† ์‹คํŒจ</h3>
577
- <pre style="background-color: #fff5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
578
- {status_msg}
579
- </pre>
580
- <p><strong>ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:</strong> Chrome ๋ธŒ๋ผ์šฐ์ €์™€ ChromeDriver๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</p>
581
- </div>
582
- """
583
- return
584
 
585
- # 6๋‹จ๊ณ„: ์Šคํฌ๋ฆฐ์ƒท ์™„๋ฃŒ ๋ฐ AI ๋ถ„์„ ์‹œ์ž‘
586
- yield f"""
587
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
588
- <h4>2๋‹จ๊ณ„: ์ž…๋ ฅํ•˜์‹  ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ–ˆ๊ณ , ์กฐ๊ฑด์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค...</h4>
589
- <p>Google Trends ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์บก์ฒ˜ํ–ˆ์Šต๋‹ˆ๋‹ค.</p>
590
- </div>
591
 
592
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
593
- <h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ง„ํ–‰ ์ค‘...</h4>
594
- <p>Claude AI๊ฐ€ ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.</p>
595
- <p style="font-size: 14px; opacity: 0.9;">์•ฝ 10-20์ดˆ ์†Œ์š” ์˜ˆ์ƒ</p>
596
- </div>
597
- """
598
-
599
- # 7๋‹จ๊ณ„: Claude API ๋ถ„์„
600
- ai_start_time = time.time()
601
- print("์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๊ณ , Claude ๋ถ„์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค...")
602
-
603
- # AI ๋ถ„์„์„ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
604
- with ThreadPoolExecutor(max_workers=1) as executor:
605
- ai_future = executor.submit(
606
- analyze_with_claude,
607
- screenshot_b64, region, period, category
608
- )
609
-
610
- # AI ๋ถ„์„ ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
611
- while not ai_future.done():
612
- ai_elapsed = time.time() - ai_start_time
613
- total_elapsed = time.time() - start_time
614
- yield f"""
615
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
616
- <h4>๐Ÿ“ธ 2๋‹จ๊ณ„: ์ž…๋ ฅํ•˜์‹  ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ–ˆ๊ณ , ์กฐ๊ฑด์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค...</h4>
617
- <p>Google Trends ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์บก์ฒ˜ํ–ˆ์Šต๋‹ˆ๋‹ค.</p>
618
- </div>
619
-
620
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
621
- <h4>3๋‹จ๊ณ„: AI ๋ถ„์„ ์ง„ํ–‰ ์ค‘...</h4>
622
- <p>Claude AI๊ฐ€ ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ({ai_elapsed:.1f}์ดˆ)</p>
623
- <p style="font-size: 14px; opacity: 0.9;">์ด ๊ฒฝ๊ณผ ์‹œ๊ฐ„: {total_elapsed:.1f}์ดˆ</p>
624
- </div>
625
- """
626
- time.sleep(0.5)
627
-
628
- analysis_result = ai_future.result()
629
-
630
- ai_time = time.time() - ai_start_time
631
- total_time = time.time() - start_time
632
-
633
- # 8๋‹จ๊ณ„: ์ตœ์ข… ๊ฒฐ๊ณผ ์ถœ๋ ฅ
634
- yield f"""
635
- <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
636
- <h3> ๋ถ„์„ ์™„๋ฃŒ!</h3>
637
- <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
638
- <p>๋ชจ๋“  ๋‹จ๊ณ„๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.</p>
639
- </div>
640
 
641
- {analysis_result}
 
642
 
643
- <div style="margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center;">
644
- <p style="margin: 0; font-size: 14px;">
645
- <strong>๋‹ค๋ฅธ ์„ค์ •์œผ๋กœ ๋‹ค์‹œ ๋ถ„์„ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?</strong><br>
646
- ์œ„์˜ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‹ค์‹œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!
647
- </p>
648
- </div>
649
-
650
- <div style="margin-top: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 5px; font-size: 12px; color: #666; text-align: center;">
651
- <p style="margin: 5px 0;"> <strong>๋ถ„์„ ์™„๋ฃŒ ์‹œ๊ฐ„:</strong> ์•ฝ 15-30์ดˆ</p>
652
- <p style="margin: 5px 0;"> <strong>AI ์—”์ง„:</strong> Claude 3.5 Sonnet</p>
653
- <p style="margin: 5px 0;"> <strong>๋ฐ์ดํ„ฐ ์ถœ์ฒ˜:</strong> Google Trends ์‹ค์‹œ๊ฐ„ ์Šคํฌ๋ฆฐ์ƒท</p>
654
- </div>
655
- """
656
 
657
- print("์ „์ฒด ๋ถ„์„ ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ!")
658
 
659
  except Exception as e:
660
- error_details = traceback.format_exc()
661
- print(f"๋ฉ”์ธ ๋ถ„์„ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
662
-
663
- yield f"""
664
- <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
665
- <h3>์˜ค๋ฅ˜ ๋ฐœ์ƒ</h3>
666
- <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
667
- <details>
668
- <summary>์ƒ์„ธ ์˜ค๋ฅ˜ ์ •๋ณด (ํด๋ฆญํ•˜์—ฌ ํŽผ์น˜๊ธฐ)</summary>
669
- <pre style="background-color: #f5f5f5; padding: 10px; margin-top: 10px; overflow-x: auto; font-size: 11px;">
670
- {error_details}
671
- </pre>
672
- </details>
673
- <p><strong>ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:</strong> ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</p>
674
- </div>
675
- """
 
 
 
 
 
676
 
677
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค (์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ ํ˜•ํƒœ๋กœ)
678
  def create_interface():
 
679
 
680
- with gr.Blocks(
681
- theme=gr.themes.Soft(),
682
- title="Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ",
683
- css="""
684
- .gradio-container {
685
- max-width: 1200px !important;
686
- }
687
- .output-html {
688
- max-height: none !important;
689
- }
690
- """
691
- ) as interface:
692
 
693
- # Header
694
- gr.HTML("""
695
- <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
696
- <h1>Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ</h1>
697
- <p>Google Trends์˜ ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด๋ฅผ AI๊ฐ€ ์ž๋™์œผ๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค</p>
698
- <p style="font-size: 14px; opacity: 0.9;">๊ฐœ์„ ๋œ ๋ฐฉ์‹์œผ๋กœ ๋‹จ๊ณ„๋ณ„ ์ง„ํ–‰์ƒํ™ฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</p>
699
- </div>
700
- """)
701
-
702
- # ์ž…๋ ฅ ์„น์…˜
703
  with gr.Row():
704
  with gr.Column(scale=1):
705
- region_input = gr.Dropdown(
706
- choices=["๋Œ€ํ•œ๋ฏผ๊ตญ", "์ „์„ธ๊ณ„", "๋ฏธ๊ตญ", "์ผ๋ณธ", "์ค‘๊ตญ"],
707
- value="๋Œ€ํ•œ๋ฏผ๊ตญ",
708
- label="์ง€์—ญ ์„ ํƒ",
709
- info="๋ถ„์„ํ•  ์ง€์—ญ์„ ์„ ํƒํ•˜์„ธ์š”"
710
  )
711
 
712
- with gr.Column(scale=1):
713
- period_input = gr.Dropdown(
714
- choices=["์ง€๋‚œ 24์‹œ๊ฐ„", "์ง€๋‚œ 1์‹œ๊ฐ„", "์ง€๋‚œ 4์‹œ๊ฐ„", "์ง€๋‚œ 1์ผ", "์ง€๋‚œ 7์ผ"],
715
- value="์ง€๋‚œ 7์ผ",
716
- label="๊ธฐ๊ฐ„ ์„ ํƒ",
717
- info="ํŠธ๋ Œ๋“œ ๋ถ„์„ ๊ธฐ๊ฐ„์„ ์„ ํƒํ•˜์„ธ์š”"
718
  )
719
 
720
- with gr.Column(scale=1):
721
- category_input = gr.Dropdown(
722
- choices=["๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ", "๊ฒŒ์ž„", "๊ฑด๊ฐ•", "๊ธฐ์ˆ ", "์Šคํฌ์ธ ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "๋‰ด์Šค", "๋น„์ฆˆ๋‹ˆ์Šค"],
723
  value="๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ",
724
- label="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ",
725
- info="๊ด€์‹ฌ ๋ถ„์•ผ๋ฅผ ์„ ํƒํ•˜์„ธ์š”"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  )
 
727
 
728
- # ๋ถ„์„ ๋ฒ„ํŠผ
729
- analyze_btn = gr.Button(
730
- "์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ ์‹œ์ž‘ (๋น ๋ฅธ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ชจ๋“œ)",
731
- variant="primary",
732
- size="lg"
733
  )
734
 
735
- # ์ง„ํ–‰ ์ƒํ™ฉ ๋ฐ ๊ฒฐ๊ณผ ์ถœ๋ ฅ
736
- output = gr.HTML(
737
- label="์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ฒฐ๊ณผ",
738
- value="""
739
- <div style="text-align: center; padding: 20px; border: 2px dashed #ccc; border-radius: 10px; color: #666;">
740
- <h3>๋ถ„์„ ๋Œ€๊ธฐ ์ค‘</h3>
741
- <p>์œ„์˜ ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  '์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ ์‹œ์ž‘' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!</p>
742
- <p style="font-size: 14px;">๊ฐœ์„ ๋œ ๋ฐฉ์‹์œผ๋กœ <strong>15-30์ดˆ</strong>๋งŒ์— ๋ถ„์„์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.</p>
743
- </div>
744
- """
745
  )
746
 
747
- # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ (Generator ํ•จ์ˆ˜๋กœ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ)
748
- analyze_btn.click(
749
- fn=main_analysis,
750
- inputs=[region_input, period_input, category_input],
751
- outputs=output,
752
- show_progress="hidden" # ๋‚ด์žฅ ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ ์ˆจ๊น€ (์ž์ฒด ์ŠคํŠธ๋ฆฌ๋ฐ ์‚ฌ์šฉ)
753
  )
754
-
755
- # ์‚ฌ์šฉ๋ฒ• ์•ˆ๋‚ด
756
- gr.HTML("""
757
- <div style="margin-top: 30px; padding: 20px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px;">
758
- <h3 style="margin-top: 0; color: #333;"> ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ!</h3>
759
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: #555;">
760
- <div>
761
- <h4> ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ</h4>
762
- <ul style="margin: 0; padding-left: 20px;">
763
- <li>๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰ ์ƒํƒœ ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ</li>
764
- <li>์„ค์ • ๋ณ€๊ฒฝ ๊ณผ์ • ๋‹จ๊ณ„๋ณ„ ํ™•์ธ</li>
765
- <li>AI ๋ถ„์„ ์ง„ํ–‰์ƒํ™ฉ ํ‘œ์‹œ</li>
766
- </ul>
767
- </div>
768
- <div>
769
- <h4> ์†๋„ ์ตœ์ ํ™”</h4>
770
- <ul style="margin: 0; padding-left: 20px;">
771
- <li>๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋‹จ์ถ• </li>
772
- <li>๋ถˆํ•„์š”ํ•œ ๋กœ๋”ฉ ์‹œ๊ฐ„ ์ œ๊ฑฐ</li>
773
- <li>ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ</li>
774
- </ul>
775
- </div>
776
- </div>
777
- </div>
778
- """)
779
-
780
- # footer
781
- gr.HTML("""
782
- <div style="text-align: center; margin-top: 20px; padding: 15px; color: #666; border-top: 1px solid #eee;">
783
- <p> <strong>์‚ฌ์šฉ๋ฒ•:</strong> ์›ํ•˜๋Š” ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  ๋ถ„์„ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”</p>
784
- <p> <strong>์†๋„:</strong> ์ ์ฐจ์ ์œผ๋กœ ๊ฐœ์„  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.</p>
785
- <p> <strong>Powered by:</strong> Google Trends + Claude AI + ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ</p>
786
- </div>
787
- """)
788
 
789
  return interface
790
 
791
- # ๋ฉ”์ธ ์‹คํ–‰
792
- if __name__ == "__main__":
793
  print("=" * 50)
794
  print("Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ ์‹œ์ž‘!")
795
  print("=" * 50)
796
 
797
  # API ํ‚ค ํ™•์ธ
798
- if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
799
- print(" ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ")
800
- print("\n ์ด ์ƒํƒœ๋กœ๋„ ์•ฑ์€ ์‹คํ–‰๋˜์ง€๋งŒ, ๋ถ„์„ ๊ธฐ๋Šฅ์€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค.")
801
- print("=" * 50)
802
- else:
803
- print(" Claude API ํ‚ค ํ™•์ธ๋จ")
804
 
805
- # ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ ๋ฐ ์‹คํ–‰
806
- print(" Gradio ์›น ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
807
- app = create_interface()
808
 
809
- # ํ™˜๊ฒฝ๋ณ„ ์‹คํ–‰ ์„ค์ •
810
  if os.getenv("SPACE_ID"):
811
- # Hugging Face Space ํ™˜๊ฒฝ
812
- print(" Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์ค‘...")
813
- app.launch(
814
- server_name="0.0.0.0",
815
- server_port=7860,
816
- show_error=True,
817
- show_api=False
818
- )
819
- else:
820
- # ๋กœ์ปฌ ํ™˜๊ฒฝ
821
- print(" ๋กœ์ปฌ ์„œ๋ฒ„ ์‹œ์ž‘ ์ค‘...")
822
- print(" ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:7860 ์œผ๋กœ ์ ‘์†")
823
- print(" ์ข…๋ฃŒํ•˜๋ ค๋ฉด Ctrl+C")
824
- print("=" * 50)
825
-
826
- try:
827
- app.launch(
828
- server_name="127.0.0.1",
829
- server_port=7860,
830
- show_error=True,
831
- show_api=False,
832
- share=False, # ๋กœ์ปฌ์—์„œ๋Š” ๊ณต๊ฐœ ๋งํฌ ๋น„ํ™œ์„ฑํ™”
833
- inbrowser=True, # ์ž๋™์œผ๋กœ ๋ธŒ๋ผ์šฐ์ € ์—ด๊ธฐ
834
- quiet=False # ์ƒ์„ธ ๋กœ๊ทธ ํ‘œ์‹œ
835
- )
836
- except KeyboardInterrupt:
837
- print("\n\nGoogle Trends ๋ถ„์„๊ธฐ๊ฐ€ ์ข…๋ฃŒ")
838
- print("๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ")
839
- except Exception as e:
840
- print(f"\n ์„œ๋ฒ„ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
841
- print("ํฌํŠธ 7860์ด ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ์ง€ ํ™•์ธ")
 
1
  import os
2
  import time
3
  import base64
4
+ import io
5
  import traceback
6
+ import threading
7
+ import atexit
8
+ from datetime import datetime
9
+ from typing import Optional, Tuple
10
+
11
  import gradio as gr
12
  import anthropic
13
  from selenium import webdriver
14
  from selenium.webdriver.chrome.options import Options
15
+ from selenium.webdriver.chrome.service import Service
16
  from selenium.webdriver.common.by import By
17
+ from selenium.webdriver.support.ui import WebDriverWait, Select
18
  from selenium.webdriver.support import expected_conditions as EC
19
+ from selenium.common.exceptions import TimeoutException, WebDriverException
20
+ from webdriver_manager.chrome import ChromeDriverManager
21
+ from PIL import Image
22
+ import requests
23
 
24
+ # ์ „์—ญ ๋ณ€์ˆ˜
25
+ driver = None
26
+ driver_lock = threading.Lock()
27
+ is_browser_ready = False
 
 
 
 
 
28
 
29
+ # Claude API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
30
+ def get_claude_client():
31
+ api_key = os.getenv("ANTHROPIC_API_KEY")
32
+ if not api_key:
33
+ raise ValueError("ANTHROPIC_API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
34
+ return anthropic.Anthropic(api_key=api_key)
 
 
 
 
 
 
35
 
36
+ def init_browser():
37
+ """๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  Google Trends ํŽ˜์ด์ง€๋กœ ์ด๋™"""
38
+ global driver, is_browser_ready
39
 
40
+ try:
41
+ print("Chrome ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘...")
42
+
43
+ # Chrome ์˜ต์…˜ ์„ค์ • (Hugging Face Space ์ตœ์ ํ™”)
44
+ chrome_options = Options()
45
+ chrome_options.add_argument("--headless")
46
+ chrome_options.add_argument("--no-sandbox")
47
+ chrome_options.add_argument("--disable-dev-shm-usage")
48
+ chrome_options.add_argument("--disable-gpu")
49
+ chrome_options.add_argument("--disable-extensions")
50
+ chrome_options.add_argument("--disable-web-security")
51
+ chrome_options.add_argument("--allow-running-insecure-content")
52
+ chrome_options.add_argument("--disable-blink-features=AutomationControlled")
53
+ chrome_options.add_argument("--disable-features=VizDisplayCompositor")
54
+ chrome_options.add_argument("--window-size=1920,1080")
55
+ chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
56
+
57
+ # ChromeDriver ์„œ๋น„์Šค ์„ค์ •
58
+ service = Service(ChromeDriverManager().install())
59
+
60
+ # ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
61
+ driver = webdriver.Chrome(service=service, options=chrome_options)
62
+ driver.set_page_load_timeout(30)
63
+ driver.implicitly_wait(10)
64
+
65
+ print("Google Trends ํŽ˜์ด์ง€๋กœ ์ด๋™ ์ค‘...")
66
+ driver.get("https://trends.google.com/trending?geo=KR")
67
+
68
+ # ํŽ˜์ด์ง€ ๋กœ๋”ฉ ๋Œ€๊ธฐ
69
+ WebDriverWait(driver, 20).until(
70
+ EC.presence_of_element_located((By.TAG_NAME, "body"))
71
+ )
 
 
 
 
 
 
 
 
 
 
72
 
73
+ # ์ถ”๊ฐ€ ๋กœ๋”ฉ ์‹œ๊ฐ„
74
+ time.sleep(3)
 
 
75
 
76
+ is_browser_ready = True
77
+ print("๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!")
 
78
 
79
+ except Exception as e:
80
+ print(f"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
81
+ print(traceback.format_exc())
82
+ is_browser_ready = False
83
+ if driver:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  try:
85
+ driver.quit()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  except:
87
+ pass
88
+ driver = None
89
+
90
+ def cleanup_browser():
91
+ """๋ธŒ๋ผ์šฐ๏ฟฝ๏ฟฝ๏ฟฝ ์ •๋ฆฌ"""
92
+ global driver
93
+ if driver:
94
+ try:
95
+ driver.quit()
96
+ print("๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์™„๋ฃŒ")
97
+ except:
98
+ pass
99
+ driver = None
100
+
101
+ def apply_filters(geo: str, time_range: str, category: str) -> bool:
102
+ """Google Trends ํ•„ํ„ฐ ์ ์šฉ"""
103
+ global driver
104
 
105
+ if not driver or not is_browser_ready:
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  return False
 
 
 
 
 
 
 
 
 
107
 
108
+ try:
109
+ with driver_lock:
110
+ # ํ•„ํ„ฐ ๋ฒ„ํŠผ๋“ค์„ ์ฐพ๊ณ  ํด๋ฆญ
111
+ try:
112
+ # ์ง€์—ญ ์„ค์ •
113
+ if geo != "KR": # ๊ธฐ๋ณธ๊ฐ’์ด ์•„๋‹Œ ๊ฒฝ์šฐ
114
+ geo_elements = driver.find_elements(By.XPATH, f"//div[contains(text(), '{geo}')]")
115
+ if geo_elements:
116
+ driver.execute_script("arguments[0].click();", geo_elements[0])
117
+ time.sleep(1)
118
 
119
+ # ์‹œ๊ฐ„ ๋ฒ”์œ„ ์„ค์ •
120
+ if time_range != "24์‹œ๊ฐ„":
121
+ time_elements = driver.find_elements(By.XPATH, f"//div[contains(text(), '{time_range}')]")
122
+ if time_elements:
123
+ driver.execute_script("arguments[0].click();", time_elements[0])
124
+ time.sleep(1)
 
125
 
126
+ # ์นดํ…Œ๊ณ ๋ฆฌ ์„ค์ •
127
+ if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
128
+ category_elements = driver.find_elements(By.XPATH, f"//div[contains(text(), '{category}')]")
129
+ if category_elements:
130
+ driver.execute_script("arguments[0].click();", category_elements[0])
131
+ time.sleep(1)
 
132
 
133
+ # ํ•„ํ„ฐ ์ ์šฉ ํ›„ ๋กœ๋”ฉ ๋Œ€๊ธฐ
134
+ time.sleep(3)
135
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ except Exception as filter_error:
138
+ print(f"ํ•„ํ„ฐ ์ ์šฉ ์ค‘ ์˜ค๋ฅ˜: {filter_error}")
139
+ return True # ํ•„ํ„ฐ ์ ์šฉ ์‹คํŒจํ•ด๋„ ์Šคํฌ๋ฆฐ์ƒท์€ ์ฐ์„ ์ˆ˜ ์žˆ์Œ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
+ except Exception as e:
142
+ print(f"ํ•„ํ„ฐ ์ ์šฉ ์‹คํŒจ: {str(e)}")
143
+ return False
144
+
145
+ def capture_screenshot() -> Optional[Image.Image]:
146
+ """ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜"""
147
+ global driver
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
+ if not driver or not is_browser_ready:
150
+ return None
 
151
 
152
+ try:
153
+ with driver_lock:
154
+ # ํŽ˜์ด์ง€๊ฐ€ ์™„์ „ํžˆ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
155
+ time.sleep(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
+ # ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
158
+ screenshot_data = driver.get_screenshot_as_png()
159
+ screenshot = Image.open(io.BytesIO(screenshot_data))
160
 
161
+ return screenshot
 
 
162
 
163
+ except Exception as e:
164
+ print(f"์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์‹คํŒจ: {str(e)}")
165
+ return None
166
 
167
+ def analyze_with_claude(image: Image.Image, user_prompt: str = "") -> str:
168
+ """Claude API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ๋ถ„์„"""
169
  try:
170
+ client = get_claude_client()
 
 
 
 
 
 
 
171
 
172
+ # ์ด๋ฏธ์ง€๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ
173
+ buffer = io.BytesIO()
174
+ image.save(buffer, format="PNG")
175
+ image_base64 = base64.b64encode(buffer.getvalue()).decode()
176
 
177
+ # Claude API ํ˜ธ์ถœ์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
178
+ default_prompt = """
179
+ ์ด Google Trends ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
 
 
 
 
180
 
181
+ ๋‹ค์Œ ๋‚ด์šฉ์„ ํฌํ•จํ•ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”:
182
+ 1. ํ˜„์žฌ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ๊ฒ€์ƒ‰์–ด๋“ค๊ณผ ๊ทธ ์ˆœ์œ„
183
+ 2. ๊ฐ ๊ฒ€์ƒ‰์–ด์˜ ์ฆ๊ฐ€ ์ถ”์„ธ๋‚˜ ํŠน์ด์‚ฌํ•ญ
184
+ 3. ๊ฒ€์ƒ‰์–ด๋“ค์—์„œ ๋ฐœ๊ฒฌ๋˜๋Š” ์ฃผ์š” ํŠธ๋ Œ๋“œ๋‚˜ ํŒจํ„ด
185
+ 4. ์‹œ์‚ฌ์ /๋ฌธํ™”์  ๋งฅ๋ฝ์—์„œ์˜ ํ•ด์„
186
+
187
+ ํ•œ๊ตญ์–ด๋กœ ์ƒ์„ธํ•˜๊ณ  ๊ตฌ์ฒด์ ์œผ๋กœ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
188
+ """
189
+
190
+ prompt = user_prompt if user_prompt.strip() else default_prompt
191
+
192
+ response = client.messages.create(
193
+ model="claude-3-sonnet-20240229",
194
+ max_tokens=1500,
 
 
 
 
195
  messages=[
196
  {
197
  "role": "user",
 
205
  "source": {
206
  "type": "base64",
207
  "media_type": "image/png",
208
+ "data": image_base64
209
  }
210
  }
211
  ]
 
213
  ]
214
  )
215
 
216
+ return response.content[0].text
217
 
218
  except Exception as e:
219
+ error_msg = f"Claude API ๋ถ„์„ ์‹คํŒจ: {str(e)}"
220
+ print(error_msg)
221
+ return error_msg
 
 
 
222
 
223
+ def refresh_trends_page():
224
+ """Google Trends ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ"""
225
+ global driver
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ if not driver or not is_browser_ready:
228
+ return "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
229
+
230
+ try:
231
+ with driver_lock:
232
+ driver.refresh()
233
+ time.sleep(3)
234
+ return "ํŽ˜์ด์ง€๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
235
+ except Exception as e:
236
+ return f"ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ: {str(e)}"
237
+
238
+ def main_process(geo: str, time_range: str, category: str, user_prompt: str) -> Tuple[Optional[Image.Image], str]:
239
+ """๋ฉ”์ธ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜"""
240
+ if not is_browser_ready:
241
+ return None, "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด์ฃผ์„ธ์š”."
242
 
243
  try:
244
+ # 1. ํ•„ํ„ฐ ์ ์šฉ
245
+ print(f"ํ•„ํ„ฐ ์ ์šฉ ์ค‘: ์ง€์—ญ={geo}, ์‹œ๊ฐ„={time_range}, ์นดํ…Œ๊ณ ๋ฆฌ={category}")
246
+ filter_success = apply_filters(geo, time_range, category)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
+ if not filter_success:
249
+ return None, "ํ•„ํ„ฐ ์ ์šฉ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
 
 
 
 
250
 
251
+ # 2. ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
252
+ print("์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
253
+ screenshot = capture_screenshot()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
+ if screenshot is None:
256
+ return None, "์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
257
 
258
+ # 3. Claude API๋กœ ๋ถ„์„
259
+ print("Claude API๋กœ ๋ถ„์„ ์ค‘...")
260
+ analysis_result = analyze_with_claude(screenshot, user_prompt)
 
 
 
 
 
 
 
 
 
 
261
 
262
+ return screenshot, analysis_result
263
 
264
  except Exception as e:
265
+ error_msg = f"์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
266
+ print(error_msg)
267
+ print(traceback.format_exc())
268
+ return None, error_msg
269
+
270
+ def check_browser_status():
271
+ """๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ"""
272
+ global is_browser_ready, driver
273
+
274
+ if not driver:
275
+ return "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
276
+
277
+ if not is_browser_ready:
278
+ return "๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘์ž…๋‹ˆ๋‹ค..."
279
+
280
+ try:
281
+ with driver_lock:
282
+ current_url = driver.current_url
283
+ return f"๋ธŒ๋ผ์šฐ์ € ์ค€๋น„ ์™„๋ฃŒ - ํ˜„์žฌ URL: {current_url}"
284
+ except:
285
+ return "๋ธŒ๋ผ์šฐ์ € ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค."
286
 
287
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
288
  def create_interface():
289
+ """Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ"""
290
 
291
+ with gr.Blocks(title="Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ", theme=gr.themes.Soft()) as interface:
292
+ gr.Markdown("# Google Trends ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ๋ถ„์„๊ธฐ")
293
+ gr.Markdown("Google Trends์˜ ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด๋ฅผ ์บก์ฒ˜ํ•˜๊ณ  Claude AI๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.")
 
 
 
 
 
 
 
 
 
294
 
 
 
 
 
 
 
 
 
 
 
295
  with gr.Row():
296
  with gr.Column(scale=1):
297
+ geo_dropdown = gr.Dropdown(
298
+ choices=["KR", "US", "JP", "GB", "DE", "FR"],
299
+ value="KR",
300
+ label="์ง€์—ญ ์„ ํƒ"
 
301
  )
302
 
303
+ time_dropdown = gr.Dropdown(
304
+ choices=["24์‹œ๊ฐ„", "7์ผ", "30์ผ", "90์ผ", "12๊ฐœ์›”"],
305
+ value="24์‹œ๊ฐ„",
306
+ label="๊ธฐ๊ฐ„ ์„ ํƒ"
 
 
307
  )
308
 
309
+ category_dropdown = gr.Dropdown(
310
+ choices=["๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "์Šคํฌ์ธ ", "๋น„์ฆˆ๋‹ˆ์Šค", "๊ณผํ•™๊ธฐ์ˆ ", "๊ฑด๊ฐ•"],
 
311
  value="๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ",
312
+ label="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ"
313
+ )
314
+
315
+ user_prompt = gr.Textbox(
316
+ label="๋ถ„์„ ์š”์ฒญ์‚ฌํ•ญ (์„ ํƒ์‚ฌํ•ญ)",
317
+ placeholder="ํŠน๋ณ„ํžˆ ๋ถ„์„ํ•˜๊ณ  ์‹ถ์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ์ž…๋ ฅํ•˜์„ธ์š”...",
318
+ lines=3
319
+ )
320
+
321
+ analyze_btn = gr.Button("๋ถ„์„ ์‹œ์ž‘", variant="primary")
322
+ refresh_btn = gr.Button("ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
323
+ status_btn = gr.Button("๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ", variant="secondary")
324
+
325
+ with gr.Column(scale=2):
326
+ screenshot_output = gr.Image(label="Google Trends ์Šคํฌ๋ฆฐ์ƒท")
327
+ analysis_output = gr.Textbox(
328
+ label="Claude AI ๋ถ„์„ ๊ฒฐ๊ณผ",
329
+ lines=10,
330
+ max_lines=20
331
  )
332
+ status_output = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€")
333
 
334
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
335
+ analyze_btn.click(
336
+ fn=main_process,
337
+ inputs=[geo_dropdown, time_dropdown, category_dropdown, user_prompt],
338
+ outputs=[screenshot_output, analysis_output]
339
  )
340
 
341
+ refresh_btn.click(
342
+ fn=refresh_trends_page,
343
+ outputs=status_output
 
 
 
 
 
 
 
344
  )
345
 
346
+ status_btn.click(
347
+ fn=check_browser_status,
348
+ outputs=status_output
 
 
 
349
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  return interface
352
 
353
+ def main():
354
+ """๋ฉ”์ธ ํ•จ์ˆ˜"""
355
  print("=" * 50)
356
  print("Google Trends ์‹ค์‹œ๊ฐ„ ๋ถ„์„๊ธฐ ์‹œ์ž‘!")
357
  print("=" * 50)
358
 
359
  # API ํ‚ค ํ™•์ธ
360
+ try:
361
+ get_claude_client()
362
+ print("โœ“ Claude API ํ‚ค ํ™•์ธ๋จ")
363
+ except ValueError as e:
364
+ print(f"โœ— {e}")
365
+ return
366
 
367
+ print("โœ“ Gradio ์›น ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
 
 
368
 
369
+ # ํ™˜๊ฒฝ ํ™•์ธ
370
  if os.getenv("SPACE_ID"):
371
+ print("โœ“ Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์ค‘...")
372
+
373
+ # ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” (๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ)
374
+ init_thread = threading.Thread(target=init_browser, daemon=True)
375
+ init_thread.start()
376
+
377
+ # ์ข…๋ฃŒ ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ
378
+ atexit.register(cleanup_browser)
379
+
380
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ ๋ฐ ์‹คํ–‰
381
+ interface = create_interface()
382
+
383
+ # Hugging Face Space์—์„œ๋Š” share=False๋กœ ์‹คํ–‰
384
+ interface.launch(
385
+ server_name="0.0.0.0",
386
+ server_port=7860,
387
+ share=False,
388
+ show_error=True,
389
+ quiet=False
390
+ )
391
+
392
+ if __name__ == "__main__":
393
+ main()