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