File size: 27,582 Bytes
1aa69e4
 
 
349ae2c
 
 
75152db
bf40342
 
349ae2c
bf40342
349ae2c
1aa69e4
 
75152db
349ae2c
16cf3af
 
 
 
1aa69e4
75152db
 
 
 
 
1aa69e4
349ae2c
75152db
349ae2c
 
 
16cf3af
75152db
 
 
 
 
1aa69e4
6c4649a
75152db
6c4649a
75152db
1603d09
75152db
6c4649a
 
75152db
6c4649a
 
37ff9f3
 
349ae2c
37ff9f3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf40342
 
37ff9f3
bf40342
37ff9f3
bf40342
 
37ff9f3
 
 
bf40342
 
 
 
37ff9f3
 
 
bf40342
 
 
349ae2c
6c4649a
75152db
6c4649a
75152db
bf40342
 
6c4649a
 
75152db
bf40342
 
 
 
 
 
 
 
 
 
0299c67
bf40342
75152db
98e58ec
75152db
 
 
 
 
bf40342
 
 
 
 
 
 
 
 
 
75152db
bf40342
644a47d
bf40342
 
644a47d
bf40342
 
0299c67
75152db
 
bf40342
 
0299c67
bf40342
 
75152db
 
bf40342
75152db
 
0299c67
bf40342
644a47d
75152db
349ae2c
bf40342
 
349ae2c
75152db
37ff9f3
bf40342
 
75152db
bf40342
 
37ff9f3
 
 
 
 
 
 
 
 
 
bf40342
 
37ff9f3
 
 
bf40342
 
 
 
37ff9f3
 
 
bf40342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75152db
bf40342
 
 
 
 
 
 
 
 
 
 
 
 
 
37ff9f3
bf40342
 
 
 
 
37ff9f3
 
 
 
 
 
 
 
 
 
98e58ec
bf40342
 
75152db
bf40342
75152db
1aa69e4
349ae2c
1aa69e4
349ae2c
 
 
1aa69e4
75152db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bf40342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75152db
349ae2c
16cf3af
bf40342
1aa69e4
 
 
 
 
 
 
 
 
 
 
 
 
349ae2c
1aa69e4
 
 
 
 
 
 
bf40342
1aa69e4
 
bf40342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1aa69e4
bf40342
 
 
 
 
 
 
 
 
 
 
daf5ca3
bf40342
 
 
 
 
75152db
 
 
 
bf40342
75152db
16cf3af
1603d09
75152db
bf40342
75152db
1603d09
bf40342
 
75152db
bf40342
 
 
75152db
bf40342
 
75152db
bf40342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1603d09
bf40342
75152db
bf40342
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75152db
bf40342
 
75152db
bf40342
75152db
 
 
 
 
 
 
 
 
 
 
 
 
bf40342
1603d09
75152db
1603d09
75152db
 
 
16cf3af
bf40342
 
75152db
2c476a3
bf40342
 
75152db
 
 
 
 
 
 
0299c67
bf40342
0299c67
75152db
 
0299c67
75152db
bf40342
 
 
 
75152db
 
 
 
 
 
 
bf40342
75152db
 
 
 
 
1aa69e4
bf40342
 
1aa69e4
bf40342
 
75152db
 
 
 
 
 
bf40342
75152db
 
 
 
 
 
 
1aa69e4
75152db
 
 
 
 
 
 
 
1aa69e4
bf40342
 
 
 
 
 
75152db
bf40342
 
 
 
 
 
75152db
 
 
1aa69e4
75152db
 
 
 
 
 
1aa69e4
bf40342
 
0299c67
1aa69e4
 
75152db
1aa69e4
75152db
bf40342
 
1aa69e4
75152db
 
 
 
bf40342
349ae2c
75152db
0299c67
75152db
 
 
 
 
 
 
bf40342
 
 
 
 
 
 
 
 
 
 
349ae2c
 
75152db
bf40342
75152db
bf40342
1aa69e4
0299c67
75152db
 
 
 
16cf3af
 
75152db
 
 
1aa69e4
 
 
 
349ae2c
75152db
 
