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()