hanjunjung commited on
Commit
98e58ec
ยท
1 Parent(s): 30b99f5
Files changed (1) hide show
  1. app.py +1211 -344
app.py CHANGED
@@ -4,15 +4,11 @@ import base64
4
  import json
5
  import traceback
6
  import threading
 
7
  from typing import Tuple, Optional
8
  import gradio as gr
9
  import anthropic
10
- from selenium import webdriver
11
- from selenium.webdriver.chrome.options import Options
12
- from selenium.webdriver.common.by import By
13
- from selenium.webdriver.support.ui import WebDriverWait
14
- from selenium.webdriver.support import expected_conditions as EC
15
- from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException
16
  from concurrent.futures import ThreadPoolExecutor
17
  from datetime import datetime
18
 
@@ -37,8 +33,8 @@ def create_claude_client():
37
  print(f"Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์‹คํŒจ: {e}")
38
  return None
39
 
40
- class PersistentGoogleTrendsAutomator:
41
- """๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ณ„์† ์œ ์ง€ํ•˜๋Š” Google Trends ์ž๋™ํ™” ํด๋ž˜์Šค"""
42
 
43
  _instance = None
44
  _lock = threading.Lock()
@@ -47,281 +43,182 @@ class PersistentGoogleTrendsAutomator:
47
  if cls._instance is None:
48
  with cls._lock:
49
  if cls._instance is None:
50
- cls._instance = super(PersistentGoogleTrendsAutomator, cls).__new__(cls)
51
  cls._instance._initialized = False
52
  return cls._instance
53
 
54
  def __init__(self):
55
  if not self._initialized:
56
- self.driver = None
 
 
57
  self.is_browser_ready = False
58
  self.last_used = time.time()
59
  self.browser_lock = threading.Lock()
60
  self.initialization_lock = threading.Lock()
61
  self._initialized = True
62
- print("PersistentGoogleTrendsAutomator ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ")
63
-
64
- def setup_driver(self):
65
- """๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • (์ตœ์ดˆ 1ํšŒ๋งŒ)"""
66
- with self.initialization_lock:
67
- if self.driver is not None and self.is_browser_ready:
68
- print("๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ")
69
- return True
70
-
71
- print("์ƒˆ ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์ค‘...")
72
-
73
- options = Options()
74
-
75
- # Hugging Face Space ํ™˜๊ฒฝ ์„ค์ •
76
- if IS_HUGGINGFACE:
77
- print("Hugging Face Space ํ™˜๊ฒฝ, ํ—ค๋“œ๋ฆฌ์Šค ๋ชจ๋“œ")
78
- options.add_argument("--headless=new")
79
- options.add_argument("--no-sandbox")
80
- options.add_argument("--disable-dev-shm-usage")
81
- options.add_argument("--disable-gpu")
82
- options.add_argument("--disable-software-rasterizer")
83
- options.add_argument("--remote-debugging-port=9222")
84
- else:
85
- options.add_argument("--headless")
86
-
87
- # ์„ฑ๋Šฅ ์ตœ์ ํ™” ์„ค์ •
88
- options.add_argument("--window-size=1280,720")
89
- options.add_argument("--disable-blink-features=AutomationControlled")
90
- options.add_argument("--disable-extensions")
91
- options.add_argument("--disable-web-security")
92
- options.add_argument("--allow-running-insecure-content")
93
- options.add_argument("--disable-background-timer-throttling")
94
- options.add_argument("--disable-backgrounding-occluded-windows")
95
- options.add_argument("--disable-renderer-backgrounding")
96
- options.add_argument("--disable-features=TranslateUI")
97
- options.add_argument("--disable-ipc-flooding-protection")
98
-
99
- # ๋„คํŠธ์›Œํฌ ์ตœ์ ํ™”
100
- options.add_argument("--aggressive-cache-discard")
101
- options.add_argument("--disable-background-networking")
102
-
103
- # ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”
104
- options.add_argument("--memory-pressure-off")
105
- options.add_argument("--max_old_space_size=2048")
106
-
107
- # ์ด๋ฏธ์ง€/CSS ๋น„ํ™œ์„ฑํ™”
108
- prefs = {
109
- "profile.managed_default_content_settings.images": 2,
110
- "profile.default_content_setting_values.notifications": 2,
111
- }
112
- options.add_experimental_option("prefs", prefs)
113
 