bf40342
75152db
1aa69e4
1603d09
16cf3af
 
75152db
 
349ae2c
16cf3af
75152db
349ae2c
bf40342
 
 
1603d09
75152db
bf40342
75152db
 
349ae2c
75152db
 
 
 
 
349ae2c
75152db
 
1603d09
75152db
 
 
 
 
 
 
 
 
 
 
 
 
349ae2c
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
import os
import time
import base64
import io
import threading
import atexit
import traceback
import asyncio
import concurrent.futures
from datetime import datetime
from typing import Optional, Dict, Any, Tuple

import gradio as gr
import anthropic
from playwright.sync_api import sync_playwright
from PIL import Image
from dotenv import load_dotenv

# 환경변수 로드
load_dotenv()

# 분석 데이터 저장소
analyzed_data = {}
last_update_time = None
update_thread = None
is_updating = False

def get_claude_client():
    """Claude API 클라이언트 초기화"""
    api_key = os.getenv("ANTHROPIC_API_KEY")
    if not api_key:
        raise ValueError("ANTHROPIC_API_KEY 환경변수가 설정되지 않았습니다.")
    
    return anthropic.Anthropic(
        api_key=api_key,
        max_retries=3,
        timeout=60.0
    )

def install_browsers():
    """Playwright 브라우저 설치"""
    try:
        print("Playwright 브라우저 설치 중...")
        os.system("playwright install chromium --force")
        print("브라우저 설치 완료!")
        return True
    except Exception as e:
        print(f"브라우저 설치 실패: {e}")
        return False

def create_browser_for_thread():
    """스레드별 독립적인 브라우저 인스턴스 생성"""
    try:
        print(f"[Thread {threading.current_thread().name}] 브라우저 인스턴스 생성 중...")
        
        playwright = sync_playwright().start()
        
        # 브라우저 실행 옵션
        browser_args = [
            '--no-sandbox',
            '--disable-dev-shm-usage',
            '--disable-gpu',
            '--disable-extensions',
            '--disable-web-security',
            '--disable-blink-features=AutomationControlled',
            '--disable-background-timer-throttling',
            '--disable-backgrounding-occluded-windows',
            '--disable-renderer-backgrounding',
            '--disable-features=TranslateUI',
            '--disable-ipc-flooding-protection'
        ]
        
        browser = playwright.chromium.launch(
            headless=True,
            args=browser_args
        )
        
        print(f"[Thread {threading.current_thread().name}] 브라우저 인스턴스 생성 완료")
        return playwright, browser
        
    except Exception as e:
        print(f"[Thread {threading.current_thread().name}] 브라우저 인스턴스 생성 실패: {str(e)}")
        print(f"상세 오류: {traceback.format_exc()}")
        return None, None

