HanJun commited on
Commit
75152db
·
verified ·
1 Parent(s): 1603d09

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +303 -416
app.py CHANGED
@@ -2,80 +2,63 @@ import os
2
  import time
3
  import base64
4
  import io
5
- import traceback
6
  import threading
7
  import atexit
8
- import asyncio
9
  from datetime import datetime
10
- from typing import Optional, Tuple
11
 
12
  import gradio as gr
13
  import anthropic
14
- from playwright.async_api import async_playwright
15
- from playwright.sync_api import sync_playwright, Browser, Page
16
  from PIL import Image
17
- import requests
18
  from dotenv import load_dotenv
19
 
20
  # 환경변수 로드
21
  load_dotenv()
22
 
23
- # 전역 변수
24
- browser = None
25
- page = None
26
- playwright_instance = None
27
- browser_lock = threading.Lock()
28
- is_browser_ready = False
29
- browser_thread_id = None
30
 
31
- # Claude API 클라이언트 초기화
32
  def get_claude_client():
 
33
  api_key = os.getenv("ANTHROPIC_API_KEY")
34
  if not api_key:
35
  raise ValueError("ANTHROPIC_API_KEY 환경변수가 설정되지 않았습니다.")
36
 
37
- try:
38
- # 더 안전한 클라이언트 초기화
39
- client = anthropic.Anthropic(
40
- api_key=api_key,
41
- max_retries=2,
42
- timeout=60.0
43
- )
44
- return client
45
- except Exception as e:
46
- print(f"Claude 클라이언트 초기화 오류: {e}")
47
- # 기본 초기화 방식으로 재시도
48
- return anthropic.Anthropic(api_key=api_key)
49
 
50
  def install_browsers():
51
- """Playwright 브라우저 및 한글 폰트 설치 (한번만 실행)"""
52
  try:
53
- print("Playwright 브라우저 빠른 설치 중...")
54
- # 병렬 설치로 속도 향상
55
  os.system("playwright install chromium --force")
56
- print("한글 폰트 빠른 설치 중...")
57
- os.system("apt-get install -y fonts-nanum fonts-noto-cjk &")
58
- print("브라우저 및 폰트 설치 완료!")
59
  return True
60
  except Exception as e:
61
- print(f"설치 실패: {e}")
62
  return False
63
 
64
- def init_browser():
65
- """Playwright 브라우저를 초기화하고 Google Trends 페이지로 이동 (WarmUp 전략)"""
66
- global browser, page, playwright_instance, is_browser_ready, browser_thread_id
 
 
67
 
68
  try:
69
- browser_thread_id = threading.current_thread().ident
70
- print(f"브라우저 WarmUp 시작 (스레드: {browser_thread_id})")
71
-
72
- # 빠른 브라우저 설치
73
- install_browsers()
74
 
75
- # Playwright 인스턴스 생성
76
  playwright_instance = sync_playwright().start()
77
 
78
- # 속도 최적화된 브라우저 실행
79
  browser = playwright_instance.chromium.launch(
80
  headless=True,
81
  args=[
@@ -84,159 +67,77 @@ def init_browser():
84
  '--disable-gpu',
85
  '--disable-extensions',
86
  '--disable-web-security',
87
- '--disable-features=VizDisplayCompositor',
88
- '--disable-background-timer-throttling',
89
- '--disable-renderer-backgrounding',
90
- '--disable-plugins',
91
- '--disable-images', # 이미지 로딩 비활성화로 속도 향상
92
- '--disable-javascript', # JS 비활성화로 속도 향상
93
- '--disable-css', # CSS 비활성화로 속도 향상
94
- '--force-device-scale-factor=1',
95
- '--aggressive-cache-discard',
96
- '--memory-pressure-off'
97
  ]
98
  )
99
 
100
  # 새 페이지 생성
101
  page = browser.new_page()
102
-
103
- # 뷰포트 설정 (작게 설정하여 속도 향상)
104
- page.set_viewport_size({"width": 1200, "height": 800})
105
-
106
- # 빠른 헤더 설정
107
  page.set_extra_http_headers({
108
- "Accept-Language": "ko-KR,ko;q=0.9"
 
109
  })
110
 
111
- print("Google Trends 페이지로 빠른 이동 중...")
112
- # 빠른 로딩을 위해 domcontentloaded 사용, 타임아웃 단축
113
- page.goto("https://trends.google.com/trending?geo=KR",
114
- wait_until="domcontentloaded",
115
- timeout=15000)
116
 
117
- # 최소 대기 시간으로 단축
118
- time.sleep(2)
119
 