114
- # ๋ด‡ ๊ฐ์ง€ ํšŒํ”ผ
115
- 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")
116
- options.add_experimental_option("excludeSwitches", ["enable-automation"])
117
- options.add_experimental_option('useAutomationExtension', False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- # ์–ธ์–ด ์„ค์ •
120
- options.add_argument("--lang=ko")
121
- options.add_argument("--accept-lang=ko-KR,ko;q=0.9,en;q=0.8")
122
 
123
- try:
124
- # ๋“œ๋ผ์ด๋ฒ„ ์ƒ์„ฑ
125
- if IS_HUGGINGFACE:
126
- try:
127
- from webdriver_manager.chrome import ChromeDriverManager
128
- from selenium.webdriver.chrome.service import Service
129
-
130
- service = Service(ChromeDriverManager().install())
131
- self.driver = webdriver.Chrome(service=service, options=options)
132
- print("ChromeDriver ์ž๋™ ์„ค์น˜ ์„ฑ๊ณต")
133
- except Exception as e:
134
- print(f"webdriver-manager ์‹คํŒจ: {e}")
135
- self.driver = webdriver.Chrome(options=options)
136
- print("์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ")
137
- else:
138
- try:
139
- from webdriver_manager.chrome import ChromeDriverManager
140
- from selenium.webdriver.chrome.service import Service
141
-
142
- service = Service(ChromeDriverManager().install())
143
- self.driver = webdriver.Chrome(service=service, options=options)
144
- except:
145
- self.driver = webdriver.Chrome(options=options)
146
-
147
- # ํƒ€์ž„์•„์›ƒ ์„ค์ •
148
- self.driver.set_page_load_timeout(20)
149
- self.driver.implicitly_wait(3)
150
-
151
- # Google Trends ํŽ˜์ด์ง€ ์ตœ์ดˆ ๋กœ๋”ฉ
152
- print("Google Trends ํŽ˜์ด์ง€ ์ตœ์ดˆ ๋กœ๋”ฉ ์ค‘...")
153
- self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
154
-
155
- # ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ ๋Œ€๊ธฐ
156
- WebDriverWait(self.driver, 10).until(
157
- EC.presence_of_element_located((By.TAG_NAME, "body"))
158
- )
159
-
160
- # ๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ
161
- try:
162
- self.driver.execute_script("""
163
- Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
164
- Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
165
- Object.defineProperty(navigator, 'languages', {get: () => ['ko-KR', 'ko', 'en']});
166
- """)
167
- except Exception as script_error:
168
- print(f"๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ ์‹คํŒจ (๋ฌด์‹œ): {script_error}")
169
-
170
- self.is_browser_ready = True
171
- self.last_used = time.time()
172
- print("๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์™„๋ฃŒ ๋ฐ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์„ฑ๊ณต")
173
- return True
174
-
175
- except Exception as e:
176
- print(f"๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์‹คํŒจ: {e}")
177
- self.cleanup_driver()
178
- return False
179
 
180
- def cleanup_driver(self):
181
  """๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
182
  try:
183
- if self.driver:
184
- self.driver.quit()
185
- print("๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ")
186
- except:
187
- pass
 
 
 
 
188
  finally:
189
- self.driver = None
 
 
190
  self.is_browser_ready = False
191
 
192
- def check_browser_health(self):
193
- """๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ ๋ฐ ํ•„์š”์‹œ ๋ณต๊ตฌ"""
194
  try:
195
- if not self.driver:
196
  return False
197
 
198
- # ๊ฐ„๋‹จํ•œ ์ƒํƒœ ์ฒดํฌ
199
- current_url = self.driver.current_url
200
  if "trends.google.com" not in current_url:
201
  print("๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž˜๋ชป๋œ ํŽ˜์ด์ง€์— ์žˆ์Œ, ๋ณต๊ตฌ ์ค‘...")
202
- self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
203
- time.sleep(2)
 
204
 
205
  return True
206
 
207
- except WebDriverException as e:
208
  print(f"๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ ์‹คํŒจ: {e}")
209
  return False
210
 
211
- def refresh_and_wait(self):
212
- """ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ๋ฐ ๋Œ€๊ธฐ (๋น ๋ฅธ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ )"""
213
  try:
214
  print("ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์œผ๋กœ ์ตœ์‹  ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ...")
215
- self.driver.refresh()
216
-
217
- # ์ตœ์†Œํ•œ์˜ ๋กœ๋”ฉ ๋Œ€๊ธฐ
218
- time.sleep(2)
219
-
220
- # ํŽ˜์ด์ง€ ์ค€๋น„ ํ™•์ธ
221
- WebDriverWait(self.driver, 5).until(
222
- EC.presence_of_element_located((By.TAG_NAME, "button"))
223
- )
224
-
225
  return True
226
-
227
  except Exception as e:
228
  print(f"ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ: {e}")
229
  return False
230
 
231
- def safe_find_element(self, selectors: list, timeout: int = 3):
232
- """์•ˆ์ „ํ•œ ์š”์†Œ ์ฐพ๊ธฐ"""
233
  for selector_type, selector in selectors:
234
  try:
235
  if selector_type == "xpath":
236
- element = WebDriverWait(self.driver, timeout).until(
237
- EC.presence_of_element_located((By.XPATH, selector))
238
- )
239
  elif selector_type == "css":
240
- element = WebDriverWait(self.driver, timeout).until(
241
- EC.presence_of_element_located((By.CSS_SELECTOR, selector))
242
- )
243
  elif selector_type == "text":
244
- element = WebDriverWait(self.driver, timeout).until(
245
- EC.presence_of_element_located((By.XPATH, f"//*[contains(text(), '{selector}')]"))
246
- )
247
 
248
  if element:
249
- return element
250
- except:
251
- continue
252
- return None
253
-
254
- def safe_click(self, selectors: list, timeout: int = 3) -> bool:
255
- """์•ˆ์ „ํ•œ ํด๋ฆญ"""
256
- element = self.safe_find_element(selectors, timeout)
257
- if element:
258
- try:
259
- self.driver.execute_script("arguments[0].click();", element)
260
- return True
261
- except:
262
- try:
263
- element.click()
264
  return True
265
- except:
266
- return False
 
267
  return False
268
 
269
- def change_settings_fast(self, region: str, period: str, category: str, progress_callback=None) -> dict:
270
- """๋น ๋ฅธ ์„ค์ • ๋ณ€๊ฒฝ (๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ํ™œ์šฉ)"""
271
- results = {
272
- 'region': False,
273
- 'period': False,
274
- 'category': False,
275
- 'errors': []
276
- }
277
-
278
- try:
279
- # ์„ค์ • ๋ณ€๊ฒฝ ์ „ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์œผ๋กœ ์ตœ์‹  ์ƒํƒœ ํ™•๋ณด
280
- self.refresh_and_wait()
281
-
282
- if region != "๋Œ€ํ•œ๋ฏผ๊ตญ":
283
- if progress_callback:
284
- progress_callback(f"์ง€์—ญ์„ {region}์œผ๋กœ ๋ณ€๊ฒฝ ์ค‘...")
285
- results['region'] = self.change_region(region)
286
- time.sleep(0.5)
287
- else:
288
- results['region'] = True
289
-
290
- if period != "์ง€๋‚œ 24์‹œ๊ฐ„":
291
- if progress_callback:
292
- progress_callback(f"๊ธฐ๊ฐ„์„ {period}๋กœ ๋ณ€๊ฒฝ ์ค‘...")
293
- results['period'] = self.change_time_period(period)
294
- time.sleep(0.5)
295
- else:
296
- results['period'] = True
297
-
298
- if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
299
- if progress_callback:
300
- progress_callback(f"์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ {category}๋กœ ๋ณ€๊ฒฝ ์ค‘...")
301
- results['category'] = self.change_category(category)
302
- time.sleep(0.5)
303
- else:
304
- results['category'] = True
305
-
306
- except Exception as e:
307
- results['errors'].append(str(e))
308
-
309
- return results
310
-
311
- def change_region(self, target_region: str) -> bool:
312
  """์ง€์—ญ ๋ณ€๊ฒฝ"""
313
  try:
 
314
  region_selectors = [
315
  ("xpath", "//button[@aria-label='๋Œ€ํ•œ๋ฏผ๊ตญ, ์œ„์น˜ ์„ ํƒ']"),
316
  ("xpath", "//button[contains(@aria-label, '์œ„์น˜ ์„ ํƒ')]"),
317
- ("xpath", "//span[contains(text(), '๋Œ€ํ•œ๋ฏผ๊ตญ')]//parent::button"),
318
  ]
319
 
320
- if not self.safe_click(region_selectors):
321
  return False
322
 
323
- time.sleep(1)
324
 
 
325
  region_mapping = {
326
  "์ „์„ธ๊ณ„": ["์ „ ์„ธ๊ณ„", "Worldwide"],
327
  "๋ฏธ๊ตญ": ["๋ฏธ๊ตญ", "United States"],
@@ -331,12 +228,12 @@ class PersistentGoogleTrendsAutomator:
331
 
332
  if target_region in region_mapping:
333
  for region_text in region_mapping[target_region]:
334
- region_option_selectors = [
 
335
  ("xpath", f"//span[contains(text(), '{region_text}')]"),
336
- ("xpath", f"//*[contains(text(), '{region_text}')]"),
337
  ]
338
 
339
- if self.safe_click(region_option_selectors, timeout=2):
340
  return True
341
 
342
  return False
@@ -345,18 +242,18 @@ class PersistentGoogleTrendsAutomator:
345
  print(f"์ง€์—ญ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
346
  return False
347
 
348
- def change_time_period(self, target_period: str) -> bool:
349
  """๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ"""
350
  try:
351
  period_selectors = [
352
  ("xpath", "//button[contains(@aria-label, '๊ธฐ๊ฐ„ ์„ ํƒ')]"),
353
- ("xpath", "//span[contains(text(), '์ง€๋‚œ 24์‹œ๊ฐ„')]//parent::button"),
354
  ]
355
 
356
- if not self.safe_click(period_selectors):
357
  return False
358
 
359
- time.sleep(1)
360
 
361
  period_mapping = {
362
  "์ง€๋‚œ 1์‹œ๊ฐ„": ["์ง€๋‚œ 1์‹œ๊ฐ„"],
@@ -367,12 +264,12 @@ class PersistentGoogleTrendsAutomator:
367
 
368
  if target_period in period_mapping:
369
  for period_text in period_mapping[target_period]:
370
- period_option_selectors = [
 
371
  ("xpath", f"//span[contains(text(), '{period_text}')]"),
372
- ("xpath", f"//*[contains(text(), '{period_text}')]"),
373
  ]
374
 
375
- if self.safe_click(period_option_selectors, timeout=2):
376
  return True
377
 
378
  return False
@@ -381,18 +278,18 @@ class PersistentGoogleTrendsAutomator:
381
  print(f"๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
382
  return False
383
 
384
- def change_category(self, target_category: str) -> bool:
385
  """์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ"""
386
  try:
387
  category_selectors = [
388
  ("xpath", "//button[contains(@aria-label, '์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ')]"),
389
- ("xpath", "//span[contains(text(), '๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ')]//parent::button"),
390
  ]
391
 
392
- if not self.safe_click(category_selectors):
393
  return False
394
 
395
- time.sleep(1)
396
 
397
  category_mapping = {
398
  "๊ฒŒ์ž„": ["๊ฒŒ์ž„"],
@@ -406,12 +303,12 @@ class PersistentGoogleTrendsAutomator:
406
 
407
  if target_category in category_mapping:
408
  for category_text in category_mapping[target_category]:
409
- category_option_selectors = [
 
410
  ("xpath", f"//span[contains(text(), '{category_text}')]"),
411
- ("xpath", f"//*[contains(text(), '{category_text}')]"),
412
  ]
413
 
414
- if self.safe_click(category_option_selectors, timeout=2):
415
  return True
416
 
417
  return False
@@ -420,76 +317,137 @@ class PersistentGoogleTrendsAutomator:
420
  print(f"์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
421
  return False
422
 
423
- def capture_trends_fast(self, region: str, period: str, category: str, progress_callback=None) -> Tuple[Optional[str], bool, str]:
424
- """๋น ๋ฅธ ํŠธ๋ Œ๋“œ ์บก์ฒ˜ (๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ)"""
 
 
 
 
 
 
425
 
426
- with self.browser_lock: # ๋™์‹œ ์ ‘๊ทผ ๋ฐฉ์ง€
427
- error_msg = ""
 
428
 
429
- try:
430
- # ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ
431
- if not self.is_browser_ready or not self.check_browser_health():
432
- if progress_callback:
433
- progress_callback("๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘... (์ตœ์ดˆ ์‹คํ–‰์‹œ๋งŒ)")
434
-
435
- if not self.setup_driver():
436
- return None, False, "๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์‹คํŒจ"
437
- else:
438
- if progress_callback:
439
- progress_callback("๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ์ค‘...")
440
-
441
- # ์„ค์ • ๋ณ€๊ฒฝ
442
  if progress_callback:
443
- progress_callback("์„ค์ • ๋ณ€๊ฒฝ ๋ฐ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ์ค‘...")
444
-
445
- settings_result = self.change_settings_fast(region, period, category, progress_callback)
446
-
447
- # ๊ฒฐ๊ณผ ํ™•์ธ
448
- changes_made = []
449
- if settings_result['region']:
450
- changes_made.append(f"์ง€์—ญ: {region}")
451
- if settings_result['period']:
452
- changes_made.append(f"๊ธฐ๊ฐ„: {period}")
453
- if settings_result['category']:
454
- changes_made.append(f"์นดํ…Œ๊ณ ๋ฆฌ: {category}")
455
-
456
- if settings_result['errors']:
457
- error_msg = "\n".join(settings_result['errors'])
458
 
 
459
  if progress_callback:
460
- progress_callback("์ตœ์‹  ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ, ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
461
-
462
- # ์ตœ์ข… ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋Œ€๊ธฐ
463
- time.sleep(1)
464
-
465
- # ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜
466
- screenshot = self.driver.get_screenshot_as_png()
467
- screenshot_b64 = base64.b64encode(screenshot).decode()
468
-
469
- # ์‚ฌ์šฉ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
470
- self.last_used = time.time()
471
 
472
- success_msg = f"์„ค์ • ์™„๋ฃŒ: {', '.join(changes_made) if changes_made else '๊ธฐ๋ณธ ์„ค์ • ์‚ฌ์šฉ'}"
 
 
 
 
 
 
473
 
474
- return screenshot_b64, True, success_msg + ("\n" + error_msg if error_msg else "")
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
- except Exception as e:
477
- error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
478
- # ์˜ค๋ฅ˜ ๋ฐœ์ƒ์‹œ ๋ธŒ๋ผ์šฐ์ € ์žฌ์‹œ์ž‘ ์‹œ๋„
479
- print(f"์˜ค๋ฅ˜๋กœ ์ธํ•œ ๋ธŒ๋ผ์šฐ์ € ์žฌ์‹œ์ž‘: {e}")
480
- self.cleanup_driver()
481
- return None, False, error_msg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
 
483
- # ๊ธ€๋กœ๋ฒŒ ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค (์‹ฑ๊ธ€ํ†ค)
484
  automator_instance = None
485
 
486
  def get_automator():
487
  """์ž๋™ํ™” ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ"""
488
  global automator_instance
489
  if automator_instance is None:
490
- automator_instance = PersistentGoogleTrendsAutomator()
491
  return automator_instance
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  def analyze_with_claude(screenshot_b64: str, region: str, period: str, category: str) -> str:
494
  """Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„"""
495
  try:
@@ -558,16 +516,16 @@ HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์‹œ๊ฐ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
558
  </div>
559
  """
560
 
561
- def persistent_browser_analysis(region: str, period: str, category: str):
562
- """๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„ ํ•จ์ˆ˜"""
563
 
564
  start_time = time.time()
565
 
566
  yield f"""
567
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
568
- <h3>๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„</h3>
569
  <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
570
- <p>์ฒซ ์‹คํ–‰: 15-20์ดˆ | ์ดํ›„ ์‹คํ–‰: 5-10์ดˆ</p>
571
  </div>
572
  """
573
 
@@ -588,21 +546,19 @@ def persistent_browser_analysis(region: str, period: str, category: str):
588
 
589
  # ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
590
  automator = get_automator()
591
-
592
- # ์ฒซ ์‹คํ–‰์ธ์ง€ ํ™•์ธ
593
  is_first_run = not automator.is_browser_ready
594
 
595
  yield f"""
596
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
597
- <h4>1๋‹จ๊ณ„: {"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”" if is_first_run else "๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"}</h4>
598
- <p>{"์ตœ์ดˆ ์‹คํ–‰์ด๋ฏ€๋กœ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ƒˆ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค" if is_first_run else "์ด๋ฏธ ์‹คํ–‰ ์ค‘์ธ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค"}</p>
599
  </div>
600
  """
601
 
602
- # ๋ธŒ๋ผ์šฐ์ € ์ž‘์—…์„ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
603
  with ThreadPoolExecutor(max_workers=1) as executor:
604
  future = executor.submit(
605
- automator.capture_trends_fast,
606
  region, period, category, progress_callback
607
  )
608
 
@@ -613,7 +569,7 @@ def persistent_browser_analysis(region: str, period: str, category: str):
613
  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>1๋‹จ๊ณ„: {"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”" if is_first_run else "๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"}</h4>
617
  <p>{current_progress}</p>
618
  <p>๊ฒฝ๊ณผ ์‹œ๊ฐ„: {elapsed:.1f}์ดˆ</p>
619
  </div>
@@ -639,7 +595,7 @@ def persistent_browser_analysis(region: str, period: str, category: str):
639
  # 2๋‹จ๊ณ„: AI ๋ถ„์„
640
  yield f"""
641
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
642
- <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์„ฑ๊ณต ({browser_time:.1f}์ดˆ)</h4>
643
  <p>{status_msg}</p>
644
  </div>
645
 
@@ -663,7 +619,7 @@ def persistent_browser_analysis(region: str, period: str, category: str):
663
  total_elapsed = time.time() - start_time
664
  yield f"""
665
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
666
- <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์„ฑ๊ณต ({browser_time:.1f}์ดˆ)</h4>
667
  <p>{status_msg}</p>
668
  </div>
669
 
@@ -681,35 +637,35 @@ def persistent_browser_analysis(region: str, period: str, category: str):
681
  # ์ตœ์ข… ๊ฒฐ๊ณผ
682
  yield f"""
683
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
684
- <h3>๋ถ„์„ ์™„๋ฃŒ ({total_time:.1f}์ดˆ)</h3>
685
- <p><strong>์„ฑ๋Šฅ:</strong> {"์ฒซ ์‹คํ–‰" if is_first_run else "๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"} | <strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
686
  </div>
687
 
688
  {analysis_result}
689
 
690
  <div style="margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center;">
691
  <p style="margin: 0; font-size: 14px;">
692
- <strong>๋‹ค์Œ ๋ถ„์„์€ ๋”์šฑ ๋น ๋ฆ…๋‹ˆ๋‹ค!</strong><br>
693
- ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ด๋ฏ€๋กœ 5-10์ดˆ๋งŒ์— ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค
694
  </p>
695
  </div>
696
 
697
  <div style="margin-top: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 5px; font-size: 12px; color: #666; text-align: center;">
698
- <p style="margin: 5px 0;"><strong>๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ:</strong> {"์ƒˆ๋กœ ์‹œ์ž‘๋จ" if is_first_run else "์žฌ์‚ฌ์šฉ๋จ"}</p>
699
  <p style="margin: 5px 0;"><strong>๋ถ„์„ ์‹œ๊ฐ„:</strong> {total_time:.1f}์ดˆ</p>
700
- <p style="margin: 5px 0;"><strong>๋‹ค์Œ ์˜ˆ์ƒ ์‹œ๊ฐ„:</strong> 5-10์ดˆ</p>
701
  </div>
702
  """
703
 
704
- print(f"๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„ ์™„๋ฃŒ: {total_time:.1f}์ดˆ")
705
 
706
  except Exception as e:
707
  error_details = traceback.format_exc()
708
- print(f"๋ถ„์„ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
709
 
710
  yield f"""
711
  <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
712
- <h3>์˜ค๋ฅ˜ ๋ฐœ์ƒ</h3>
713
  <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
714
  <details>
715
  <summary>์ƒ์„ธ ์˜ค๋ฅ˜ ์ •๋ณด</summary>
@@ -721,12 +677,12 @@ def persistent_browser_analysis(region: str, period: str, category: str):
721
  </div>
722
  """
723
 
724
- def create_persistent_interface():
725
- """๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Gradio ์ธํ„ฐํŽ˜์ด์Šค"""
726
 
727
  with gr.Blocks(
728
  theme=gr.themes.Soft(),
729
- title="๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„๊ธฐ",
730
  css="""
731
  .gradio-container {
732
  max-width: 1200px !important;
@@ -739,9 +695,9 @@ def create_persistent_interface():
739
 
740
  gr.HTML("""
741
  <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
742
- <h1>๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„๊ธฐ</h1>
743
- <p><strong>ํ˜์‹ ์  ์„ฑ๋Šฅ ๊ฐœ์„ :</strong> ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ณ„์† ์ผœ๋‘๊ณ  ์žฌ์‚ฌ์šฉํ•˜์—ฌ ์†๋„ ํ–ฅ์ƒ</p>
744
- <p style="font-size: 14px; opacity: 0.9;">์ฒซ ์‹คํ–‰: 15-20์ดˆ | ์ดํ›„ ์‹คํ–‰: 5-10์ดˆ</p>
745
  </div>
746
  """)
747
 
@@ -768,7 +724,7 @@ def create_persistent_interface():
768
  )
769
 
770
  analyze_btn = gr.Button(
771
- "๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„ ์‹œ์ž‘",
772
  variant="primary",
773
  size="lg"
774
  )
@@ -777,16 +733,16 @@ def create_persistent_interface():
777
  label="์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ฒฐ๊ณผ",
778
  value="""
779
  <div style="text-align: center; padding: 20px; border: 2px dashed #ccc; border-radius: 10px; color: #666;">
780
- <h3>๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„๊ธฐ ๋Œ€๊ธฐ ์ค‘</h3>
781
- <p><strong>์ฒซ ์‹คํ–‰:</strong> ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”๋กœ 15-20์ดˆ ์†Œ์š”</p>
782
- <p><strong>์ดํ›„ ์‹คํ–‰:</strong> ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ์œผ๋กœ 5-10์ดˆ ์™„๋ฃŒ</p>
783
  <p style="font-size: 14px;">์œ„์˜ ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!</p>
784
  </div>
785
  """
786
  )
787
 
788
  analyze_btn.click(
789
- fn=persistent_browser_analysis,
790
  inputs=[region_input, period_input, category_input],
791
  outputs=output,
792
  show_progress="hidden"
@@ -794,22 +750,22 @@ def create_persistent_interface():
794
 
795
  gr.HTML("""
796
  <div style="margin-top: 30px; padding: 20px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px;">
797
- <h3 style="margin-top: 0; color: #333;">๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ์˜ ์žฅ์ </h3>
798
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: #555;">
799
  <div>
800
- <h4>์„ฑ๋Šฅ ํ–ฅ์ƒ</h4>
801
  <ul style="margin: 0; padding-left: 20px;">
802
- <li>๋‘ ๋ฒˆ์งธ๋ถ€ํ„ฐ 8-15์ดˆ ๋‹จ์ถ•</li>
803
- <li>๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์‹œ๊ฐ„ ์ œ๊ฑฐ</li>
804
- <li>ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์‹œ๊ฐ„ ์ตœ์†Œํ™”</li>
805
  </ul>
806
  </div>
807
  <div>
808
- <h4>์ž์› ํšจ์œจ์„ฑ</h4>
809
  <ul style="margin: 0; padding-left: 20px;">
810
- <li>๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™”</li>
811
- <li>CPU ๋ถ€ํ•˜ ๊ฐ์†Œ</li>
812
- <li>์•ˆ์ •์ ์ธ ์„ธ์…˜ ์œ ์ง€</li>
813
  </ul>
814
  </div>
815
  </div>
@@ -825,23 +781,63 @@ def cleanup_on_exit():
825
  """์•ฑ ์ข…๋ฃŒ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
826
  global automator_instance
827
  if automator_instance:
828
- automator_instance.cleanup_driver()
829
- print("์•ฑ ์ข…๋ฃŒ: ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์™„๋ฃŒ")
 
 
 
 
 
 
830
 
831
  atexit.register(cleanup_on_exit)
832
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
  if __name__ == "__main__":
834
  print("=" * 50)
835
- print("๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„๊ธฐ ์‹œ์ž‘")
836
  print("=" * 50)
837
 
 
 
 
 
 
 
 
 
 
838
  if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
839
  print("ANTHROPIC_API_KEY ๋ฏธ์„ค์ • - ์Šคํฌ๋ฆฐ์ƒท๋งŒ ์บก์ฒ˜")
840
  else:
841
  print("Claude API ํ‚ค ํ™•์ธ๋จ")
842
 
843
- print("๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
844
- app = create_persistent_interface()
845
 
846
  if os.getenv("SPACE_ID"):
847
  print("Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰")
@@ -860,4 +856,875 @@ if __name__ == "__main__":
860
  show_api=False,
861
  share=False,
862
  inbrowser=True
863
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import json
5
  import traceback
6
  import threading
7
+ import asyncio
8
  from typing import Tuple, Optional
9
  import gradio as gr
10
  import anthropic
11
+ from playwright.async_api import async_playwright, Browser, Page
 
 
 
 
 
12
  from concurrent.futures import ThreadPoolExecutor
13
  from datetime import datetime
14
 
 
33
  print(f"Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์‹คํŒจ: {e}")
34
  return None
35
 
36
+ class PlaywrightGoogleTrendsAutomator:
37
+ """Playwright ๊ธฐ๋ฐ˜ Google Trends ์ž๋™ํ™” ํด๋ž˜์Šค (๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ)"""
38
 
39
  _instance = None
40
  _lock = threading.Lock()
 
43
  if cls._instance is None:
44
  with cls._lock:
45
  if cls._instance is None:
46
+ cls._instance = super(PlaywrightGoogleTrendsAutomator, cls).__new__(cls)
47
  cls._instance._initialized = False
48
  return cls._instance
49
 
50
  def __init__(self):
51
  if not self._initialized:
52
+ self.playwright = None
53
+ self.browser = None
54
+ self.page = None
55
  self.is_browser_ready = False
56
  self.last_used = time.time()
57
  self.browser_lock = threading.Lock()
58
  self.initialization_lock = threading.Lock()
59
  self._initialized = True
60
+ print("PlaywrightGoogleTrendsAutomator ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ")
61
+
62
+ async def setup_browser(self):
63
+ """๋ธŒ๋ผ์šฐ์ € ์„ค์ • (์ตœ์ดˆ 1ํšŒ๋งŒ)"""
64
+ if self.browser is not None and self.is_browser_ready:
65
+ print("๊ธฐ์กด Playwright ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ")
66
+ return True
67
+
68
+ print("์ƒˆ Playwright ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์ค‘...")
69
+
70
+ try:
71
+ # Playwright ์‹œ์ž‘
72
+ self.playwright = await async_playwright().start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
+ # ๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰ ์˜ต์…˜
75
+ browser_args = [
76
+ '--no-sandbox',
77
+ '--disable-dev-shm-usage',
78
+ '--disable-gpu',
79
+ '--disable-software-rasterizer',
80
+ '--disable-background-timer-throttling',
81
+ '--disable-backgrounding-occluded-windows',
82
+ '--disable-renderer-backgrounding',
83
+ '--disable-features=TranslateUI',
84
+ '--disable-extensions',
85
+ '--disable-web-security',
86
+ '--allow-running-insecure-content',
87
+ '--memory-pressure-off',
88
+ '--max_old_space_size=2048',
89
+ '--lang=ko',
90
+ '--accept-lang=ko-KR,ko;q=0.9,en;q=0.8'
91
+ ]
92
 
93
+ # Hugging Face Space ํ™˜๊ฒฝ์—์„œ๋Š” headless ๋ชจ๋“œ
94
+ headless = IS_HUGGINGFACE
 
95
 
96
+ # Chromium ๋ธŒ๋ผ์šฐ์ € ์‹คํ–‰
97
+ self.browser = await self.playwright.chromium.launch(
98
+ headless=headless,
99
+ args=browser_args
100
+ )
101
+
102
+ # ์ƒˆ ํŽ˜์ด์ง€ ์ƒ์„ฑ
103
+ self.page = await self.browser.new_page()
104
+
105
+ # User Agent ์„ค์ •
106
+ await self.page.set_user_agent(
107
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
108
+ )
109
+
110
+ # ๋ทฐํฌํŠธ ์„ค์ •
111
+ await self.page.set_viewport_size({"width": 1280, "height": 720})
112
+
113
+ # Google Trends ํŽ˜์ด์ง€ ์ตœ์ดˆ ๋กœ๋”ฉ
114
+ print("Google Trends ํŽ˜์ด์ง€ ์ตœ์ดˆ ๋กœ๋”ฉ ์ค‘...")
115
+ await self.page.goto("https://trends.google.com/trending?geo=KR&hl=ko",
116
+ wait_until='domcontentloaded', timeout=30000)
117
+
118
+ # ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ ๋Œ€๊ธฐ
119
+ await self.page.wait_for_load_state('networkidle', timeout=15000)
120
+
121
+ # ๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ
122
+ await self.page.evaluate("""
123
+ Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
124
+ Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
125
+ Object.defineProperty(navigator, 'languages', {get: () => ['ko-KR', 'ko', 'en']});
126
+ """)
127
+
128
+ self.is_browser_ready = True
129
+ self.last_used = time.time()
130
+ print("Playwright ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
131
+ return True
132
+
133
+ except Exception as e:
134
+ print(f"Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์ • ์‹คํŒจ: {e}")
135
+ await self.cleanup_browser()
136
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ async def cleanup_browser(self):
139
  """๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
140
  try:
141
+ if self.page:
142
+ await self.page.close()
143
+ if self.browser:
144
+ await self.browser.close()
145
+ if self.playwright:
146
+ await self.playwright.stop()
147
+ print("Playwright ๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ")
148
+ except Exception as e:
149
+ print(f"๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {e}")
150
  finally:
151
+ self.page = None
152
+ self.browser = None
153
+ self.playwright = None
154
  self.is_browser_ready = False
155
 
156
+ async def check_browser_health(self):
157
+ """๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ"""
158
  try:
159
+ if not self.page or not self.browser:
160
  return False
161
 
162
+ # ํ˜„์žฌ URL ํ™•์ธ
163
+ current_url = self.page.url
164
  if "trends.google.com" not in current_url:
165
  print("๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž˜๋ชป๋œ ํŽ˜์ด์ง€์— ์žˆ์Œ, ๋ณต๊ตฌ ์ค‘...")
166
+ await self.page.goto("https://trends.google.com/trending?geo=KR&hl=ko",
167
+ wait_until='domcontentloaded')
168
+ await self.page.wait_for_load_state('networkidle', timeout=10000)
169
 
170
  return True
171
 
172
+ except Exception as e:
173
  print(f"๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ ์‹คํŒจ: {e}")
174
  return False
175
 
176
+ async def refresh_and_wait(self):
177
+ """ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ๋ฐ ๋Œ€๊ธฐ"""
178
  try:
179
  print("ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์œผ๋กœ ์ตœ์‹  ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ...")
180
+ await self.page.reload(wait_until='domcontentloaded')
181
+ await self.page.wait_for_load_state('networkidle', timeout=10000)
 
 
 
 
 
 
 
 
182
  return True
 
183
  except Exception as e:
184
  print(f"ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ: {e}")
185
  return False
186
 
187
+ async def safe_click(self, selectors: list, timeout: int = 3000) -> bool:
188
+ """์•ˆ์ „ํ•œ ํด๋ฆญ"""
189
  for selector_type, selector in selectors:
190
  try:
191
  if selector_type == "xpath":
192
+ element = await self.page.wait_for_selector(f"xpath={selector}", timeout=timeout)
 
 
193
  elif selector_type == "css":
194
+ element = await self.page.wait_for_selector(selector, timeout=timeout)
 
 
195
  elif selector_type == "text":
196
+ element = await self.page.wait_for_selector(f"text={selector}", timeout=timeout)
 
 
197
 
198
  if element:
199
+ await element.click()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  return True
201
+ except Exception as e:
202
+ print(f"ํด๋ฆญ ์‹œ๋„ ์‹คํŒจ ({selector_type}: {selector}): {e}")
203
+ continue
204
  return False
205
 
206
+ async def change_region(self, target_region: str) -> bool:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  """์ง€์—ญ ๋ณ€๊ฒฝ"""
208
  try:
209
+ # ์ง€์—ญ ๋ฒ„ํŠผ ํด๋ฆญ
210
  region_selectors = [
211
  ("xpath", "//button[@aria-label='๋Œ€ํ•œ๋ฏผ๊ตญ, ์œ„์น˜ ์„ ํƒ']"),
212
  ("xpath", "//button[contains(@aria-label, '์œ„์น˜ ์„ ํƒ')]"),
213
+ ("css", "button[aria-label*='์œ„์น˜ ์„ ํƒ']"),
214
  ]
215
 
216
+ if not await self.safe_click(region_selectors):
217
  return False
218
 
219
+ await asyncio.sleep(1)
220
 
221
+ # ์ง€์—ญ ๋งคํ•‘
222
  region_mapping = {
223
  "์ „์„ธ๊ณ„": ["์ „ ์„ธ๊ณ„", "Worldwide"],
224
  "๋ฏธ๊ตญ": ["๋ฏธ๊ตญ", "United States"],
 
228
 
229
  if target_region in region_mapping:
230
  for region_text in region_mapping[target_region]:
231
+ region_selectors = [
232
+ ("text", region_text),
233
  ("xpath", f"//span[contains(text(), '{region_text}')]"),
 
234
  ]
235
 
236
+ if await self.safe_click(region_selectors, timeout=2000):
237
  return True
238
 
239
  return False
 
242
  print(f"์ง€์—ญ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
243
  return False
244
 
245
+ async def change_time_period(self, target_period: str) -> bool:
246
  """๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ"""
247
  try:
248
  period_selectors = [
249
  ("xpath", "//button[contains(@aria-label, '๊ธฐ๊ฐ„ ์„ ํƒ')]"),
250
+ ("css", "button[aria-label*='๊ธฐ๊ฐ„ ์„ ํƒ']"),
251
  ]
252
 
253
+ if not await self.safe_click(period_selectors):
254
  return False
255
 
256
+ await asyncio.sleep(1)
257
 
258
  period_mapping = {
259
  "์ง€๋‚œ 1์‹œ๊ฐ„": ["์ง€๋‚œ 1์‹œ๊ฐ„"],
 
264
 
265
  if target_period in period_mapping:
266
  for period_text in period_mapping[target_period]:
267
+ period_selectors = [
268
+ ("text", period_text),
269
  ("xpath", f"//span[contains(text(), '{period_text}')]"),
 
270
  ]
271
 
272
+ if await self.safe_click(period_selectors, timeout=2000):
273
  return True
274
 
275
  return False
 
278
  print(f"๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
279
  return False
280
 
281
+ async def change_category(self, target_category: str) -> bool:
282
  """์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ"""
283
  try:
284
  category_selectors = [
285
  ("xpath", "//button[contains(@aria-label, '์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ')]"),
286
+ ("css", "button[aria-label*='์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ']"),
287
  ]
288
 
289
+ if not await self.safe_click(category_selectors):
290
  return False
291
 
292
+ await asyncio.sleep(1)
293
 
294
  category_mapping = {
295
  "๊ฒŒ์ž„": ["๊ฒŒ์ž„"],
 
303
 
304
  if target_category in category_mapping:
305
  for category_text in category_mapping[target_category]:
306
+ category_selectors = [
307
+ ("text", category_text),
308
  ("xpath", f"//span[contains(text(), '{category_text}')]"),
 
309
  ]
310
 
311
+ if await self.safe_click(category_selectors, timeout=2000):
312
  return True
313
 
314
  return False
 
317
  print(f"์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
318
  return False
319
 
320
+ async def change_settings_fast(self, region: str, period: str, category: str, progress_callback=None) -> dict:
321
+ """๋น ๋ฅธ ์„ค์ • ๋ณ€๊ฒฝ"""
322
+ results = {
323
+ 'region': False,
324
+ 'period': False,
325
+ 'category': False,
326
+ 'errors': []
327
+ }
328
 
329
+ try:
330
+ # ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
331
+ await self.refresh_and_wait()
332
 
333
+ if region != "๋Œ€ํ•œ๋ฏผ๊ตญ":
 
 
 
 
 
 
 
 
 
 
 
 
334
  if progress_callback:
335
+ progress_callback(f"์ง€์—ญ์„ {region}์œผ๋กœ ๋ณ€๊ฒฝ ์ค‘...")
336
+ results['region'] = await self.change_region(region)
337
+ await asyncio.sleep(0.5)
338
+ else:
339
+ results['region'] = True
 
 
 
 
 
 
 
 
 
 
340
 
341
+ if period != "์ง€๋‚œ 24์‹œ๊ฐ„":
342
  if progress_callback:
343
+ progress_callback(f"๊ธฐ๊ฐ„์„ {period}๋กœ ๋ณ€๊ฒฝ ์ค‘...")
344
+ results['period'] = await self.change_time_period(period)
345
+ await asyncio.sleep(0.5)
346
+ else:
347
+ results['period'] = True
 
 
 
 
 
 
348
 
349
+ if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
350
+ if progress_callback:
351
+ progress_callback(f"์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ {category}๋กœ ๋ณ€๊ฒฝ ์ค‘...")
352
+ results['category'] = await self.change_category(category)
353
+ await asyncio.sleep(0.5)
354
+ else:
355
+ results['category'] = True
356
 
357
+ except Exception as e:
358
+ results['errors'].append(str(e))
359
+
360
+ return results
361
+
362
+ async def capture_trends_fast(self, region: str, period: str, category: str, progress_callback=None) -> Tuple[Optional[str], bool, str]:
363
+ """๋น ๋ฅธ ํŠธ๋ Œ๋“œ ์บก์ฒ˜"""
364
+ error_msg = ""
365
+
366
+ try:
367
+ # ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ
368
+ if not self.is_browser_ready or not await self.check_browser_health():
369
+ if progress_callback:
370
+ progress_callback("Playwright ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘... (์ตœ์ดˆ ์‹คํ–‰์‹œ๋งŒ)")
371
 
372
+ if not await self.setup_browser():
373
+ return None, False, "Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์ • ์‹คํŒจ"
374
+ else:
375
+ if progress_callback:
376
+ progress_callback("๊ธฐ์กด Playwright ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ์ค‘...")
377
+
378
+ # ์„ค์ • ๋ณ€๊ฒฝ
379
+ if progress_callback:
380
+ progress_callback("์„ค์ • ๋ณ€๊ฒฝ ๋ฐ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ์ค‘...")
381
+
382
+ settings_result = await self.change_settings_fast(region, period, category, progress_callback)
383
+
384
+ # ๊ฒฐ๊ณผ ํ™•์ธ
385
+ changes_made = []
386
+ if settings_result['region']:
387
+ changes_made.append(f"์ง€์—ญ: {region}")
388
+ if settings_result['period']:
389
+ changes_made.append(f"๊ธฐ๊ฐ„: {period}")
390
+ if settings_result['category']:
391
+ changes_made.append(f"์นดํ…Œ๊ณ ๋ฆฌ: {category}")
392
+
393
+ if settings_result['errors']:
394
+ error_msg = "\n".join(settings_result['errors'])
395
+
396
+ if progress_callback:
397
+ progress_callback("์ตœ์‹  ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ, ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
398
+
399
+ # ์ตœ์ข… ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋Œ€๊ธฐ
400
+ await asyncio.sleep(1)
401
+
402
+ # ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜
403
+ screenshot_bytes = await self.page.screenshot(full_page=False)
404
+ screenshot_b64 = base64.b64encode(screenshot_bytes).decode()
405
+
406
+ # ์‚ฌ์šฉ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
407
+ self.last_used = time.time()
408
+
409
+ success_msg = f"์„ค์ • ์™„๋ฃŒ: {', '.join(changes_made) if changes_made else '๊ธฐ๋ณธ ์„ค์ • ์‚ฌ์šฉ'}"
410
+
411
+ return screenshot_b64, True, success_msg + ("\n" + error_msg if error_msg else "")
412
+
413
+ except Exception as e:
414
+ error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
415
+ print(f"์˜ค๋ฅ˜๋กœ ์ธํ•œ ๋ธŒ๋ผ์šฐ์ € ์žฌ์‹œ์ž‘: {e}")
416
+ await self.cleanup_browser()
417
+ return None, False, error_msg
418
 
419
+ # ๊ธ€๋กœ๋ฒŒ ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค
420
  automator_instance = None
421
 
422
  def get_automator():
423
  """์ž๋™ํ™” ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ"""
424
  global automator_instance
425
  if automator_instance is None:
426
+ automator_instance = PlaywrightGoogleTrendsAutomator()
427
  return automator_instance
428
 
429
+ # ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๋ž˜ํผ
430
+ def run_async_capture(region: str, period: str, category: str, progress_callback=None):
431
+ """๋น„๋™๊ธฐ ์บก์ฒ˜ ํ•จ์ˆ˜๋ฅผ ๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰"""
432
+ automator = get_automator()
433
+
434
+ try:
435
+ # ์ƒˆ๋กœ์šด ์ด๋ฒคํŠธ ๋ฃจํ”„ ์ƒ์„ฑ ๋ฐ ์‹คํ–‰
436
+ loop = asyncio.new_event_loop()
437
+ asyncio.set_event_loop(loop)
438
+ result = loop.run_until_complete(
439
+ automator.capture_trends_fast(region, period, category, progress_callback)
440
+ )
441
+ return result
442
+ except Exception as e:
443
+ print(f"๋น„๋™๊ธฐ ์‹คํ–‰ ์˜ค๋ฅ˜: {e}")
444
+ return None, False, f"๋น„๋™๊ธฐ ์‹คํ–‰ ์˜ค๋ฅ˜: {str(e)}"
445
+ finally:
446
+ try:
447
+ loop.close()
448
+ except:
449
+ pass
450
+
451
  def analyze_with_claude(screenshot_b64: str, region: str, period: str, category: str) -> str:
452
  """Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„"""
453
  try:
 
516
  </div>
517
  """
518
 
519
+ def playwright_browser_analysis(region: str, period: str, category: str):
520
+ """Playwright ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„ ํ•จ์ˆ˜"""
521
 
522
  start_time = time.time()
523
 
524
  yield f"""
525
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
526
+ <h3>Playwright Google Trends ๋ถ„์„</h3>
527
  <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
528
+ <p>Chrome ์„ค์น˜ ์—†์ด Playwright๋กœ ์•ˆ์ •์  ์‹คํ–‰</p>
529
  </div>
530
  """
531
 
 
546
 
547
  # ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
548
  automator = get_automator()
 
 
549
  is_first_run = not automator.is_browser_ready
550
 
551
  yield f"""
552
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
553
+ <h4>1๋‹จ๊ณ„: {"Playwright ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”" if is_first_run else "๊ธฐ์กด Playwright ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"}</h4>
554
+ <p>{"Chromium ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ž๋™ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค" if is_first_run else "์ด๋ฏธ ์‹คํ–‰ ์ค‘์ธ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค"}</p>
555
  </div>
556
  """
557
 
558
+ # ๋ธŒ๋ผ์šฐ์ € ์ž‘์—… ์‹คํ–‰
559
  with ThreadPoolExecutor(max_workers=1) as executor:
560
  future = executor.submit(
561
+ run_async_capture,
562
  region, period, category, progress_callback
563
  )
564
 
 
569
  elapsed = time.time() - start_time
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>1๋‹จ๊ณ„: {"Playwright ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”" if is_first_run else "๊ธฐ์กด Playwright ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"}</h4>
573
  <p>{current_progress}</p>
574
  <p>๊ฒฝ๊ณผ ์‹œ๊ฐ„: {elapsed:.1f}์ดˆ</p>
575
  </div>
 
595
  # 2๋‹จ๊ณ„: AI ๋ถ„์„
596
  yield f"""
597
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
598
+ <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: Playwright ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์„ฑ๊ณต ({browser_time:.1f}์ดˆ)</h4>
599
  <p>{status_msg}</p>
600
  </div>
601
 
 
619
  total_elapsed = time.time() - start_time
620
  yield f"""
621
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
622
+ <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: Playwright ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์„ฑ๊ณต ({browser_time:.1f}์ดˆ)</h4>
623
  <p>{status_msg}</p>
624
  </div>
625
 
 
637
  # ์ตœ์ข… ๊ฒฐ๊ณผ
638
  yield f"""
639
  <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
640
+ <h3>Playwright ๋ถ„์„ ์™„๋ฃŒ ({total_time:.1f}์ดˆ)</h3>
641
+ <p><strong>์—”์ง„:</strong> Playwright Chromium | <strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
642
  </div>
643
 
644
  {analysis_result}
645
 
646
  <div style="margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center;">
647
  <p style="margin: 0; font-size: 14px;">
648
+ <strong>Playwright์˜ ์žฅ์ </strong><br>
649
+ Chrome ์„ค์น˜ ๋ถˆํ•„์š”, ์ž๋™ ๋ธŒ๋ผ์šฐ์ € ๊ด€๋ฆฌ, Hugging Face Space ์™„๋ฒฝ ํ˜ธํ™˜
650
  </p>
651
  </div>
652
 
653
  <div style="margin-top: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 5px; font-size: 12px; color: #666; text-align: center;">
654
+ <p style="margin: 5px 0;"><strong>๋ธŒ๋ผ์šฐ์ €:</strong> Playwright Chromium {"(์ƒˆ๋กœ ์‹œ์ž‘๋จ)" if is_first_run else "(์žฌ์‚ฌ์šฉ๋จ)"}</p>
655
  <p style="margin: 5px 0;"><strong>๋ถ„์„ ์‹œ๊ฐ„:</strong> {total_time:.1f}์ดˆ</p>
656
+ <p style="margin: 5px 0;"><strong>ํ™˜๊ฒฝ:</strong> {"Hugging Face Space" if IS_HUGGINGFACE else "๋กœ์ปฌ"}</p>
657
  </div>
658
  """
659
 
660
+ print(f"Playwright ๋ถ„์„ ์™„๋ฃŒ: {total_time:.1f}์ดˆ")
661
 
662
  except Exception as e:
663
  error_details = traceback.format_exc()
664
+ print(f"Playwright ๋ถ„์„ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
665
 
666
  yield f"""
667
  <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
668
+ <h3>Playwright ์˜ค๋ฅ˜ ๋ฐœ์ƒ</h3>
669
  <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
670
  <details>
671
  <summary>์ƒ์„ธ ์˜ค๋ฅ˜ ์ •๋ณด</summary>
 
677
  </div>
678
  """
679
 
680
+ def create_playwright_interface():
681
+ """Playwright Gradio ์ธํ„ฐํŽ˜์ด์Šค"""
682
 
683
  with gr.Blocks(
684
  theme=gr.themes.Soft(),
685
+ title="Playwright Google Trends ๋ถ„์„๊ธฐ",
686
  css="""
687
  .gradio-container {
688
  max-width: 1200px !important;
 
695
 
696
  gr.HTML("""
697
  <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
698
+ <h1>Playwright Google Trends ๋ถ„์„๊ธฐ</h1>
699
+ <p><strong>Chrome ์„ค์น˜ ๋ถˆํ•„์š”:</strong> Playwright๊ฐ€ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค</p>
700
+ <p style="font-size: 14px; opacity: 0.9;">Hugging Face Space ์™„๋ฒฝ ํ˜ธํ™˜ | ์•ˆ์ •์  ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ</p>
701
  </div>
702
  """)
703
 
 
724
  )
725
 
726
  analyze_btn = gr.Button(
727
+ "Playwright ๋ถ„์„ ์‹œ์ž‘ (Chrome ์„ค์น˜ ๋ถˆํ•„์š”)",
728
  variant="primary",
729
  size="lg"
730
  )
 
733
  label="์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ฒฐ๊ณผ",
734
  value="""
735
  <div style="text-align: center; padding: 20px; border: 2px dashed #ccc; border-radius: 10px; color: #666;">
736
+ <h3>Playwright ๋ถ„์„๊ธฐ ๋Œ€๊ธฐ ์ค‘</h3>
737
+ <p><strong>Chrome ์„ค์น˜ ๋ถˆํ•„์š”:</strong> Playwright๊ฐ€ Chromium์„ ์ž๋™์œผ๋กœ ๋‹ค์šด๋กœ๋“œ</p>
738
+ <p><strong>Hugging Face Space:</strong> ์™„๋ฒฝ ํ˜ธํ™˜</p>
739
  <p style="font-size: 14px;">์œ„์˜ ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!</p>
740
  </div>
741
  """
742
  )
743
 
744
  analyze_btn.click(
745
+ fn=playwright_browser_analysis,
746
  inputs=[region_input, period_input, category_input],
747
  outputs=output,
748
  show_progress="hidden"
 
750
 
751
  gr.HTML("""
752
  <div style="margin-top: 30px; padding: 20px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px;">
753
+ <h3 style="margin-top: 0; color: #333;">Playwright์˜ ์žฅ์ </h3>
754
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: #555;">
755
  <div>
756
+ <h4>์„ค์น˜ ๋ถˆํ•„์š”</h4>
757
  <ul style="margin: 0; padding-left: 20px;">
758
+ <li>Chrome ์ˆ˜๋™ ์„ค์น˜ ๋ถˆํ•„์š”</li>
759
+ <li>๋ธŒ๋ผ์šฐ์ € ์ž๋™ ๋‹ค์šด๋กœ๋“œ</li>
760
+ <li>๋ฒ„์ „ ํ˜ธํ™˜์„ฑ ์ž๋™ ๊ด€๋ฆฌ</li>
761
  </ul>
762
  </div>
763
  <div>
764
+ <h4>์•ˆ์ •์„ฑ</h4>
765
  <ul style="margin: 0; padding-left: 20px;">
766
+ <li>Hugging Face Space ์™„๋ฒฝ ์ง€์›</li>
767
+ <li>๋ธŒ๋ผ์šฐ์ € ํฌ๋ž˜์‹œ ์ž๋™๋ณต๊ตฌ</li>
768
+ <li>๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์  ๊ด€๋ฆฌ</li>
769
  </ul>
770
  </div>
771
  </div>
 
781
  """์•ฑ ์ข…๋ฃŒ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
782
  global automator_instance
783
  if automator_instance:
784
+ try:
785
+ loop = asyncio.new_event_loop()
786
+ asyncio.set_event_loop(loop)
787
+ loop.run_until_complete(automator_instance.cleanup_browser())
788
+ loop.close()
789
+ print("์•ฑ ์ข…๋ฃŒ: Playwright ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์™„๋ฃŒ")
790
+ except Exception as e:
791
+ print(f"๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {e}")
792
 
793
  atexit.register(cleanup_on_exit)
794
 
795
+ # Playwright ๋ธŒ๋ผ์šฐ์ € ์ž๋™ ์„ค์น˜
796
+ def install_playwright_browsers():
797
+ """Playwright ๋ธŒ๋ผ์šฐ์ € ์ž๋™ ์„ค์น˜"""
798
+ try:
799
+ import subprocess
800
+ import sys
801
+
802
+ print("Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜ ํ™•์ธ ์ค‘...")
803
+
804
+ # Chromium ์„ค์น˜
805
+ result = subprocess.run([
806
+ sys.executable, "-m", "playwright", "install", "chromium"
807
+ ], capture_output=True, text=True, timeout=300)
808
+
809
+ if result.returncode == 0:
810
+ print("Playwright Chromium ์„ค์น˜ ์™„๋ฃŒ")
811
+ return True
812
+ else:
813
+ print(f"Playwright ์„ค์น˜ ์‹คํŒจ: {result.stderr}")
814
+ return False
815
+
816
+ except Exception as e:
817
+ print(f"Playwright ์„ค์น˜ ์ค‘ ์˜ค๋ฅ˜: {e}")
818
+ return False
819
+
820
  if __name__ == "__main__":
821
  print("=" * 50)
822
+ print("Playwright Google Trends ๋ถ„์„๊ธฐ ์‹œ์ž‘")
823
  print("=" * 50)
824
 
825
+ # Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜ (์ตœ์ดˆ 1ํšŒ)
826
+ if IS_HUGGINGFACE:
827
+ print("Hugging Face Space ํ™˜๊ฒฝ: Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜ ์ค‘...")
828
+ if not install_playwright_browsers():
829
+ print("๊ฒฝ๊ณ : Playwright ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜ ์‹คํŒจ, ๋Ÿฐํƒ€์ž„ ์„ค์น˜ ์‹œ๋„๋ฉ๋‹ˆ๋‹ค")
830
+ else:
831
+ print("๋กœ์ปฌ ํ™˜๊ฒฝ: Playwright ๋ธŒ๋ผ์šฐ์ € ํ™•์ธ")
832
+ install_playwright_browsers()
833
+
834
  if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
835
  print("ANTHROPIC_API_KEY ๋ฏธ์„ค์ • - ์Šคํฌ๋ฆฐ์ƒท๋งŒ ์บก์ฒ˜")
836
  else:
837
  print("Claude API ํ‚ค ํ™•์ธ๋จ")
838
 
839
+ print("Playwright Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
840
+ app = create_playwright_interface()
841
 
842
  if os.getenv("SPACE_ID"):
843
  print("Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰")
 
856
  show_api=False,
857
  share=False,
858
  inbrowser=True
859
+ )
860
+
861
+
862
+
863
+
864
+
865
+
866
+
867
+
868
+ # import os
869
+ # import time
870
+ # import base64
871
+ # import json
872
+ # import traceback
873
+ # import threading
874
+ # from typing import Tuple, Optional
875
+ # import gradio as gr
876
+ # import anthropic
877
+ # from selenium import webdriver
878
+ # from selenium.webdriver.chrome.options import Options
879
+ # from selenium.webdriver.common.by import By
880
+ # from selenium.webdriver.support.ui import WebDriverWait
881
+ # from selenium.webdriver.support import expected_conditions as EC
882
+ # from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException
883
+ # from concurrent.futures import ThreadPoolExecutor
884
+ # from datetime import datetime
885
+
886
+ # # Hugging Face Space ํ™˜๊ฒฝ ํ™•์ธ
887
+ # IS_HUGGINGFACE = os.getenv("SPACE_ID") is not None
888
+ # print(f'IS_HUGGINGFACE = {IS_HUGGINGFACE}')
889
+
890
+ # # Claude API ์„ค์ •
891
+ # ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
892
+
893
+ # if not ANTHROPIC_API_KEY:
894
+ # print("ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ, Secret ํ™•์ธ ํ•„์š”")
895
+ # else:
896
+ # print("Claude API ํ‚ค ํ™•์ธ ์™„๋ฃŒ")
897
+
898
+ # def create_claude_client():
899
+ # try:
900
+ # client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
901
+ # print("Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์„ฑ๊ณต")
902
+ # return client
903
+ # except Exception as e:
904
+ # print(f"Claude API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ์‹คํŒจ: {e}")
905
+ # return None
906
+
907
+ # class PersistentGoogleTrendsAutomator:
908
+ # """๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ณ„์† ์œ ์ง€ํ•˜๋Š” Google Trends ์ž๋™ํ™” ํด๋ž˜์Šค"""
909
+
910
+ # _instance = None
911
+ # _lock = threading.Lock()
912
+
913
+ # def __new__(cls):
914
+ # if cls._instance is None:
915
+ # with cls._lock:
916
+ # if cls._instance is None:
917
+ # cls._instance = super(PersistentGoogleTrendsAutomator, cls).__new__(cls)
918
+ # cls._instance._initialized = False
919
+ # return cls._instance
920
+
921
+ # def __init__(self):
922
+ # if not self._initialized:
923
+ # self.driver = None
924
+ # self.is_browser_ready = False
925
+ # self.last_used = time.time()
926
+ # self.browser_lock = threading.Lock()
927
+ # self.initialization_lock = threading.Lock()
928
+ # self._initialized = True
929
+ # print("PersistentGoogleTrendsAutomator ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ")
930
+
931
+ # def setup_driver(self):
932
+ # """๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • (์ตœ์ดˆ 1ํšŒ๋งŒ)"""
933
+ # with self.initialization_lock:
934
+ # if self.driver is not None and self.is_browser_ready:
935
+ # print("๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ")
936
+ # return True
937
+
938
+ # print("์ƒˆ ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์ค‘...")
939
+
940
+ # options = Options()
941
+
942
+ # # Hugging Face Space ํ™˜๊ฒฝ ์„ค์ •
943
+ # if IS_HUGGINGFACE:
944
+ # print("Hugging Face Space ํ™˜๊ฒฝ, ํ—ค๋“œ๋ฆฌ์Šค ๋ชจ๋“œ")
945
+ # options.add_argument("--headless=new")
946
+ # options.add_argument("--no-sandbox")
947
+ # options.add_argument("--disable-dev-shm-usage")
948
+ # options.add_argument("--disable-gpu")
949
+ # options.add_argument("--disable-software-rasterizer")
950
+ # options.add_argument("--remote-debugging-port=9222")
951
+ # else:
952
+ # options.add_argument("--headless")
953
+
954
+ # # ์„ฑ๋Šฅ ์ตœ์ ํ™” ์„ค์ •
955
+ # options.add_argument("--window-size=1280,720")
956
+ # options.add_argument("--disable-blink-features=AutomationControlled")
957
+ # options.add_argument("--disable-extensions")
958
+ # options.add_argument("--disable-web-security")
959
+ # options.add_argument("--allow-running-insecure-content")
960
+ # options.add_argument("--disable-background-timer-throttling")
961
+ # options.add_argument("--disable-backgrounding-occluded-windows")
962
+ # options.add_argument("--disable-renderer-backgrounding")
963
+ # options.add_argument("--disable-features=TranslateUI")
964
+ # options.add_argument("--disable-ipc-flooding-protection")
965
+
966
+ # # ๋„คํŠธ์›Œํฌ ์ตœ์ ํ™”
967
+ # options.add_argument("--aggressive-cache-discard")
968
+ # options.add_argument("--disable-background-networking")
969
+
970
+ # # ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”
971
+ # options.add_argument("--memory-pressure-off")
972
+ # options.add_argument("--max_old_space_size=2048")
973
+
974
+ # # ์ด๋ฏธ์ง€/CSS ๋น„ํ™œ์„ฑํ™”
975
+ # prefs = {
976
+ # "profile.managed_default_content_settings.images": 2,
977
+ # "profile.default_content_setting_values.notifications": 2,
978
+ # }
979
+ # options.add_experimental_option("prefs", prefs)
980
+
981
+ # # ๋ด‡ ๊ฐ์ง€ ํšŒํ”ผ
982
+ # 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")
983
+ # options.add_experimental_option("excludeSwitches", ["enable-automation"])
984
+ # options.add_experimental_option('useAutomationExtension', False)
985
+
986
+ # # ์–ธ์–ด ์„ค์ •
987
+ # options.add_argument("--lang=ko")
988
+ # options.add_argument("--accept-lang=ko-KR,ko;q=0.9,en;q=0.8")
989
+
990
+ # try:
991
+ # # ๋“œ๋ผ์ด๋ฒ„ ์ƒ์„ฑ
992
+ # if IS_HUGGINGFACE:
993
+ # try:
994
+ # from webdriver_manager.chrome import ChromeDriverManager
995
+ # from selenium.webdriver.chrome.service import Service
996
+
997
+ # service = Service(ChromeDriverManager().install())
998
+ # self.driver = webdriver.Chrome(service=service, options=options)
999
+ # print("ChromeDriver ์ž๋™ ์„ค์น˜ ์„ฑ๊ณต")
1000
+ # except Exception as e:
1001
+ # print(f"webdriver-manager ์‹คํŒจ: {e}")
1002
+ # self.driver = webdriver.Chrome(options=options)
1003
+ # print("์‹œ์Šคํ…œ ChromeDriver ์‚ฌ์šฉ")
1004
+ # else:
1005
+ # try:
1006
+ # from webdriver_manager.chrome import ChromeDriverManager
1007
+ # from selenium.webdriver.chrome.service import Service
1008
+
1009
+ # service = Service(ChromeDriverManager().install())
1010
+ # self.driver = webdriver.Chrome(service=service, options=options)
1011
+ # except:
1012
+ # self.driver = webdriver.Chrome(options=options)
1013
+
1014
+ # # ํƒ€์ž„์•„์›ƒ ์„ค์ •
1015
+ # self.driver.set_page_load_timeout(20)
1016
+ # self.driver.implicitly_wait(3)
1017
+
1018
+ # # Google Trends ํŽ˜์ด์ง€ ์ตœ์ดˆ ๋กœ๋”ฉ
1019
+ # print("Google Trends ํŽ˜์ด์ง€ ์ตœ์ดˆ ๋กœ๋”ฉ ์ค‘...")
1020
+ # self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
1021
+
1022
+ # # ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์™„๋ฃŒ ๋Œ€๊ธฐ
1023
+ # WebDriverWait(self.driver, 10).until(
1024
+ # EC.presence_of_element_located((By.TAG_NAME, "body"))
1025
+ # )
1026
+
1027
+ # # ๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ
1028
+ # try:
1029
+ # self.driver.execute_script("""
1030
+ # Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
1031
+ # Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
1032
+ # Object.defineProperty(navigator, 'languages', {get: () => ['ko-KR', 'ko', 'en']});
1033
+ # """)
1034
+ # except Exception as script_error:
1035
+ # print(f"๋ด‡ ๊ฐ์ง€ ๋ฐฉ์ง€ ์Šคํฌ๋ฆฝํŠธ ์‹คํŒจ (๋ฌด์‹œ): {script_error}")
1036
+
1037
+ # self.is_browser_ready = True
1038
+ # self.last_used = time.time()
1039
+ # print("๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์™„๋ฃŒ ๋ฐ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์„ฑ๊ณต")
1040
+ # return True
1041
+
1042
+ # except Exception as e:
1043
+ # print(f"๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์‹คํŒจ: {e}")
1044
+ # self.cleanup_driver()
1045
+ # return False
1046
+
1047
+ # def cleanup_driver(self):
1048
+ # """๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
1049
+ # try:
1050
+ # if self.driver:
1051
+ # self.driver.quit()
1052
+ # print("๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ")
1053
+ # except:
1054
+ # pass
1055
+ # finally:
1056
+ # self.driver = None
1057
+ # self.is_browser_ready = False
1058
+
1059
+ # def check_browser_health(self):
1060
+ # """๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ ๋ฐ ํ•„์š”์‹œ ๋ณต๊ตฌ"""
1061
+ # try:
1062
+ # if not self.driver:
1063
+ # return False
1064
+
1065
+ # # ๊ฐ„๋‹จํ•œ ์ƒํƒœ ์ฒดํฌ
1066
+ # current_url = self.driver.current_url
1067
+ # if "trends.google.com" not in current_url:
1068
+ # print("๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž˜๋ชป๋œ ํŽ˜์ด์ง€์— ์žˆ์Œ, ๋ณต๊ตฌ ์ค‘...")
1069
+ # self.driver.get("https://trends.google.com/trending?geo=KR&hl=ko")
1070
+ # time.sleep(2)
1071
+
1072
+ # return True
1073
+
1074
+ # except WebDriverException as e:
1075
+ # print(f"๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ ์‹คํŒจ: {e}")
1076
+ # return False
1077
+
1078
+ # def refresh_and_wait(self):
1079
+ # """ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ๋ฐ ๋Œ€๊ธฐ (๋น ๋ฅธ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ )"""
1080
+ # try:
1081
+ # print("ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์œผ๋กœ ์ตœ์‹  ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ...")
1082
+ # self.driver.refresh()
1083
+
1084
+ # # ์ตœ์†Œํ•œ์˜ ๋กœ๋”ฉ ๋Œ€๊ธฐ
1085
+ # time.sleep(2)
1086
+
1087
+ # # ํŽ˜์ด์ง€ ์ค€๋น„ ํ™•์ธ
1088
+ # WebDriverWait(self.driver, 5).until(
1089
+ # EC.presence_of_element_located((By.TAG_NAME, "button"))
1090
+ # )
1091
+
1092
+ # return True
1093
+
1094
+ # except Exception as e:
1095
+ # print(f"ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ: {e}")
1096
+ # return False
1097
+
1098
+ # def safe_find_element(self, selectors: list, timeout: int = 3):
1099
+ # """์•ˆ์ „ํ•œ ์š”์†Œ ์ฐพ๊ธฐ"""
1100
+ # for selector_type, selector in selectors:
1101
+ # try:
1102
+ # if selector_type == "xpath":
1103
+ # element = WebDriverWait(self.driver, timeout).until(
1104
+ # EC.presence_of_element_located((By.XPATH, selector))
1105
+ # )
1106
+ # elif selector_type == "css":
1107
+ # element = WebDriverWait(self.driver, timeout).until(
1108
+ # EC.presence_of_element_located((By.CSS_SELECTOR, selector))
1109
+ # )
1110
+ # elif selector_type == "text":
1111
+ # element = WebDriverWait(self.driver, timeout).until(
1112
+ # EC.presence_of_element_located((By.XPATH, f"//*[contains(text(), '{selector}')]"))
1113
+ # )
1114
+
1115
+ # if element:
1116
+ # return element
1117
+ # except:
1118
+ # continue
1119
+ # return None
1120
+
1121
+ # def safe_click(self, selectors: list, timeout: int = 3) -> bool:
1122
+ # """์•ˆ์ „ํ•œ ํด๋ฆญ"""
1123
+ # element = self.safe_find_element(selectors, timeout)
1124
+ # if element:
1125
+ # try:
1126
+ # self.driver.execute_script("arguments[0].click();", element)
1127
+ # return True
1128
+ # except:
1129
+ # try:
1130
+ # element.click()
1131
+ # return True
1132
+ # except:
1133
+ # return False
1134
+ # return False
1135
+
1136
+ # def change_settings_fast(self, region: str, period: str, category: str, progress_callback=None) -> dict:
1137
+ # """๋น ๋ฅธ ์„ค์ • ๋ณ€๊ฒฝ (๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ํ™œ์šฉ)"""
1138
+ # results = {
1139
+ # 'region': False,
1140
+ # 'period': False,
1141
+ # 'category': False,
1142
+ # 'errors': []
1143
+ # }
1144
+
1145
+ # try:
1146
+ # # ์„ค์ • ๋ณ€๊ฒฝ ์ „ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์œผ๋กœ ์ตœ์‹  ์ƒํƒœ ํ™•๋ณด
1147
+ # self.refresh_and_wait()
1148
+
1149
+ # if region != "๋Œ€ํ•œ๋ฏผ๊ตญ":
1150
+ # if progress_callback:
1151
+ # progress_callback(f"์ง€์—ญ์„ {region}์œผ๋กœ ๋ณ€๊ฒฝ ์ค‘...")
1152
+ # results['region'] = self.change_region(region)
1153
+ # time.sleep(0.5)
1154
+ # else:
1155
+ # results['region'] = True
1156
+
1157
+ # if period != "์ง€๋‚œ 24์‹œ๊ฐ„":
1158
+ # if progress_callback:
1159
+ # progress_callback(f"๊ธฐ๊ฐ„์„ {period}๋กœ ๋ณ€๊ฒฝ ์ค‘...")
1160
+ # results['period'] = self.change_time_period(period)
1161
+ # time.sleep(0.5)
1162
+ # else:
1163
+ # results['period'] = True
1164
+
1165
+ # if category != "๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ":
1166
+ # if progress_callback:
1167
+ # progress_callback(f"์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ {category}๋กœ ๋ณ€๊ฒฝ ์ค‘...")
1168
+ # results['category'] = self.change_category(category)
1169
+ # time.sleep(0.5)
1170
+ # else:
1171
+ # results['category'] = True
1172
+
1173
+ # except Exception as e:
1174
+ # results['errors'].append(str(e))
1175
+
1176
+ # return results
1177
+
1178
+ # def change_region(self, target_region: str) -> bool:
1179
+ # """์ง€์—ญ ๋ณ€๊ฒฝ"""
1180
+ # try:
1181
+ # region_selectors = [
1182
+ # ("xpath", "//button[@aria-label='๋Œ€ํ•œ๋ฏผ๊ตญ, ์œ„์น˜ ์„ ํƒ']"),
1183
+ # ("xpath", "//button[contains(@aria-label, '์œ„์น˜ ์„ ํƒ')]"),
1184
+ # ("xpath", "//span[contains(text(), '๋Œ€ํ•œ๋ฏผ๊ตญ')]//parent::button"),
1185
+ # ]
1186
+
1187
+ # if not self.safe_click(region_selectors):
1188
+ # return False
1189
+
1190
+ # time.sleep(1)
1191
+
1192
+ # region_mapping = {
1193
+ # "์ „์„ธ๊ณ„": ["์ „ ์„ธ๊ณ„", "Worldwide"],
1194
+ # "๋ฏธ๊ตญ": ["๋ฏธ๊ตญ", "United States"],
1195
+ # "์ผ๋ณธ": ["์ผ๋ณธ", "Japan"],
1196
+ # "์ค‘๊ตญ": ["์ค‘๊ตญ", "China"]
1197
+ # }
1198
+
1199
+ # if target_region in region_mapping:
1200
+ # for region_text in region_mapping[target_region]:
1201
+ # region_option_selectors = [
1202
+ # ("xpath", f"//span[contains(text(), '{region_text}')]"),
1203
+ # ("xpath", f"//*[contains(text(), '{region_text}')]"),
1204
+ # ]
1205
+
1206
+ # if self.safe_click(region_option_selectors, timeout=2):
1207
+ # return True
1208
+
1209
+ # return False
1210
+
1211
+ # except Exception as e:
1212
+ # print(f"์ง€์—ญ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
1213
+ # return False
1214
+
1215
+ # def change_time_period(self, target_period: str) -> bool:
1216
+ # """๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ"""
1217
+ # try:
1218
+ # period_selectors = [
1219
+ # ("xpath", "//button[contains(@aria-label, '๊ธฐ๊ฐ„ ์„ ํƒ')]"),
1220
+ # ("xpath", "//span[contains(text(), '์ง€๋‚œ 24์‹œ๊ฐ„')]//parent::button"),
1221
+ # ]
1222
+
1223
+ # if not self.safe_click(period_selectors):
1224
+ # return False
1225
+
1226
+ # time.sleep(1)
1227
+
1228
+ # period_mapping = {
1229
+ # "์ง€๋‚œ 1์‹œ๊ฐ„": ["์ง€๋‚œ 1์‹œ๊ฐ„"],
1230
+ # "์ง€๋‚œ 4์‹œ๊ฐ„": ["์ง€๋‚œ 4์‹œ๊ฐ„"],
1231
+ # "์ง€๋‚œ 1์ผ": ["์ง€๋‚œ 1์ผ"],
1232
+ # "์ง€๋‚œ 7์ผ": ["์ง€๋‚œ 7์ผ", "์ง€๋‚œ ์ฃผ"]
1233
+ # }
1234
+
1235
+ # if target_period in period_mapping:
1236
+ # for period_text in period_mapping[target_period]:
1237
+ # period_option_selectors = [
1238
+ # ("xpath", f"//span[contains(text(), '{period_text}')]"),
1239
+ # ("xpath", f"//*[contains(text(), '{period_text}')]"),
1240
+ # ]
1241
+
1242
+ # if self.safe_click(period_option_selectors, timeout=2):
1243
+ # return True
1244
+
1245
+ # return False
1246
+
1247
+ # except Exception as e:
1248
+ # print(f"๊ธฐ๊ฐ„ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
1249
+ # return False
1250
+
1251
+ # def change_category(self, target_category: str) -> bool:
1252
+ # """์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ"""
1253
+ # try:
1254
+ # category_selectors = [
1255
+ # ("xpath", "//button[contains(@aria-label, '์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ')]"),
1256
+ # ("xpath", "//span[contains(text(), '๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ')]//parent::button"),
1257
+ # ]
1258
+
1259
+ # if not self.safe_click(category_selectors):
1260
+ # return False
1261
+
1262
+ # time.sleep(1)
1263
+
1264
+ # category_mapping = {
1265
+ # "๊ฒŒ์ž„": ["๊ฒŒ์ž„"],
1266
+ # "๊ฑด๊ฐ•": ["๊ฑด๊ฐ•"],
1267
+ # "๊ธฐ์ˆ ": ["์ปดํ“จํ„ฐ ๋ฐ ์ „์ž์ œํ’ˆ", "๊ธฐ์ˆ "],
1268
+ # "์Šคํฌ์ธ ": ["์Šคํฌ์ธ "],
1269
+ # "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ": ["์˜ˆ์ˆ  ๋ฐ ์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ"],
1270
+ # "๋‰ด์Šค": ["๋‰ด์Šค"],
1271
+ # "๋น„์ฆˆ๋‹ˆ์Šค": ["๋น„์ฆˆ๋‹ˆ์Šค"]
1272
+ # }
1273
+
1274
+ # if target_category in category_mapping:
1275
+ # for category_text in category_mapping[target_category]:
1276
+ # category_option_selectors = [
1277
+ # ("xpath", f"//span[contains(text(), '{category_text}')]"),
1278
+ # ("xpath", f"//*[contains(text(), '{category_text}')]"),
1279
+ # ]
1280
+
1281
+ # if self.safe_click(category_option_selectors, timeout=2):
1282
+ # return True
1283
+
1284
+ # return False
1285
+
1286
+ # except Exception as e:
1287
+ # print(f"์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€๊ฒฝ ์‹คํŒจ: {e}")
1288
+ # return False
1289
+
1290
+ # def capture_trends_fast(self, region: str, period: str, category: str, progress_callback=None) -> Tuple[Optional[str], bool, str]:
1291
+ # """๋น ๋ฅธ ํŠธ๋ Œ๋“œ ์บก์ฒ˜ (๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ)"""
1292
+
1293
+ # with self.browser_lock: # ๋™์‹œ ์ ‘๊ทผ ๋ฐฉ์ง€
1294
+ # error_msg = ""
1295
+
1296
+ # try:
1297
+ # # ๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ ํ™•์ธ
1298
+ # if not self.is_browser_ready or not self.check_browser_health():
1299
+ # if progress_callback:
1300
+ # progress_callback("๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์ค‘... (์ตœ์ดˆ ์‹คํ–‰์‹œ๋งŒ)")
1301
+
1302
+ # if not self.setup_driver():
1303
+ # return None, False, "๋ธŒ๋ผ์šฐ์ € ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • ์‹คํŒจ"
1304
+ # else:
1305
+ # if progress_callback:
1306
+ # progress_callback("๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ์ค‘...")
1307
+
1308
+ # # ์„ค์ • ๋ณ€๊ฒฝ
1309
+ # if progress_callback:
1310
+ # progress_callback("์„ค์ • ๋ณ€๊ฒฝ ๋ฐ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ์ค‘...")
1311
+
1312
+ # settings_result = self.change_settings_fast(region, period, category, progress_callback)
1313
+
1314
+ # # ๊ฒฐ๊ณผ ํ™•์ธ
1315
+ # changes_made = []
1316
+ # if settings_result['region']:
1317
+ # changes_made.append(f"์ง€์—ญ: {region}")
1318
+ # if settings_result['period']:
1319
+ # changes_made.append(f"๊ธฐ๊ฐ„: {period}")
1320
+ # if settings_result['category']:
1321
+ # changes_made.append(f"์นดํ…Œ๊ณ ๋ฆฌ: {category}")
1322
+
1323
+ # if settings_result['errors']:
1324
+ # error_msg = "\n".join(settings_result['errors'])
1325
+
1326
+ # if progress_callback:
1327
+ # progress_callback("์ตœ์‹  ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ, ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์ค‘...")
1328
+
1329
+ # # ์ตœ์ข… ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋Œ€๊ธฐ
1330
+ # time.sleep(1)
1331
+
1332
+ # # ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜
1333
+ # screenshot = self.driver.get_screenshot_as_png()
1334
+ # screenshot_b64 = base64.b64encode(screenshot).decode()
1335
+
1336
+ # # ์‚ฌ์šฉ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
1337
+ # self.last_used = time.time()
1338
+
1339
+ # success_msg = f"์„ค์ • ์™„๋ฃŒ: {', '.join(changes_made) if changes_made else '๊ธฐ๋ณธ ์„ค์ • ์‚ฌ์šฉ'}"
1340
+
1341
+ # return screenshot_b64, True, success_msg + ("\n" + error_msg if error_msg else "")
1342
+
1343
+ # except Exception as e:
1344
+ # error_msg = f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
1345
+ # # ์˜ค๋ฅ˜ ๋ฐœ์ƒ์‹œ ๋ธŒ๋ผ์šฐ์ € ์žฌ์‹œ์ž‘ ์‹œ๋„
1346
+ # print(f"์˜ค๋ฅ˜๋กœ ์ธํ•œ ๋ธŒ๋ผ์šฐ์ € ์žฌ์‹œ์ž‘: {e}")
1347
+ # self.cleanup_driver()
1348
+ # return None, False, error_msg
1349
+
1350
+ # # ๊ธ€๋กœ๋ฒŒ ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค (์‹ฑ๊ธ€ํ†ค)
1351
+ # automator_instance = None
1352
+
1353
+ # def get_automator():
1354
+ # """์ž๋™ํ™” ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ"""
1355
+ # global automator_instance
1356
+ # if automator_instance is None:
1357
+ # automator_instance = PersistentGoogleTrendsAutomator()
1358
+ # return automator_instance
1359
+
1360
+ # def analyze_with_claude(screenshot_b64: str, region: str, period: str, category: str) -> str:
1361
+ # """Claude API๋กœ ์Šคํฌ๋ฆฐ์ƒท ๋ถ„์„"""
1362
+ # try:
1363
+ # if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
1364
+ # return """
1365
+ # <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
1366
+ # <h3>API ํ‚ค ์˜ค๋ฅ˜</h3>
1367
+ # <p><strong>ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!</strong></p>
1368
+ # </div>
1369
+ # """
1370
+
1371
+ # claude_client = create_claude_client()
1372
+
1373
+ # if claude_client is None:
1374
+ # return """
1375
+ # <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
1376
+ # <h3>Claude API ์—ฐ๊ฒฐ ์‹คํŒจ</h3>
1377
+ # <p>API ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.</p>
1378
+ # </div>
1379
+ # """
1380
+
1381
+ # prompt = f"""
1382
+ # ์ด Google Trends ์Šคํฌ๋ฆฐ์ƒท์„ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
1383
+ # ์„ค์ •: {region} | {period} | {category}
1384
+
1385
+ # ๋‹ค์Œ์„ HTML ํ‘œ ํ˜•ํƒœ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ์ •๋ฆฌํ•ด์ฃผ์„ธ์š”:
1386
+ # 1. ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ ๊ฒ€์ƒ‰์–ด ์ƒ์œ„ 10๊ฐœ (์ˆœ์œ„, ๊ฒ€์ƒ‰์–ด, ๊ฒ€์ƒ‰๋Ÿ‰, ์ƒ์Šน๋ฅ )
1387
+ # 2. ์ฃผ์š” ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ 3-5๊ฐœ์˜ ํŠน์ง• ์„ค๋ช…
1388
+ # 3. ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํŠน์ด์‚ฌํ•ญ (์žˆ๋Š” ๊ฒฝ์šฐ)
1389
+
1390
+ # HTML ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ , ์‹œ๊ฐ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”.
1391
+ # ํ•œ๊ธ€ ํ…์ŠคํŠธ๋ฅผ ์ •ํ™•ํžˆ ์ฝ์–ด์„œ ๋ถ„์„ํ•ด์ฃผ์„ธ์š”.
1392
+ # """
1393
+
1394
+ # message = claude_client.messages.create(
1395
+ # model="claude-3-5-sonnet-20241022",
1396
+ # max_tokens=1000,
1397
+ # messages=[
1398
+ # {
1399
+ # "role": "user",
1400
+ # "content": [
1401
+ # {
1402
+ # "type": "text",
1403
+ # "text": prompt
1404
+ # },
1405
+ # {
1406
+ # "type": "image",
1407
+ # "source": {
1408
+ # "type": "base64",
1409
+ # "media_type": "image/png",
1410
+ # "data": screenshot_b64
1411
+ # }
1412
+ # }
1413
+ # ]
1414
+ # }
1415
+ # ]
1416
+ # )
1417
+
1418
+ # return message.content[0].text
1419
+
1420
+ # except Exception as e:
1421
+ # return f"""
1422
+ # <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
1423
+ # <h3>Claude API ๋ถ„์„ ์‹คํŒจ</h3>
1424
+ # <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
1425
+ # </div>
1426
+ # """
1427
+
1428
+ # def persistent_browser_analysis(region: str, period: str, category: str):
1429
+ # """๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„ ํ•จ์ˆ˜"""
1430
+
1431
+ # start_time = time.time()
1432
+
1433
+ # yield f"""
1434
+ # <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
1435
+ # <h3>๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„</h3>
1436
+ # <p><strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
1437
+ # <p>์ฒซ ์‹คํ–‰: 15-20์ดˆ | ์ดํ›„ ์‹คํ–‰: 5-10์ดˆ</p>
1438
+ # </div>
1439
+ # """
1440
+
1441
+ # progress_status = {"current": ""}
1442
+
1443
+ # def progress_callback(message):
1444
+ # progress_status["current"] = message
1445
+
1446
+ # try:
1447
+ # # API ํ‚ค ํ™•์ธ
1448
+ # if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
1449
+ # yield """
1450
+ # <div style="color: orange; padding: 20px; border: 1px solid orange; border-radius: 5px;">
1451
+ # <h3>API ํ‚ค ๋ˆ„๋ฝ - ์Šคํฌ๋ฆฐ์ƒท๋งŒ ์บก์ฒ˜</h3>
1452
+ # <p><strong>ANTHROPIC_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค!</strong></p>
1453
+ # </div>
1454
+ # """
1455
+
1456
+ # # ์ž๋™ํ™” ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
1457
+ # automator = get_automator()
1458
+
1459
+ # # ์ฒซ ์‹คํ–‰์ธ์ง€ ํ™•์ธ
1460
+ # is_first_run = not automator.is_browser_ready
1461
+
1462
+ # yield f"""
1463
+ # <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
1464
+ # <h4>1๋‹จ๊ณ„: {"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”" if is_first_run else "๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"}</h4>
1465
+ # <p>{"์ตœ์ดˆ ์‹คํ–‰์ด๋ฏ€๋กœ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ƒˆ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค" if is_first_run else "์ด๋ฏธ ์‹คํ–‰ ์ค‘์ธ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค"}</p>
1466
+ # </div>
1467
+ # """
1468
+
1469
+ # # ๋ธŒ๋ผ์šฐ์ € ์ž‘์—…์„ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰
1470
+ # with ThreadPoolExecutor(max_workers=1) as executor:
1471
+ # future = executor.submit(
1472
+ # automator.capture_trends_fast,
1473
+ # region, period, category, progress_callback
1474
+ # )
1475
+
1476
+ # # ์ง„ํ–‰ ์ƒํ™ฉ ์—…๋ฐ์ดํŠธ
1477
+ # while not future.done():
1478
+ # current_progress = progress_status["current"]
1479
+ # if current_progress:
1480
+ # elapsed = time.time() - start_time
1481
+ # yield f"""
1482
+ # <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
1483
+ # <h4>1๋‹จ๊ณ„: {"๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”" if is_first_run else "๊ธฐ์กด ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"}</h4>
1484
+ # <p>{current_progress}</p>
1485
+ # <p>๊ฒฝ๊ณผ ์‹œ๊ฐ„: {elapsed:.1f}์ดˆ</p>
1486
+ # </div>
1487
+ # """
1488
+ # time.sleep(0.5)
1489
+
1490
+ # # ๊ฒฐ๊ณผ ๋ฐ›๊ธฐ
1491
+ # screenshot_b64, success, status_msg = future.result()
1492
+
1493
+ # browser_time = time.time() - start_time
1494
+
1495
+ # if not success:
1496
+ # yield f"""
1497
+ # <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
1498
+ # <h3>Google Trends ์บก์ฒ˜ ์‹คํŒจ</h3>
1499
+ # <pre style="background-color: #fff5f5; padding: 15px; border-radius: 5px; margin: 15px 0;">
1500
+ # {status_msg}
1501
+ # </pre>
1502
+ # </div>
1503
+ # """
1504
+ # return
1505
+
1506
+ # # 2๋‹จ๊ณ„: AI ๋ถ„์„
1507
+ # yield f"""
1508
+ # <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
1509
+ # <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์„ฑ๊ณต ({browser_time:.1f}์ดˆ)</h4>
1510
+ # <p>{status_msg}</p>
1511
+ # </div>
1512
+
1513
+ # <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
1514
+ # <h4>2๋‹จ๊ณ„: Claude AI ๋ถ„์„ ์ค‘...</h4>
1515
+ # <p>ํŠธ๋ Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค</p>
1516
+ # </div>
1517
+ # """
1518
+
1519
+ # # AI ๋ถ„์„ ์‹คํ–‰
1520
+ # ai_start_time = time.time()
1521
+
1522
+ # with ThreadPoolExecutor(max_workers=1) as executor:
1523
+ # ai_future = executor.submit(
1524
+ # analyze_with_claude,
1525
+ # screenshot_b64, region, period, category
1526
+ # )
1527
+
1528
+ # while not ai_future.done():
1529
+ # ai_elapsed = time.time() - ai_start_time
1530
+ # total_elapsed = time.time() - start_time
1531
+ # yield f"""
1532
+ # <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
1533
+ # <h4>1๋‹จ๊ณ„ ์™„๋ฃŒ: ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์„ฑ๊ณต ({browser_time:.1f}์ดˆ)</h4>
1534
+ # <p>{status_msg}</p>
1535
+ # </div>
1536
+
1537
+ # <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%); color: white; border-radius: 10px; margin-bottom: 15px;">
1538
+ # <h4>2๋‹จ๊ณ„: Claude AI ๋ถ„์„ ์ค‘... ({ai_elapsed:.1f}์ดˆ)</h4>
1539
+ # <p>์ด ๊ฒฝ๊ณผ ์‹œ๊ฐ„: {total_elapsed:.1f}์ดˆ</p>
1540
+ # </div>
1541
+ # """
1542
+ # time.sleep(0.5)
1543
+
1544
+ # analysis_result = ai_future.result()
1545
+
1546
+ # total_time = time.time() - start_time
1547
+
1548
+ # # ์ตœ์ข… ๊ฒฐ๊ณผ
1549
+ # yield f"""
1550
+ # <div style="text-align: center; padding: 15px; background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
1551
+ # <h3>๋ถ„์„ ์™„๋ฃŒ ({total_time:.1f}์ดˆ)</h3>
1552
+ # <p><strong>์„ฑ๋Šฅ:</strong> {"์ฒซ ์‹คํ–‰" if is_first_run else "๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ"} | <strong>์„ค์ •:</strong> {region} | {period} | {category}</p>
1553
+ # </div>
1554
+
1555
+ # {analysis_result}
1556
+
1557
+ # <div style="margin-top: 20px; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center;">
1558
+ # <p style="margin: 0; font-size: 14px;">
1559
+ # <strong>๋‹ค์Œ ๋ถ„์„์€ ๋”์šฑ ๋น ๋ฆ…๋‹ˆ๋‹ค!</strong><br>
1560
+ # ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ด๋ฏ€๋กœ 5-10์ดˆ๋งŒ์— ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค
1561
+ # </p>
1562
+ # </div>
1563
+
1564
+ # <div style="margin-top: 15px; padding: 10px; background-color: #f9f9f9; border-radius: 5px; font-size: 12px; color: #666; text-align: center;">
1565
+ # <p style="margin: 5px 0;"><strong>๋ธŒ๋ผ์šฐ์ € ์ƒํƒœ:</strong> {"์ƒˆ๋กœ ์‹œ์ž‘๋จ" if is_first_run else "์žฌ์‚ฌ์šฉ๋จ"}</p>
1566
+ # <p style="margin: 5px 0;"><strong>๋ถ„์„ ์‹œ๊ฐ„:</strong> {total_time:.1f}์ดˆ</p>
1567
+ # <p style="margin: 5px 0;"><strong>๋‹ค์Œ ์˜ˆ์ƒ ์‹œ๊ฐ„:</strong> 5-10์ดˆ</p>
1568
+ # </div>
1569
+ # """
1570
+
1571
+ # print(f"๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„ ์™„๋ฃŒ: {total_time:.1f}์ดˆ")
1572
+
1573
+ # except Exception as e:
1574
+ # error_details = traceback.format_exc()
1575
+ # print(f"๋ถ„์„ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {e}")
1576
+
1577
+ # yield f"""
1578
+ # <div style="color: red; padding: 20px; border: 1px solid red; border-radius: 5px;">
1579
+ # <h3>์˜ค๋ฅ˜ ๋ฐœ์ƒ</h3>
1580
+ # <p><strong>์˜ค๋ฅ˜:</strong> {str(e)}</p>
1581
+ # <details>
1582
+ # <summary>์ƒ์„ธ ์˜ค๋ฅ˜ ์ •๋ณด</summary>
1583
+ # <pre style="background-color: #f5f5f5; padding: 10px; margin-top: 10px; overflow-x: auto; font-size: 11px;">
1584
+ # {error_details}
1585
+ # </pre>
1586
+ # </details>
1587
+ # <p><strong>ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:</strong> ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.</p>
1588
+ # </div>
1589
+ # """
1590
+
1591
+ # def create_persistent_interface():
1592
+ # """๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Gradio ์ธํ„ฐํŽ˜์ด์Šค"""
1593
+
1594
+ # with gr.Blocks(
1595
+ # theme=gr.themes.Soft(),
1596
+ # title="๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„๊ธฐ",
1597
+ # css="""
1598
+ # .gradio-container {
1599
+ # max-width: 1200px !important;
1600
+ # }
1601
+ # .output-html {
1602
+ # max-height: none !important;
1603
+ # }
1604
+ # """
1605
+ # ) as interface:
1606
+
1607
+ # gr.HTML("""
1608
+ # <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 20px;">
1609
+ # <h1>๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„๊ธฐ</h1>
1610
+ # <p><strong>ํ˜์‹ ์  ์„ฑ๋Šฅ ๊ฐœ์„ :</strong> ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ณ„์† ์ผœ๋‘๊ณ  ์žฌ์‚ฌ์šฉํ•˜์—ฌ ์†๋„ ํ–ฅ์ƒ</p>
1611
+ # <p style="font-size: 14px; opacity: 0.9;">์ฒซ ์‹คํ–‰: 15-20์ดˆ | ์ดํ›„ ์‹คํ–‰: 5-10์ดˆ</p>
1612
+ # </div>
1613
+ # """)
1614
+
1615
+ # with gr.Row():
1616
+ # with gr.Column(scale=1):
1617
+ # region_input = gr.Dropdown(
1618
+ # choices=["๋Œ€ํ•œ๋ฏผ๊ตญ", "์ „์„ธ๊ณ„", "๋ฏธ๊ตญ", "์ผ๋ณธ", "์ค‘๊ตญ"],
1619
+ # value="๋Œ€ํ•œ๋ฏผ๊ตญ",
1620
+ # label="์ง€์—ญ ์„ ํƒ"
1621
+ # )
1622
+
1623
+ # with gr.Column(scale=1):
1624
+ # period_input = gr.Dropdown(
1625
+ # choices=["์ง€๋‚œ 24์‹œ๊ฐ„", "์ง€๋‚œ 1์‹œ๊ฐ„", "์ง€๋‚œ 4์‹œ๊ฐ„", "์ง€๋‚œ 1์ผ", "์ง€๋‚œ 7์ผ"],
1626
+ # value="์ง€๋‚œ 7์ผ",
1627
+ # label="๊ธฐ๊ฐ„ ์„ ํƒ"
1628
+ # )
1629
+
1630
+ # with gr.Column(scale=1):
1631
+ # category_input = gr.Dropdown(
1632
+ # choices=["๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ", "๊ฒŒ์ž„", "๊ฑด๊ฐ•", "๊ธฐ์ˆ ", "์Šคํฌ์ธ ", "์—”ํ„ฐํ…Œ์ธ๋จผํŠธ", "๋‰ด์Šค", "๋น„์ฆˆ๋‹ˆ์Šค"],
1633
+ # value="๋ชจ๋“  ์นดํ…Œ๊ณ ๋ฆฌ",
1634
+ # label="์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ"
1635
+ # )
1636
+
1637
+ # analyze_btn = gr.Button(
1638
+ # "๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„ ์‹œ์ž‘",
1639
+ # variant="primary",
1640
+ # size="lg"
1641
+ # )
1642
+
1643
+ # output = gr.HTML(
1644
+ # label="์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ฒฐ๊ณผ",
1645
+ # value="""
1646
+ # <div style="text-align: center; padding: 20px; border: 2px dashed #ccc; border-radius: 10px; color: #666;">
1647
+ # <h3>๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ ๋ถ„์„๊ธฐ ๋Œ€๊ธฐ ์ค‘</h3>
1648
+ # <p><strong>์ฒซ ์‹คํ–‰:</strong> ๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™”๋กœ 15-20์ดˆ ์†Œ์š”</p>
1649
+ # <p><strong>์ดํ›„ ์‹คํ–‰:</strong> ๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ์œผ๋กœ 5-10์ดˆ ์™„๋ฃŒ</p>
1650
+ # <p style="font-size: 14px;">์œ„์˜ ์„ค์ •์„ ์„ ํƒํ•˜๊ณ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”!</p>
1651
+ # </div>
1652
+ # """
1653
+ # )
1654
+
1655
+ # analyze_btn.click(
1656
+ # fn=persistent_browser_analysis,
1657
+ # inputs=[region_input, period_input, category_input],
1658
+ # outputs=output,
1659
+ # show_progress="hidden"
1660
+ # )
1661
+
1662
+ # gr.HTML("""
1663
+ # <div style="margin-top: 30px; padding: 20px; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); border-radius: 10px;">
1664
+ # <h3 style="margin-top: 0; color: #333;">๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ์˜ ์žฅ์ </h3>
1665
+ # <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; color: #555;">
1666
+ # <div>
1667
+ # <h4>์„ฑ๋Šฅ ํ–ฅ์ƒ</h4>
1668
+ # <ul style="margin: 0; padding-left: 20px;">
1669
+ # <li>๋‘ ๋ฒˆ์งธ๋ถ€ํ„ฐ 8-15์ดˆ ๋‹จ์ถ•</li>
1670
+ # <li>๋ธŒ๋ผ์šฐ์ € ์ดˆ๊ธฐํ™” ์‹œ๊ฐ„ ์ œ๊ฑฐ</li>
1671
+ # <li>ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์‹œ๊ฐ„ ์ตœ์†Œํ™”</li>
1672
+ # </ul>
1673
+ # </div>
1674
+ # <div>
1675
+ # <h4>์ž์› ํšจ์œจ์„ฑ</h4>
1676
+ # <ul style="margin: 0; padding-left: 20px;">
1677
+ # <li>๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™”</li>
1678
+ # <li>CPU ๋ถ€ํ•˜ ๊ฐ์†Œ</li>
1679
+ # <li>์•ˆ์ •์ ์ธ ์„ธ์…˜ ์œ ์ง€</li>
1680
+ # </ul>
1681
+ # </div>
1682
+ # </div>
1683
+ # </div>
1684
+ # """)
1685
+
1686
+ # return interface
1687
+
1688
+ # # ์•ฑ ์ข…๋ฃŒ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ
1689
+ # import atexit
1690
+
1691
+ # def cleanup_on_exit():
1692
+ # """์•ฑ ์ข…๋ฃŒ์‹œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ"""
1693
+ # global automator_instance
1694
+ # if automator_instance:
1695
+ # automator_instance.cleanup_driver()
1696
+ # print("์•ฑ ์ข…๋ฃŒ: ๋ธŒ๋ผ์šฐ์ € ์ •๋ฆฌ ์™„๋ฃŒ")
1697
+
1698
+ # atexit.register(cleanup_on_exit)
1699
+
1700
+ # if __name__ == "__main__":
1701
+ # print("=" * 50)
1702
+ # print("๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Google Trends ๋ถ„์„๊ธฐ ์‹œ์ž‘")
1703
+ # print("=" * 50)
1704
+
1705
+ # if not ANTHROPIC_API_KEY or ANTHROPIC_API_KEY == "your_api_key_here":
1706
+ # print("ANTHROPIC_API_KEY ๋ฏธ์„ค์ • - ์Šคํฌ๋ฆฐ์ƒท๋งŒ ์บก์ฒ˜")
1707
+ # else:
1708
+ # print("Claude API ํ‚ค ํ™•์ธ๋จ")
1709
+
1710
+ # print("๋ธŒ๋ผ์šฐ์ € ์žฌ์‚ฌ์šฉ Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ค€๋น„ ์ค‘...")
1711
+ # app = create_persistent_interface()
1712
+
1713
+ # if os.getenv("SPACE_ID"):
1714
+ # print("Hugging Face Space ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰")
1715
+ # app.launch(
1716
+ # server_name="0.0.0.0",
1717
+ # server_port=7860,
1718
+ # show_error=True,
1719
+ # show_api=False
1720
+ # )
1721
+ # else:
1722
+ # print("๋กœ์ปฌ ์„œ๋ฒ„ ์‹œ์ž‘: http://localhost:7860")
1723
+ # app.launch(
1724
+ # server_name="127.0.0.1",
1725
+ # server_port=7860,
1726
+ # show_error=True,
1727
+ # show_api=False,
1728
+ # share=False,
1729
+ # inbrowser=True
1730
+ # )