def capture_google_trends() -> Optional[Image.Image]:
    """Google Trends 스크린샷 캡처 (독립 브라우저 인스턴스 사용)"""
    playwright = None
    browser = None
    page = None
    
    try:
        print("Google Trends 스크린샷 시작...")
        
        # 스레드별 독립 브라우저 생성
        playwright, browser = create_browser_for_thread()
        if not browser:
            print("브라우저 초기화 실패")
            return None
        
        page = browser.new_page()
        page.set_viewport_size({"width": 1400, "height": 900})
        page.set_extra_http_headers({
            "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
            "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",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
        })
        
        url = "https://trends.google.co.kr/trending?geo=KR"
        print(f"페이지 접속: {url}")
        
        # 페이지 로딩 (networkidle로 JavaScript 완료까지 대기)
        try:
            page.goto(url, wait_until="networkidle", timeout=45000)
            print("페이지 로딩 완료")
        except:
            # networkidle 실패시 domcontentloaded로 재시도
            page.goto(url, wait_until="domcontentloaded", timeout=30000)
            print("기본 페이지 로딩 완료")
        
        # 초기 대기
        time.sleep(5)
        
        # 쿠키 팝업 처리
        try:
            accept_button = page.locator('button:has-text("모두 수락"), button:has-text("Accept all"), button:has-text("수락")')
            if accept_button.count() > 0:
                accept_button.first.click(timeout=3000)
                print("쿠키 팝업 처리됨")
                time.sleep(3)
        except Exception as e:
            print(f"쿠키 팝업 처리 실패 (무시): {e}")
        
        # 트렌드 데이터 로딩 대기
        try:
            # 트렌드 컨텐츠가 로드될 때까지 대기
            page.wait_for_selector("main, .trending-content, .trends-wrapper, [data-topic]", timeout=15000)
            print("트렌드 컨텐츠 로딩 확인됨")
        except:
            print("트렌드 요소를 찾을 수 없음, 계속 진행")
        
        # 추가 대기 (동적 컨텐츠 완전 로딩)
        time.sleep(5)
        
        # 핵심 영역 스크린샷
        print("스크린샷 캡처 시작...")
        try:
            main_content = page.locator('main, .main-content, [data-ve-type="main"], .trends-wrapper')
            if main_content.count() > 0 and main_content.first.is_visible():
                screenshot_bytes = main_content.first.screenshot(timeout=15000)
                print("메인 컨텐츠 스크린샷 캡처됨")
            else:
                screenshot_bytes = page.screenshot(timeout=20000)
                print("전체 페이지 스크린샷 캡처됨")
        except Exception as e:
            print(f"스크린샷 캡처 재시도: {e}")
            screenshot_bytes = page.screenshot(timeout=20000)
            print("재시도 스크린샷 캡처 완료")
        
        screenshot = Image.open(io.BytesIO(screenshot_bytes))
        print("Google Trends 스크린샷 완료")
        return screenshot
        
    except Exception as e:
        print(f"Google Trends 스크린샷 실패: {str(e)}")
        print(f"상세 오류: {traceback.format_exc()}")
        return None
    finally:
        # 리소스 정리
        if page:
            try:
                page.close()
            except:
                pass
        if browser:
            try:
                browser.close()
            except:
                pass
        if playwright:
            try:
                playwright.stop()
            except:
                pass

def capture_ezme_trends() -> Optional[Image.Image]:
    """rank.ezme.net 스크린샷 캡처 (독립 브라우저 인스턴스 사용)"""
    playwright = None
    browser = None
    page = None
    
    try:
        print("rank.ezme.net 스크린샷 시작...")
        
        # 스레드별 독립 브라우저 생성
        playwright, browser = create_browser_for_thread()
        if not browser:
            print("브라우저 초기화 실패")
            return None
        
        page = browser.new_page()
        page.set_viewport_size({"width": 1400, "height": 1200})
        page.set_extra_http_headers({
            "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
            "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",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
        })
        
        url = "https://rank.ezme.net/diff/"
        print(f"페이지 접속: {url}")
        
        # 페이지 로딩 (networkidle 대기로 JavaScript 로딩 완료까지 대기)
        try:
            page.goto(url, wait_until="networkidle", timeout=45000)
            print("페이지 로딩 완료")
        except:
            # networkidle 실패시 domcontentloaded로 재시도
            page.goto(url, wait_until="domcontentloaded", timeout=30000)
            print("기본 페이지 로딩 완료")
        
        # JavaScript 실행 및 데이터 로딩 대기
        print("동적 컨텐츠 로딩 대기중...")
        time.sleep(8)  # 더 긴 대기 시간
        
        # 테이블 데이터가 로드되었는지 확인
        try:
            # 시간별 데이터 테이블 요소 대기
            page.wait_for_selector("table, .table, td", timeout=10000)
            print("테이블 데이터 로딩 확인됨")
        except:
            print("테이블 요소를 찾을 수 없음, 계속 진행")
        
        # 페이지 중간으로 스크롤 (시간별 검색어 데이터 영역)
        try:
            page.evaluate("window.scrollTo(0, document.body.scrollHeight * 0.4)")
            time.sleep(3)
            print("스크롤 완료")
        except Exception as e:
            print(f"스크롤 실패: {e}")
        
        # 전체 페이지 스크린샷
        print("스크린샷 캡처 시작...")
        screenshot_bytes = page.screenshot(full_page=True, timeout=20000)
        screenshot = Image.open(io.BytesIO(screenshot_bytes))
        print("rank.ezme.net 스크린샷 완료")
        return screenshot
        
    except Exception as e:
        print(f"rank.ezme.net 스크린샷 실패: {str(e)}")
        print(f"상세 오류: {traceback.format_exc()}")
        return None
    finally:
        # 리소스 정리
        if page:
            try:
                page.close()
            except:
                pass
        if browser:
            try:
                browser.close()
            except:
                pass
        if playwright:
            try:
                playwright.stop()
            except:
                pass