120
- is_browser_ready = True
121
- print("브라우저 WarmUp 완료! (고속 모드)")
122
-
123
- except Exception as e:
124
- print(f"브라우저 초기화 실패: {str(e)}")
125
- is_browser_ready = False
126
- cleanup_browser()
127
-
128
- def ensure_browser_ready():
129
- """브라우저가 준비되어 있는지 확인하고, 필요시 초기화"""
130
- global is_browser_ready, browser, page, browser_thread_id
131
-
132
- current_thread_id = threading.current_thread().ident
133
-
134
- # 브라우저가 준비되지 않았거나 다른 스레드에서 생성된 경우
135
- if not is_browser_ready or browser_thread_id != current_thread_id or not browser or not page:
136
- print(f"브라우저 재초기화 필요 (현재 스레드: {current_thread_id}, 브라우저 스레드: {browser_thread_id})")
137
-
138
- # 기존 브라우저 정리
139
- cleanup_browser()
140
-
141
- # 현재 스레드에서 새로 초기화
142
- init_browser()
143
-
144
- return is_browser_ready
145
-
146
- def cleanup_browser():
147
- """브라우저 정리"""
148
- global browser, page, playwright_instance
149
-
150
- try:
151
- if page:
152
- page.close()
153
- page = None
154
- if browser:
155
- browser.close()
156
- browser = None
157
- if playwright_instance:
158
- playwright_instance.stop()
159
- playwright_instance = None
160
- print("브라우저 정리 완료")
161
- except Exception as e:
162
- print(f"브라우저 정리 중 오류: {e}")
163
-
164
- def apply_filters(geo: str, time_range: str, category: str) -> bool:
165
- """Google Trends 필터 적용 (고속 모드)"""
166
- global page
167
-
168
- if not ensure_browser_ready():
169
- print("브라우저 준비 실패")
170
- return False
171
-
172
- try:
173
- print(f"고속 필터 적용: 지역={geo}")
174
-
175
- # 지역 설정을 위한 URL 구성
176
- geo_map = {
177
- "KR": "KR", "US": "US", "JP": "JP",
178
- "GB": "GB", "DE": "DE", "FR": "FR"
179
- }
180
-
181
- base_url = "https://trends.google.com/trending"
182
- geo_param = geo_map.get(geo, "KR")
183
- url = f"{base_url}?geo={geo_param}"
184
-
185
- print(f"빠른 이동: {url}")
186
- # 빠른 로딩을 위해 domcontentloaded 사용, 타임아웃 대폭 단축
187
- page.goto(url, wait_until="domcontentloaded", timeout=10000)
188
-
189
- # 최소 대기 시간
190
- time.sleep(1)
191
-
192
- print("고속 필터 적용 완료")
193
- return True
194
-
195
- except Exception as e:
196
- print(f"필터 적용 실패: {str(e)}")
197
- # 실패해도 현재 페이지에서 스크린샷 시도
198
- return True # 실패해도 계속 진행
199
-
200
- def capture_screenshot() -> Optional[Image.Image]:
201
- """현재 페이지의 스크린샷 캡처 (고속 모드)"""
202
- global page
203
-
204
- if not ensure_browser_ready():
205
- print("브라우저 준비 실패")
206
- return None
207
-
208
- try:
209
- print("고속 스크린샷 캡처 중...")
210
 
211
- # 대기 시간 최소화
212
- time.sleep(0.5)
213
 
214
- # 빠른 스크린샷을 위해 특정 영역만 캡처
215
  try:
216
- # 트렌드 테이블 영역 찾기 (빠른 셀렉터만 사용)
217
- element = page.locator('main').first
218
- if element.is_visible(timeout=2000):
219
- screenshot_bytes = element.screenshot()
220
- print("메인 영역 고속 캡처 완료")
221
  else:
222
- # 대안: 페이지 중앙 영역만 빠르게 캡처
223
- screenshot_bytes = page.screenshot(clip={"x": 0, "y": 150, "width": 1200, "height": 600})
224
- print("중앙 영역 고속 캡처 완료")
225
-
226
- except Exception:
227
- # 최종 대안: 전��� 페이지 캡처
228
- screenshot_bytes = page.screenshot()
229
- print("전체 페이지 고속 캡처 완료")
230
-
231
  screenshot = Image.open(io.BytesIO(screenshot_bytes))
 
232
  return screenshot
233
-
234
  except Exception as e:
235
  print(f"스크린샷 캡처 실패: {str(e)}")
236
  return None
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
  def analyze_with_claude(image: Image.Image) -> str:
239
- """Claude API 사용하여 이미지 분석 (고속 모드)"""
 
 
 
240
  try:
241
  client = get_claude_client()
242
 
@@ -245,16 +146,40 @@ def analyze_with_claude(image: Image.Image) -> str:
245
  image.save(buffer, format="PNG")
246
  image_base64 = base64.b64encode(buffer.getvalue()).decode()
247
 
