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