def analyze_google_trends_with_claude(image: Image.Image) -> str:
    """Google Trends 이미지 Claude 분석"""
    if image is None:
        return "Google Trends 분석할 이미지가 없습니다."
    
    try:
        client = get_claude_client()
        
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        image_base64 = base64.b64encode(buffer.getvalue()).decode()
        
        prompt = """Google Trends 실시간 인기 검색어 스크린샷을 분석해주세요.

다음 항목들을 중심으로 한국어로 분석해주세요:

### A. 상위 검색어 순위 (검색량 기준)
검색량이 높은 순서대로 정렬하여 나열:
**1위.** 검색어명 (검색량)
**2위.** 검색어명 (검색량)
**3위.** 검색어명 (검색량)
(이런 식으로 10위까지)

### B. 주요 트렌드 키워드
- 특히 주목할 만한 검색어들
- 검색량 증가 패턴

### C. 카테고리별 특징
- 엔터테인먼트, 뉴스, 스포츠 등 분야별 동향

### D. 특이사항 및 분석
- 급상승 키워드
- 시기적/사회적 배경 추측
- 전체적인 관심사 패턴

**중요 지시사항:**
- 마크다운 형식을 사용하여 **굵은 글씨**, *기울임*, `코드` 등으로 중요한 내용을 강조해주세요
- 검색어명, 수치, 카테고리명 등 핵심 정보는 **굵은 글씨**로 표시
- 급상승률이나 특별한 수치는 `백틱`으로 감싸서 강조
- 상위 검색어는 반드시 검색량 순서대로 1위부터 10위까지 정렬"""

        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1200,
            messages=[
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": prompt
                        },
                        {
                            "type": "image",
                            "source": {
                                "type": "base64",
                                "media_type": "image/png",
                                "data": image_base64
                            }
                        }
                    ]
                }
            ]
        )
        
        return response.content[0].text
        
    except Exception as e:
        return f"Google Trends Claude 분석 실패: {str(e)}"

def analyze_ezme_trends_with_claude(image: Image.Image) -> str:
    """rank.ezme.net 이미지 Claude 분석"""
    if image is None:
        return "rank.ezme.net 분석할 이미지가 없습니다."
    
    try:
        client = get_claude_client()
        
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        image_base64 = base64.b64encode(buffer.getvalue()).decode()
        
        prompt = """한국 포털사이트별 시간별 실시간 검색어 데이터를 분석해주세요.

다음 항목들을 중심으로 한국어로 분석해주세요:

### A. 도메인별 주요 검색어
**Zum (줌) 주요 검색어:**
- 상위 검색어와 진입 횟수
- 주요 관심 분야

**Nate (네이트) 주요 검색어:**
- 상위 검색어와 진입 횟수
- 주요 관심 분야

**Google 한국 주요 검색어:**
- 상위 검색어와 진입 횟수
- 주요 관심 분야

### B. 시간대별 트렌드 변화
- 최근 몇 시간 동안의 변화 패턴
- 시간대별 주요 이슈 변화

### C. 검색어 진입 횟수 분석
- 가장 높은 진입 횟수를 기록한 검색어들
- 지속적으로 상위권을 유지하는 검색어들

### D. 포털별 관심사 차이점
- 각 포털사이트별 특성 분석
- 사용자층별 관심사 차이

**중요 지시사항:**
- 마크다운 형식을 사용하여 **굵은 글씨**로 검색어명 강조
- 진입 횟수는 `백틱`으로 감싸서 표시 (예: `진입횟수: 15회`)
- 시간별 데이터가 있다면 구체적으로 언급
- 각 포털의 특징을 명확히 구분하여 분석"""

        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1200,
            messages=[
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": prompt
                        },
                        {
                            "type": "image",
                            "source": {
                                "type": "base64",
                                "media_type": "image/png",
                                "data": image_base64
                            }
                        }
                    ]
                }
            ]
        )
        
        return response.content[0].text
        
    except Exception as e:
        return f"rank.ezme.net Claude 분석 실패: {str(e)}"

