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