248
- # 축약된 프롬프트 (속도 향상)
249
- prompt = """Google Trends 스크린샷을 간단히 분석해주세요:
250
- 1. 상위 5개 검색어와 순위
251
- 2. 주요 트렌드 패턴
252
- 3. 특이사항
253
- 한국어로 간단명료하게 답변하세요."""
254
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  response = client.messages.create(
256
  model="claude-3-5-sonnet-20241022",
257
- max_tokens=800, # 토큰 수 대폭 축소
258
  messages=[
259
  {
260
  "role": "user",
@@ -276,291 +201,253 @@ def analyze_with_claude(image: Image.Image) -> str:
276
  ]
277
  )
278
 
279
- return response.content[0].text
 
 
280
 
281
  except Exception as e:
282
  error_msg = f"Claude API 분석 실패: {str(e)}"
283
  print(error_msg)
284
  return error_msg
285
 
286
- def reinit_browser():
287
- """브라우저 경량 재초기화 (기존 브라우저 유지)"""
288
- global page
 
 
 
 
 
289
 
290
  try:
291
- print("경량 브라우저 재초기화 시작...")
 
292
 
293
- if page:
294
- # 브라우저를 완전히 재시작하지 않고 페이지만 새로고침
295
- page.goto("https://trends.google.com/trending?geo=KR",
296
- wait_until="domcontentloaded",
297
- timeout=10000)
298
- time.sleep(1)
299
- return "브라우저 경량 재초기화 완료! (기존 브라우저 유지)"
 
 
 
 
 
 
 
 
 
 
 
300
  else:
301
- # 페이지가 없는 경우에만 완전 재초기화
302
- init_browser()
303
- if is_browser_ready:
304
- return "브라우저 완전 재초기화 완료!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  else:
306
- return "브라우저 재초기화 실패"
307
 
308
- except Exception as e:
309
- print(f"재초기화 실패: {e}")
310
- return f"재초기화 실패: {str(e)}"
311
 
312
- def refresh_trends_page():
313
- """Google Trends 페이지 빠른 새로고침"""
314
- global page
315
 
316
- if not ensure_browser_ready():
317
- return "브라우저가 준비되지 않았습니다."
318
-
319
- try:
320
- print("빠른 페이지 새로고침...")
321
- # reload() 대신 현재 URL로 빠르게 이동
322
- current_url = page.url
323
- page.goto(current_url, wait_until="domcontentloaded", timeout=8000)
324
- time.sleep(0.5)
325
- return "페이지 빠른 새로고침 완료!"
326
- except Exception as e:
327
- return f"페이지 새로고침 실패: {str(e)}"
328
-
329
- def main_process_screenshot(geo: str, time_range: str, category: str) -> Tuple[Optional[Image.Image], str]:
330
- """스크린샷 캡처만 수행하는 함수 (고속 모드)"""
331
- try:
332
- # 1. 브라우저 준비 확인 (빠른 확인)
333
- if not ensure_browser_ready():
334
- return None, "브라우저가 준비되지 않았습니다."
335
-
336
- # 2. 고속 필터 적용
337
- print(f"고속 처리: 지역={geo}")
338
- filter_success = apply_filters(geo, time_range, category)
339
-
340
- # 필터 실패해도 현재 페이지에서 스크린샷 시도
341
- if not filter_success:
342
- print("필터 적용 실패했지만 현재 페이지에서 스크린샷 시도")
343
-
344
- # 3. 고속 스크린샷 캡처
345
- screenshot = capture_screenshot()
346
-
347
- if screenshot is None:
348
- return None, "스크린샷 캡처에 실패했습니다."
349
-
350
- return screenshot, "고속 스크린샷 캡처 완료!"
351
 
352
- except Exception as e:
353
- error_msg = f"고속 처리 중 오류: {str(e)}"
354
- print(error_msg)
355
- return None, error_msg
356
 
357
- def analyze_screenshot_with_claude(screenshot: Image.Image) -> str:
358
- """Claude API로 스크린샷 분석 (고속 모드)"""
359
- if screenshot is None:
360
- return "분석할 스크린샷이 없습니다."
361
 
362
- try:
363
- print("Claude 고속 분석 중...")
364
- analysis_result = analyze_with_claude(screenshot)
365
- return analysis_result
366
-
367
- except Exception as e:
368
- error_msg = f"Claude 분석 실패: {str(e)}"
369
- print(error_msg)
370
- return error_msg
371
-
372
- def main_process(geo: str, time_range: str, category: str) -> Tuple[Optional[Image.Image], str]:
373
- """전체 프로세스 (호환성을 위해 유지)"""
374
- try:
375
- # 1. 브라우저 준비 확인
376
- if not ensure_browser_ready():
377
- return None, "브라우저가 준비���지 않았습니다. '브라우저 재초기화' 버튼을 클릭해주세요."
378
-
379
- # 2. 필터 적용
380
- print(f"필터 적용 중: 지역={geo}, 시간={time_range}, 카테고리={category}")
381
- filter_success = apply_filters(geo, time_range, category)
382
-
383
- if not filter_success:
384
- return None, "필터 적용에 실패했습니다."
385
 
386
- # 3. 스크린샷 캡처
387
- print("스크린샷 캡처 중...")
388
- screenshot = capture_screenshot()
389
 
390
- if screenshot is None:
391
- return None, "스크린샷 캡처에 실패했습니다."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
 
393
- # 4. Claude API로 분석
394
- print("Claude API로 분석 중...")
395
- analysis_result = analyze_with_claude(screenshot)
 
 
 
 
 
396
 
397
- return screenshot, analysis_result
 
 
 
 
398
 
399
- except Exception as e:
400
- error_msg = f"처리 중 오류 발생: {str(e)}"
401
- print(error_msg)
402
- print(traceback.format_exc())
403
- return None, error_msg
404
-
405
- def check_browser_status():
406
- """브라우저 상태 확인 (고속)"""
407
- global is_browser_ready, page, browser_thread_id
408
-
409
- current_thread_id = threading.current_thread().ident
410
-
411
- if not page:
412
- return "브라우저 미초기화"
413
-
414
- if not is_browser_ready:
415
- return "브라우저 초기화 중..."
416
-
417
- try:
418
- current_url = page.url
419
- return f"브라우저 준비완료 - URL: {current_url[:50]}..."
420
- except Exception as e:
421
- return f"브라우저 연결 문제: {str(e)[:30]}..."
422
-
423
- # Gradio 인터페이스 생성
424
- def create_interface():
425
- """Gradio 인터페이스 생성"""
426
-
427
- with gr.Blocks(title="Google Trends 고속 분석기", theme=gr.themes.Soft()) as interface:
428
- gr.Markdown("# ⚡ Google Trends 고속 분석기")
429
- gr.Markdown("**초고속 모드**: Google Trends 실시간 검색어를 빠르게 캡처하고 AI 분석합니다.")
430
 
431
- # 상태 저장을 위한 State 컴포넌트
432
- screenshot_state = gr.State(None)
433
 
434
  with gr.Row():
435
  with gr.Column(scale=1):
436
- geo_dropdown = gr.Dropdown(
437
- choices=["KR", "US", "JP", "GB", "DE", "FR"],
438
- value="KR",
439
- label="지역 선택"
440
- )
441
 
442
- time_dropdown = gr.Dropdown(
443
- choices=["24시간", "7일", "30일", "90일", "12개월"],
444
- value="24시간",
445
- label="기간 선택"
446
- )
447
 
448
- category_dropdown = gr.Dropdown(
449
- choices=["모든 카테고리", "엔터테인먼트", "스포츠", "비즈니스", "과학기술", "건강"],
450
- value="모든 카테고리",
451
- label="카테고리 선택"
452
- )
453
 
454
- with gr.Row():
455
- capture_btn = gr.Button("⚡ 고속 캡처", variant="primary", size="lg")
456
- analyze_btn = gr.Button("🤖 AI 분석", variant="secondary", interactive=False)
457
 
458
- with gr.Row():
459
- refresh_btn = gr.Button("🔄 빠른 새로고침", variant="secondary", size="sm")
460
- reinit_btn = gr.Button("⚙️ 경량 재시작", variant="secondary", size="sm")
461
- status_btn = gr.Button("📊 상태 확인", variant="secondary", size="sm")
 
 
 
 
 
 
 
 
 
 
 
462
 
463
  with gr.Column(scale=2):
464
- screenshot_output = gr.Image(label="Google Trends 스크린샷")
465
- analysis_output = gr.Textbox(
466
- label="Claude AI 고속 분석 결과",
467
- lines=8,
468
- max_lines=15,
469
- placeholder="고속 캡처 후 'AI 분석' 버튼을 클릭하세요."
470
  )
471
- status_output = gr.Textbox(label="상태 메시지")
472
-
473
- # 이벤트 핸들러
474
 
475
- # 고속 캡처 버튼
476
- def on_capture_click(geo, time_range, category):
477
- screenshot, message = main_process_screenshot(geo, time_range, category)
478
- if screenshot is not None:
479
- # 분석 버튼 활성화
480
- return screenshot, screenshot, message, gr.update(interactive=True)
481
- else:
482
- return None, None, message, gr.update(interactive=False)
483
-
484
- capture_btn.click(
485
- fn=on_capture_click,
486
- inputs=[geo_dropdown, time_dropdown, category_dropdown],
487
- outputs=[screenshot_output, screenshot_state, status_output, analyze_btn]
488
- )
489
-
490
- # AI 분석 버튼
491
- def on_analyze_click(screenshot):
492
- if screenshot is None:
493
- return "분석할 스크린샷이 없습니다. 먼저 '고속 캡처' 버튼을 클릭하세요."
494
-
495
- analysis_result = analyze_screenshot_with_claude(screenshot)
496
- return analysis_result
497
-
498
- analyze_btn.click(
499
- fn=on_analyze_click,
500
- inputs=[screenshot_state],
501
- outputs=[analysis_output]
502
- )
503
-
504
- # 기타 버튼들
505
- refresh_btn.click(
506
- fn=refresh_trends_page,
507
- outputs=status_output
508
- )
509
-
510
- reinit_btn.click(
511
- fn=reinit_browser,
512
- outputs=status_output
513
  )
514
 
515
- status_btn.click(
516
- fn=check_browser_status,
517
- outputs=status_output
518
  )
519
 
520
  return interface
521
 
522
  def main():
523
- """메인 함수 (WarmUp 전략 적용)"""
524
- print("=" * 50)
525
- print("Google Trends 고속 분석기 시작!")
526
- print("=" * 50)
527
 
528
  # API 키 확인
529
  api_key = os.getenv("ANTHROPIC_API_KEY")
530
  if not api_key:
531
- print("ANTHROPIC_API_KEY 환경변수가 설정되지 않았습니다.")
 
532
  return
533
  else:
534
- print("Claude API 키 확인됨")
535
-
536
- print("✓ Gradio 고속 인터페이스 준비 중...")
537
-
538
- # 환경 확인
539
- if os.getenv("SPACE_ID"):
540
- print("✓ Hugging Face Space 환경에서 실행 중...")
541
 
542
- print("✓ 브라우저 WarmUp 시작 (백그라운드)")
 
 
543
 
544
- # WarmUp 전략: 백그라운드에서 브라우저 미리 준비
545
- warmup_thread = threading.Thread(target=init_browser, daemon=True)
546
- warmup_thread.start()
547
-
548
- # 종료 시 브라우저 정리
549
- atexit.register(cleanup_browser)
550
 
551
- # Gradio 인터페이스 생성 및 실행
552
- interface = create_interface()
 
 
 
553
 
554
- print(" 고속 분석기 준비 완료!")
 
555
 
556
- # 빠른 실행을 위한 설정
557
- interface.launch(
558
- server_name="0.0.0.0",
559
- server_port=7860,
560
- share=False,
561
- show_error=True,
562
- quiet=True # 로그 출력 최소화
563
- )
 
 
 
 
 
564
 
565
  if __name__ == "__main__":
566
  main()
 
2
  import time
3
  import base64
4
  import io
 
5
  import threading
6
  import atexit
7
+ import traceback
8
  from datetime import datetime
9
+ from typing import Optional, Dict, Any
10
 
11
  import gradio as gr
12
  import anthropic
13
+ from playwright.sync_api import sync_playwright
 
14
  from PIL import Image
 
15
  from dotenv import load_dotenv
16
 
17
  # 환경변수 로드
18
  load_dotenv()
19
 
20
+ # 분석 데이터 저장소
21
+ analyzed_data = {}
22
+ last_update_time = None
23
+ update_thread = None
24
+ is_updating = False
 
 
25
 
 
26
  def get_claude_client():
27
+ """Claude API 클라이언트 초기화"""
28
  api_key = os.getenv("ANTHROPIC_API_KEY")
29
  if not api_key:
30
  raise ValueError("ANTHROPIC_API_KEY 환경변수가 설정되지 않았습니다.")
31
 
32
+ return anthropic.Anthropic(
33
+ api_key=api_key,
34
+ max_retries=3,
35
+ timeout=60.0
36
+ )
 
 
 
 
 
 
 
37
 
38
  def install_browsers():
39
+ """Playwright 브라우저 설치"""
40
  try:
41
+ print("Playwright 브라우저 설치 중...")
 
42
  os.system("playwright install chromium --force")
43
+ print("브라우저 설치 완료!")
 
 
44
  return True
45
  except Exception as e:
46
+ print(f"브라우저 설치 실패: {e}")
47
  return False
48
 
49
+ def capture_trends_screenshot() -> Optional[Image.Image]:
50
+ """실시간 Google Trends 스크린샷 캡처 (독립 브라우저 세션)"""
51
+ playwright_instance = None
52
+ browser = None
53
+ page = None
54
 
55
  try:
56
+ print("새 브라우저 세션 시작...")
 
 
 
 
57
 
58
+ # 독립적인 Playwright 인스턴스 생성
59
  playwright_instance = sync_playwright().start()
60
 
61
+ # 브라우저 실행
62
  browser = playwright_instance.chromium.launch(
63
  headless=True,
64
  args=[
 
67
  '--disable-gpu',
68
  '--disable-extensions',
69
  '--disable-web-security',
70
+ '--disable-blink-features=AutomationControlled'
 
 
 
 
 
 
 
 
 
71
  ]
72
  )
73
 
74
  # 새 페이지 생성
75
  page = browser.new_page()
76
+ page.set_viewport_size({"width": 1400, "height": 900})
 
 
 
 
77
  page.set_extra_http_headers({
78
+ "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
79
+ "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"
80
  })
81
 
82
+ # Google Trends 실시간 페이지 로드
83
+ url = "https://trends.google.co.kr/trending?geo=KR"
84
+ print(f"페이지 로드: {url}")
 
 
85
 
86
+ page.goto(url, wait_until="domcontentloaded", timeout=30000)
87
+ time.sleep(5)
88
 
89
+ # 쿠키 팝업 처리
90
+ try:
91
+ accept_button = page.locator('button:has-text("모두 수락"), button:has-text("Accept all"), button:has-text("수락")')
92
+ if accept_button.count() > 0:
93
+ accept_button.first.click(timeout=3000)
94
+ time.sleep(2)
95
+ except:
96
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ # 페이지 완전 로드 대기
99
+ time.sleep(3)
100
 
101
+ # 스크린샷 캡처
102
  try:
103
+ main_content = page.locator('main, .main-content, [data-ve-type="main"], .trends-wrapper')
104
+ if main_content.count() > 0 and main_content.first.is_visible():
105
+ screenshot_bytes = main_content.first.screenshot(timeout=10000)
106
+ print("메인 컨텐츠 스크린샷 캡처 완료")
 
107
  else:
108
+ screenshot_bytes = page.screenshot(full_page=True, timeout=15000)
109
+ print("전체 페이지 스크린샷 캡처 완료")
110
+ except Exception as e:
111
+ print(f"스크린샷 캡처 재시도: {e}")
112
+ screenshot_bytes = page.screenshot(timeout=15000)
113
+ print("재시도 스크린샷 캡처 완료")
114
+
115
+ # 이미지 객체 생성
 
116
  screenshot = Image.open(io.BytesIO(screenshot_bytes))
117
+ print("스크린샷 처리 완료")
118
  return screenshot
119
+
120
  except Exception as e:
121
  print(f"스크린샷 캡처 실패: {str(e)}")
122
  return None
123
+ finally:
124
+ # 브라우저 정리
125
+ try:
126
+ if page:
127
+ page.close()
128
+ if browser:
129
+ browser.close()
130
+ if playwright_instance:
131
+ playwright_instance.stop()
132
+ print("브라우저 세션 정리 완료")
133
+ except Exception as e:
134
+ print(f"브라우저 정리 중 오류: {e}")
135
 
136
  def analyze_with_claude(image: Image.Image) -> str:
137
+ """Claude API 이미지 분석"""
138
+ if image is None:
139
+ return "분석할 이미지가 없습니다."
140
+
141
  try:
142
  client = get_claude_client()
143
 
 
146
  image.save(buffer, format="PNG")
147
  image_base64 = base64.b64encode(buffer.getvalue()).decode()
148
 
149
+ # 분석 프롬프트
150
+ prompt = """Google Trends 실시간 인기 검색어 스크린샷을 분석해주세요.
151
+
152
+ 다음 항목들을 중심으로 한국어로 분석해주세요:
153
+
154
+ ### A. 상위 검색어 순위 (검색량 기준)
155
+ 검색량이 높은 순서대로 정렬하여 나열:
156
+ **1위.** 검색어명 (검색량)
157
+ **2위.** 검색어명 (검색량)
158
+ **3위.** 검색어명 (검색량)
159
+ (이런 식으로 10위까지)
160
+
161
+ ### B. 주요 트렌드 키워드
162
+ - 특히 주목할 만한 검색어들
163
+ - 검색량 증가 패턴
164
+
165
+ ### C. 카테고리별 특징
166
+ - 엔터테인먼트, 뉴스, 스포츠 등 분야별 동향
167
+
168
+ ### D. 특이사항 및 분석
169
+ - 급상승 키워드
170
+ - 시기적/사회적 배경 추측
171
+ - 전체적인 관심사 패턴
172
+
173
+ **중요 지시사항:**
174
+ - 마크다운 형식을 사용하여 **굵은 글씨**, *기울임*, `코드` 등으로 중요한 내용을 강조해주세요
175
+ - 검색어명, 수치, 카테고리명 등 핵심 정보는 **굵은 글씨**로 표시
176
+ - 급상승률이나 특별한 수치는 `백틱`으로 감싸서 강조
177
+ - 상위 검색어는 반드시 검색량 순서대로 1위부터 10위까지 정렬
178
+ - ### 헤딩은 이미 제공되니 내용만 작성해주세요"""
179
+
180
  response = client.messages.create(
181
  model="claude-3-5-sonnet-20241022",
182
+ max_tokens=1500,
183
  messages=[
184
  {
185
  "role": "user",
 
201
  ]
202
  )
203
 
204
+ analysis_result = response.content[0].text
205
+ print("Claude 분석 완료")
206
+ return analysis_result
207
 
208
  except Exception as e:
209
  error_msg = f"Claude API 분석 실패: {str(e)}"
210
  print(error_msg)
211
  return error_msg
212
 
213
+ def perform_analysis():
214
+ """실시간 트렌드 분석 수행"""
215
+ global analyzed_data, last_update_time, is_updating
216
+
217
+ is_updating = True
218
+ print(f"\n{'='*60}")
219
+ print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 실시간 트렌드 분석 시작")
220
+ print(f"{'='*60}")
221
 
222
  try:
223
+ # 브라우저 설치 확인
224
+ install_browsers()
225
 
226
+ # 스크린샷 캡처
227
+ print("스크린샷 캡처 중...")
228
+ screenshot = capture_trends_screenshot()
229
+
230
+ if screenshot:
231
+ print("Claude 분석 중...")
232
+ # Claude 분석
233
+ analysis = analyze_with_claude(screenshot)
234
+
235
+ # 결과 저장
236
+ analyzed_data["실시간"] = {
237
+ 'analysis': analysis,
238
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
239
+ }
240
+
241
+ last_update_time = datetime.now()
242
+ print(f"분석 완료 - {last_update_time.strftime('%Y-%m-%d %H:%M:%S')}")
243
+ return True
244
  else:
245
+ print("스크린샷 캡처 실패")
246
+ return False
247
+
248
+ except Exception as e:
249
+ print(f"분석 중 오류: {str(e)}")
250
+ print(f"상세 오류: {traceback.format_exc()}")
251
+ return False
252
+ finally:
253
+ is_updating = False
254
+ print(f"{'='*60}\n")
255
+
256
+ def auto_update_scheduler():
257
+ """1시간마다 자동 업데이트"""
258
+ while True:
259
+ try:
260
+ print(f"[스케줄러] 다음 업데이트까지 1시간 대기 중...")
261
+ time.sleep(3600) # 1시간 대기
262
+
263
+ if not is_updating:
264
+ print(f"[스케줄러] 자동 업데이트 시작")
265
+ perform_analysis()
266
  else:
267
+ print(f"[스케줄러] 이미 업데이트 중이므로 건너뜀")
268
 
269
+ except Exception as e:
270
+ print(f"[스케줄러] 오류: {e}")
271
+ time.sleep(300) # 5분 재시도
272
 
273
+ def get_analysis_result() -> tuple:
274
+ """저장된 분석 결과 반환"""
275
+ global analyzed_data, last_update_time
276
 
277
+ if "실시간" in analyzed_data:
278
+ data = analyzed_data["실시간"]
279
+ status = f"최신 데이터 (업데이트: {data['timestamp']})"
280
+ return data['analysis'], status
281
+ else:
282
+ if last_update_time:
283
+ status = f"데이터 없음 (마지막 업데이트: {last_update_time.strftime('%H:%M:%S')})"
284
+ else:
285
+ status = "아직 분석 데이터가 준비되지 않았습니다."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
+ return "**분석 데이터가 아직 준비되지 않았습니다.**", status
 
 
 
288
 
289
+ def create_interface():
290
+ """Gradio 인터페이스 생성"""
 
 
291
 
292
+ def show_cached_analysis():
293
+ """저장된 분석 결과 표시"""
294
+ if "실시간" in analyzed_data:
295
+ data = analyzed_data["실시간"]
296
+ status = f"캐시된 분석 결과 (업데이트: {data['timestamp']})"
297
+ return data['analysis'], status
298
+ else:
299
+ if last_update_time:
300
+ status = f"저장된 데이터 없음 (마지막 업데이트: {last_update_time.strftime('%H:%M:%S')})"
301
+ else:
302
+ status = "아직 분석 데이터가 준비되지 않았습니다. 잠시 다시 시도해주세요."
303
+
304
+ return "**저장된 분석 데이터가 없습니다.**\n\n'실시간 업데이트' 버튼을 눌러주세요.", status
305
+
306
+ def perform_live_update():
307
+ """실시간 업데이트 수행"""
308
+ if is_updating:
309
+ return "**현재 업데이트가 진행 중입니다.**\n\n잠시 후 다시 시도해주세요.", "업데이트 진행 중..."
 
 
 
 
 
310
 
311
+ print("실시간 업데이트 요청됨")
312
+ success = perform_analysis()
 
313
 
314
+ if success and analyzed_data:
315
+ data = analyzed_data["실시간"]
316
+ return data['analysis'], f"실시간 업데이트 완료 - {datetime.now().strftime('%H:%M:%S')}"
317
+ else:
318
+ return "**업데이트에 실패했습니다.**\n\n잠시 후 다시 시도해주세요.", f"업데이트 실패 - {datetime.now().strftime('%H:%M:%S')}"
319
+
320
+ # Gradio 인터페이스 구성
321
+ with gr.Blocks(
322
+ title="Google Trends 인기 검색어",
323
+ theme=gr.themes.Soft(),
324
+ css="""
325
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
326
+
327
+ * {
328
+ font-family: 'Noto Sans KR', sans-serif !important;
329
+ }
330
 
331
+ .markdown-content {
332
+ font-size: 14px;
333
+ line-height: 1.6;
334
+ background: #f8f9fa;
335
+ padding: 20px;
336
+ border-radius: 8px;
337
+ border: 1px solid #e9ecef;
338
+ }
339
 
340
+ .markdown-content h3 {
341
+ color: #2563eb;
342
+ margin-top: 20px;
343
+ margin-bottom: 10px;
344
+ }
345
 
346
+ .markdown-content strong {
347
+ color: #1d4ed8;
348
+ font-weight: 600;
349
+ }
350
+ """
351
+ ) as interface:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
+ gr.Markdown("# Google Trends 인기 검색어")
 
354
 
355
  with gr.Row():
356
  with gr.Column(scale=1):
357
+ gr.Markdown("### 분석 옵션")
 
 
 
 
358
 
359
+ with gr.Column():
360
+ cached_btn = gr.Button("실시간 검색어 분석", variant="primary", size="lg")
 
 
 
361
 
362
+ gr.Markdown("---")
363
+
364
+ with gr.Column():
365
+ live_btn = gr.Button("실시간 업데이트", variant="secondary", size="lg")
 
366
 
367
+ gr.Markdown("---")
 
 
368
 
369
+ status_output = gr.Textbox(
370
+ label="상태",
371
+ value="분석할 옵션을 선택해주세요.",
372
+ interactive=False
373
+ )
374
+
375
+ # Apeach 이미지 표시
376
+ gr.Image(
377
+ value="apeach.png",
378
+ label=None,
379
+ show_label=False,
380
+ interactive=False,
381
+ height=200,
382
+ width=200
383
+ )
384
 
385
  with gr.Column(scale=2):
386
+ analysis_output = gr.Markdown(
387
+ value="**분석 버튼을 눌러주세요.**",
388
+ elem_classes=["markdown-content"],
389
+ height=600
 
 
390
  )
 
 
 
391
 
392
+ # 이벤트 연결
393
+ cached_btn.click(
394
+ fn=show_cached_analysis,
395
+ outputs=[analysis_output, status_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  )
397
 
398
+ live_btn.click(
399
+ fn=perform_live_update,
400
+ outputs=[analysis_output, status_output]
401
  )
402
 
403
  return interface
404
 
405
  def main():
406
+ """메인 함수"""
407
+ print("=" * 60)
408
+ print("Google Trends 인기 검색어 시작!")
409
+ print("=" * 60)
410
 
411
  # API 키 확인
412
  api_key = os.getenv("ANTHROPIC_API_KEY")
413
  if not api_key:
414
+ print("ANTHROPIC_API_KEY 환경변수가 설정되지 않았습니다.")
415
+ print(" .env 파일에 ANTHROPIC_API_KEY=your_key_here 를 추가하세요.")
416
  return
417
  else:
418
+ print("Claude API 키 확인됨")
 
 
 
 
 
 
419
 
420
+ # 초기 분석 수행
421
+ print("초기 분석 시작...")
422
+ initial_success = perform_analysis()
423
 
424
+ if initial_success:
425
+ print("초기 분석 완료")
426
+ else:
427
+ print("초기 분석 실패, 서비스는 계속 진행됩니다.")
 
 
428
 
429
+ # 자동 업데이트 스케줄러 시작
430
+ print("자동 업데이트 스케줄러 시작 (1시간 간격)")
431
+ global update_thread
432
+ update_thread = threading.Thread(target=auto_update_scheduler, daemon=True)
433
+ update_thread.start()
434
 
435
+ print("모든 준비 완료!")
436
+ print("=" * 60)
437
 
438
+ # Gradio 인터페이스 실행
439
+ try:
440
+ interface = create_interface()
441
+ interface.launch(
442
+ server_name="0.0.0.0",
443
+ server_port=7860,
444
+ share=False,
445
+ show_error=True,
446
+ quiet=False
447
+ )
448
+ except Exception as e:
449
+ print(f"인터페이스 시작 실패: {e}")
450
+ print(f"상세 오류: {traceback.format_exc()}")
451
 
452
  if __name__ == "__main__":
453
  main()