def combine_analysis_results(google_analysis: str, ezme_analysis: str) -> str:
    """Google Trends와 ezme 분석 결과 통합 (오류 상황 대응)"""
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    
    # 성공/실패 상태 확인
    google_failed = "분석할 이미지가 없습니다" in google_analysis or "캡처에 실패" in google_analysis
    ezme_failed = "분석할 이미지가 없습니다" in ezme_analysis or "캡처에 실패" in ezme_analysis
    
    # 상태 메시지 생성
    if google_failed and ezme_failed:
        status_msg = "**모든 데이터 소스의 수집에 실패했습니다.** 잠시 후 다시 시도해주세요."
    elif google_failed:
        status_msg = "**Google Trends 데이터 수집에 실패했습니다.** 한국 포털 데이터만 제공됩니다."
    elif ezme_failed:
        status_msg = "**한국 포털 데이터 수집에 실패했습니다.** Google Trends 데이터만 제공됩니다."
    else:
        status_msg = "**모든 데이터 소스를 성공적으로 수집했습니다.**"
    
    combined_result = f"""# 실시간 검색어 통합 분석

**최종 업데이트:** {timestamp}

{status_msg}

---

## Google Trends 글로벌 동향

{google_analysis}

---

## 한국 포털 실시간 검색어

{ezme_analysis}
"""
    
    return combined_result

def perform_parallel_analysis() -> Tuple[bool, str]:
    """병렬 처리로 통합 분석 수행 (개선된 오류 처리)"""
    global analyzed_data, last_update_time, is_updating
    
    is_updating = True
    print(f"\n{'='*60}")
    print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 통합 실시간 트렌드 분석 시작")
    print(f"{'='*60}")
    
    try:
        # 브라우저 설치 확인
        print("1. 브라우저 설치 확인...")
        install_browsers()
        
        # 병렬 스크린샷 캡처
        print("2. 병렬 스크린샷 캡처 시작...")
        
        with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
            print("   - Google Trends 스크린샷 작업 시작")
            google_future = executor.submit(capture_google_trends)
            
            print("   - rank.ezme.net 스크린샷 작업 시작")
            ezme_future = executor.submit(capture_ezme_trends)
            
            print("   - 두 작업 완료 대기 중...")
            google_screenshot = google_future.result()
            ezme_screenshot = ezme_future.result()
        
        # 스크린샷 결과 확인
        print("3. 스크린샷 결과 확인...")
        google_success = google_screenshot is not None
        ezme_success = ezme_screenshot is not None
        
        print(f"   - Google Trends 스크린샷: {'성공' if google_success else '실패'}")
        print(f"   - rank.ezme.net 스크린샷: {'성공' if ezme_success else '실패'}")
        
        if not google_success and not ezme_success:
            error_msg = "모든 스크린샷 캡처에 실패했습니다."
            print(f"   ERROR: {error_msg}")
            return False, error_msg
        
        print("4. Claude 분석 시작...")
        
        # Claude 분석 (순차 처리 - API 제한)
        if google_success:
            print("   - Google Trends 분석 중...")
            google_analysis = analyze_google_trends_with_claude(google_screenshot)
            print("   - Google Trends 분석 완료")
        else:
            google_analysis = "Google Trends 스크린샷 캡처에 실패하여 분석할 수 없습니다."
            
        if ezme_success:
            print("   - rank.ezme.net 분석 중...")
            ezme_analysis = analyze_ezme_trends_with_claude(ezme_screenshot)
            print("   - rank.ezme.net 분석 완료")
        else:
            ezme_analysis = "rank.ezme.net 스크린샷 캡처에 실패하여 분석할 수 없습니다."
        
        print("5. 통합 분석 결과 생성...")
        
        # 통합 분석 결과 생성
        combined_analysis = combine_analysis_results(google_analysis, ezme_analysis)
        
        # 결과 저장
        analyzed_data["통합실시간"] = {
            'analysis': combined_analysis,
            'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'google_analysis': google_analysis,
            'ezme_analysis': ezme_analysis,
            'google_success': google_success,
            'ezme_success': ezme_success
        }
        
        last_update_time = datetime.now()
        
        success_count = sum([google_success, ezme_success])
        print(f"통합 분석 완료 - {last_update_time.strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"성공률: {success_count}/2 소스")
        
        return True, combined_analysis
        
    except Exception as e:
        error_msg = f"통합 분석 중 예상치 못한 오류: {str(e)}"
        print(f"ERROR: {error_msg}")
        print(f"상세 오류: {traceback.format_exc()}")
        return False, error_msg
    finally:
        is_updating = False
        print(f"{'='*60}\n")

def auto_update_scheduler():
    """1시간마다 자동 업데이트"""
    while True:
        try:
            print(f"[스케줄러] 다음 업데이트까지 1시간 대기 중...")
            time.sleep(3600)  # 1시간 대기
            
            if not is_updating:
                print(f"[스케줄러] 자동 업데이트 시작")
                perform_parallel_analysis()
            else:
                print(f"[스케줄러] 이미 업데이트 중이므로 건너뜀")
                
        except Exception as e:
            print(f"[스케줄러] 오류: {e}")
            time.sleep(300)  # 5분 후 재시도

def get_cached_analysis_result() -> tuple:
    """저장된 통합 분석 결과 반환"""
    global analyzed_data, last_update_time
    
    if "통합실시간" in analyzed_data:
        data = analyzed_data["통합실시간"]
        status = f"최신 데이터 (업데이트: {data['timestamp']})"
        return data['analysis'], status
    else:
        if last_update_time:
            status = f"데이터 없음 (마지막 업데이트: {last_update_time.strftime('%H:%M:%S')})"
        else:
            status = "아직 분석 데이터가 준비되지 않았습니다."
        
        return "**분석 데이터가 아직 준비되지 않았습니다.**\n\n잠시 후 다시 시도해주세요.", status

def create_interface():
    """Gradio 인터페이스 생성"""
    
    def show_cached_analysis():
        """저장된 통합 분석 결과 표시"""
        if "통합실시간" in analyzed_data:
            data = analyzed_data["통합실시간"]
            status = f"캐시된 통합 분석 결과 (업데이트: {data['timestamp']})"
            return data['analysis'], status
        else:
            if last_update_time:
                status = f"저장된 데이터 없음 (마지막 업데이트: {last_update_time.strftime('%H:%M:%S')})"
            else:
                status = "아직 분석 데이터가 준비되지 않았습니다. 잠시 후 다시 시도해주세요."
            
            return "**저장된 통합 분석 데이터가 없습니다.**\n\n'실시간 업데이트' 버튼을 눌러주세요.", status
    
    def perform_live_update():
        """실시간 업데이트 수행"""
        if is_updating:
            return "**현재 업데이트가 진행 중입니다.**\n\n잠시 후 다시 시도해주세요.", "업데이트 진행 중..."
        
        print("실시간 통합 업데이트 요청됨")
        success, result = perform_parallel_analysis()
        
        if success and "통합실시간" in analyzed_data:
            data = analyzed_data["통합실시간"]
            return data['analysis'], f"실시간 업데이트 완료 - {datetime.now().strftime('%H:%M:%S')}"
        else:
            return "**업데이트에 실패했습니다.**\n\n잠시 후 다시 시도해주세요.", f"업데이트 실패 - {datetime.now().strftime('%H:%M:%S')}"
    
    # Gradio 인터페이스 구성
    with gr.Blocks(
        title="통합 실시간 검색어 분석", 
        theme=gr.themes.Soft(),
        css="""
        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
        
        * {
            font-family: 'Noto Sans KR', sans-serif !important;
        }
        
        .markdown-content {
            font-size: 14px;
            line-height: 1.6;
            background: #f8f9fa;
            padding: 20px;
            border-radius: 8px;
            border: 1px solid #e9ecef;
        }
        
        .markdown-content h1 {
            color: #1d4ed8;
            margin-bottom: 15px;
        }
        
        .markdown-content h2 {
            color: #2563eb;
            margin-top: 25px;
            margin-bottom: 12px;
        }
        
        .markdown-content h3 {
            color: #3b82f6;
            margin-top: 20px;
            margin-bottom: 10px;
        }
        
        .markdown-content strong {
            color: #1d4ed8;
            font-weight: 600;
        }
        """
    ) as interface:
        
        gr.Markdown("# 통합 실시간 검색어 분석")
        gr.Markdown("Google Trends와 한국 포털사이트 검색어를 통합 분석합니다.")
        
        with gr.Row():
            with gr.Column(scale=1):
                gr.Markdown("### 분석 옵션")
                
                with gr.Column():
                    cached_btn = gr.Button("통합 검색어 분석", variant="primary", size="lg")
                    gr.Markdown("저장된 통합 분석 결과를 즉시 표시합니다.")
                
                gr.Markdown("---")
                
                with gr.Column():
                    live_btn = gr.Button("실시간 업데이트", variant="secondary", size="lg")
                    gr.Markdown("Google Trends + 한국 포털 최신 데이터로 업데이트합니다. (약 1분 소요)")
                
                gr.Markdown("---")
                
                status_output = gr.Textbox(
                    label="상태",
                    value="분석할 옵션을 선택해주세요.",
                    interactive=False
                )
                
                # Apeach 이미지 표시
                try:
                    gr.Image(
                        value="apeach.png",
                        label=None,
                        show_label=False,
                        interactive=False,
                        height=200,
                        width=200
                    )
                except:
                    pass
            
            with gr.Column(scale=2):
                analysis_output = gr.Markdown(
                    value="**분석 버튼을 눌러주세요.**\n\n- **통합 검색어 분석**: 저장된 결과 즉시 확인\n- **실시간 업데이트**: 최신 데이터로 새로 분석",
                    elem_classes=["markdown-content"],
                    height=700
                )
        
        # 이벤트 연결
        cached_btn.click(
            fn=show_cached_analysis,
            outputs=[analysis_output, status_output]
        )
        
        live_btn.click(
            fn=perform_live_update,
            outputs=[analysis_output, status_output]
        )
    
    return interface

def main():
    """메인 함수"""
    print("=" * 60)
    print("통합 실시간 검색어 분석 서비스 시작!")
    print("=" * 60)
    
    # API 키 확인
    api_key = os.getenv("ANTHROPIC_API_KEY")
    if not api_key:
        print("ANTHROPIC_API_KEY 환경변수가 설정되지 않았습니다.")
        print("  .env 파일에 ANTHROPIC_API_KEY=your_key_here 를 추가하세요.")
        return
    else:
        print("Claude API 키 확인됨")
    
    # 초기 통합 분석 수행
    print("초기 통합 분석 시작...")
    initial_success, _ = perform_parallel_analysis()
    
    if initial_success:
        print("초기 통합 분석 완료")
    else:
        print("초기 분석 실패, 서비스는 계속 진행됩니다.")
    
    # 자동 업데이트 스케줄러 시작
    print("자동 업데이트 스케줄러 시작 (1시간 간격)")
    global update_thread
    update_thread = threading.Thread(target=auto_update_scheduler, daemon=True)
    update_thread.start()
    
    print("모든 준비 완료!")
    print("=" * 60)
    
    # Gradio 인터페이스 실행
    try:
        interface = create_interface()
        interface.launch(
            server_name="0.0.0.0",
            server_port=7860,
            share=False,
            show_error=True,
            quiet=False
        )
    except Exception as e:
        print(f"인터페이스 시작 실패: {e}")
        print(f"상세 오류: {traceback.format_exc()}")

if __name__ == "__main__":
    main()