hanjunjung commited on
Commit
bf40342
Β·
1 Parent(s): f4094f5
Files changed (1) hide show
  1. app.py +457 -113
app.py CHANGED
@@ -5,8 +5,10 @@ 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
@@ -23,6 +25,10 @@ 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")
@@ -46,44 +52,113 @@ def install_browsers():
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=[
 
 
65
  '--no-sandbox',
66
  '--disable-dev-shm-usage',
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
  # μΏ ν‚€ νŒμ—… 처리
@@ -91,62 +166,133 @@ def capture_trends_screenshot() -> Optional[Image.Image]:
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
 
144
- # 이미지λ₯Ό base64둜 인코딩
145
  buffer = io.BytesIO()
146
  image.save(buffer, format="PNG")
147
  image_base64 = base64.b64encode(buffer.getvalue()).decode()
148
 
149
- # 뢄석 ν”„λ‘¬ν”„νŠΈ
150
  prompt = """Google Trends μ‹€μ‹œκ°„ 인기 검색어 μŠ€ν¬λ¦°μƒ·μ„ λΆ„μ„ν•΄μ£Όμ„Έμš”.
151
 
152
  λ‹€μŒ ν•­λͺ©λ“€μ„ μ€‘μ‹¬μœΌλ‘œ ν•œκ΅­μ–΄λ‘œ λΆ„μ„ν•΄μ£Όμ„Έμš”:
@@ -174,12 +320,87 @@ def analyze_with_claude(image: Image.Image) -> str:
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,54 +422,155 @@ def analyze_with_claude(image: Image.Image) -> str:
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")
@@ -262,7 +584,7 @@ def auto_update_scheduler():
262
 
263
  if not is_updating:
264
  print(f"[μŠ€μΌ€μ€„λŸ¬] μžλ™ μ—…λ°μ΄νŠΈ μ‹œμž‘")
265
- perform_analysis()
266
  else:
267
  print(f"[μŠ€μΌ€μ€„λŸ¬] 이미 μ—…λ°μ΄νŠΈ μ€‘μ΄λ―€λ‘œ κ±΄λ„ˆλœ€")
268
 
@@ -270,12 +592,12 @@ def auto_update_scheduler():
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:
@@ -284,16 +606,16 @@ def get_analysis_result() -> tuple:
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:
@@ -301,25 +623,25 @@ def create_interface():
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');
@@ -337,8 +659,19 @@ def create_interface():
337
  border: 1px solid #e9ecef;
338
  }
339
 
340
- .markdown-content h3 {
 
 
 
 
 
341
  color: #2563eb;
 
 
 
 
 
 
342
  margin-top: 20px;
343
  margin-bottom: 10px;
344
  }
@@ -350,19 +683,22 @@ def create_interface():
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
 
@@ -373,20 +709,23 @@ def create_interface():
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
  # 이벀트 μ—°κ²°
@@ -405,7 +744,7 @@ def create_interface():
405
  def main():
406
  """메인 ν•¨μˆ˜"""
407
  print("=" * 60)
408
- print("Google Trends 인기 검색어 μ‹œμž‘!")
409
  print("=" * 60)
410
 
411
  # API ν‚€ 확인
@@ -417,12 +756,15 @@ def main():
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
 
@@ -448,6 +790,8 @@ def main():
448
  except Exception as e:
449
  print(f"μΈν„°νŽ˜μ΄μŠ€ μ‹œμž‘ μ‹€νŒ¨: {e}")
450
  print(f"상세 였λ₯˜: {traceback.format_exc()}")
 
 
451
 
452
  if __name__ == "__main__":
453
  main()
 
5
  import threading
6
  import atexit
7
  import traceback
8
+ import asyncio
9
+ import concurrent.futures
10
  from datetime import datetime
11
+ from typing import Optional, Dict, Any, Tuple
12
 
13
  import gradio as gr
14
  import anthropic
 
25
  update_thread = None
26
  is_updating = False
27
 
28
+ # μ „μ—­ λΈŒλΌμš°μ € μΈμŠ€ν„΄μŠ€
29
+ global_playwright = None
30
+ global_browser = None
31
+
32
  def get_claude_client():
33
  """Claude API ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™”"""
34
  api_key = os.getenv("ANTHROPIC_API_KEY")
 
52
  print(f"λΈŒλΌμš°μ € μ„€μΉ˜ μ‹€νŒ¨: {e}")
53
  return False
54
 
55
+ def get_shared_browser():
56
+ """곡유 λΈŒλΌμš°μ € μΈμŠ€ν„΄μŠ€ λ°˜ν™˜ (μž¬μ‚¬μš© μ΅œμ ν™” + μ•ˆμ •μ„± κ°•ν™”)"""
57
+ global global_playwright, global_browser
 
 
58
 
59
  try:
60
+ # Playwright μΈμŠ€ν„΄μŠ€ μ΄ˆκΈ°ν™”
61
+ if global_playwright is None:
62
+ print("Playwright μΈμŠ€ν„΄μŠ€ μ΄ˆκΈ°ν™”...")
63
+ global_playwright = sync_playwright().start()
64
+ print("Playwright μ΄ˆκΈ°ν™” μ™„λ£Œ")
65
 
66
+ # λΈŒλΌμš°μ € μΈμŠ€ν„΄μŠ€ 확인 및 생성
67
+ if global_browser is None or not global_browser.is_connected():
68
+ print("λΈŒλΌμš°μ € μΈμŠ€ν„΄μŠ€ 생성 쀑...")
69
+
70
+ # λΈŒλΌμš°μ € μ‹€ν–‰ μ˜΅μ…˜
71
+ browser_args = [
72
  '--no-sandbox',
73
  '--disable-dev-shm-usage',
74
  '--disable-gpu',
75
  '--disable-extensions',
76
  '--disable-web-security',
77
+ '--disable-blink-features=AutomationControlled',
78
+ '--disable-background-timer-throttling',
79
+ '--disable-backgrounding-occluded-windows',
80
+ '--disable-renderer-backgrounding',
81
+ '--disable-features=TranslateUI',
82
+ '--disable-ipc-flooding-protection'
83
  ]
84
+
85
+ global_browser = global_playwright.chromium.launch(
86
+ headless=True,
87
+ args=browser_args
88
+ )
89
+ print("λΈŒλΌμš°μ € μΈμŠ€ν„΄μŠ€ 생성 μ™„λ£Œ")
90
+
91
+ # μ—°κ²° μƒνƒœ μž¬ν™•μΈ
92
+ if global_browser.is_connected():
93
+ print("λΈŒλΌμš°μ € μ—°κ²° μƒνƒœ μ–‘ν˜Έ")
94
+ return global_browser
95
+ else:
96
+ print("λΈŒλΌμš°μ € μ—°κ²° μƒνƒœ λΆˆλŸ‰, μž¬μƒμ„± μ‹œλ„")
97
+ global_browser = None
98
+ return get_shared_browser() # μž¬κ·€ 호좜둜 μž¬μƒμ„±
99
+
100
+ except Exception as e:
101
+ print(f"λΈŒλΌμš°μ € μΈμŠ€ν„΄μŠ€ 생성 μ‹€νŒ¨: {str(e)}")
102
+ print(f"상세 였λ₯˜: {traceback.format_exc()}")
103
+
104
+ # μ‹€νŒ¨μ‹œ μ •λ¦¬ν•˜κ³  None λ°˜ν™˜
105
+ try:
106
+ if global_browser:
107
+ global_browser.close()
108
+ if global_playwright:
109
+ global_playwright.stop()
110
+ except:
111
+ pass
112
+
113
+ global_browser = None
114
+ global_playwright = None
115
+ return None
116
+
117
+ def cleanup_browser():
118
+ """λΈŒλΌμš°μ € 정리"""
119
+ global global_playwright, global_browser
120
+ try:
121
+ if global_browser:
122
+ global_browser.close()
123
+ global_browser = None
124
+ if global_playwright:
125
+ global_playwright.stop()
126
+ global_playwright = None
127
+ except:
128
+ pass
129
+
130
+ def capture_google_trends() -> Optional[Image.Image]:
131
+ """Google Trends μŠ€ν¬λ¦°μƒ· 캑처 (동적 λ‘œλ”© λŒ€μ‘)"""
132
+ page = None
133
+
134
+ try:
135
+ print("Google Trends μŠ€ν¬λ¦°μƒ· μ‹œμž‘...")
136
+ browser = get_shared_browser()
137
+ if not browser:
138
+ print("λΈŒλΌμš°μ € μ΄ˆκΈ°ν™” μ‹€νŒ¨")
139
+ return None
140
 
 
141
  page = browser.new_page()
142
  page.set_viewport_size({"width": 1400, "height": 900})
143
  page.set_extra_http_headers({
144
  "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
145
+ "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",
146
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
147
  })
148
 
 
149
  url = "https://trends.google.co.kr/trending?geo=KR"
150
+ print(f"νŽ˜μ΄μ§€ 접속: {url}")
151
+
152
+ # νŽ˜μ΄μ§€ λ‘œλ”© (networkidle둜 JavaScript μ™„λ£ŒκΉŒμ§€ λŒ€κΈ°)
153
+ try:
154
+ page.goto(url, wait_until="networkidle", timeout=45000)
155
+ print("νŽ˜μ΄μ§€ λ‘œλ”© μ™„λ£Œ")
156
+ except:
157
+ # networkidle μ‹€νŒ¨μ‹œ domcontentloaded둜 μž¬μ‹œλ„
158
+ page.goto(url, wait_until="domcontentloaded", timeout=30000)
159
+ print("κΈ°λ³Έ νŽ˜μ΄μ§€ λ‘œλ”© μ™„λ£Œ")
160
 
161
+ # 초기 λŒ€κΈ°
162
  time.sleep(5)
163
 
164
  # μΏ ν‚€ νŒμ—… 처리
 
166
  accept_button = page.locator('button:has-text("λͺ¨λ‘ 수락"), button:has-text("Accept all"), button:has-text("수락")')
167
  if accept_button.count() > 0:
168
  accept_button.first.click(timeout=3000)
169
+ print("μΏ ν‚€ νŒμ—… 처리됨")
170
+ time.sleep(3)
171
+ except Exception as e:
172
+ print(f"μΏ ν‚€ νŒμ—… 처리 μ‹€νŒ¨ (λ¬΄μ‹œ): {e}")
173
+
174
+ # νŠΈλ Œλ“œ 데이터 λ‘œλ”© λŒ€κΈ°
175
+ try:
176
+ # νŠΈλ Œλ“œ 컨텐츠가 λ‘œλ“œλ  λ•ŒκΉŒμ§€ λŒ€κΈ°
177
+ page.wait_for_selector("main, .trending-content, .trends-wrapper, [data-topic]", timeout=15000)
178
+ print("νŠΈλ Œλ“œ 컨텐츠 λ‘œλ”© 확인됨")
179
  except:
180
+ print("νŠΈλ Œλ“œ μš”μ†Œλ₯Ό 찾을 수 μ—†μŒ, 계속 μ§„ν–‰")
181
 
182
+ # μΆ”κ°€ λŒ€κΈ° (동적 컨텐츠 μ™„μ „ λ‘œλ”©)
183
+ time.sleep(5)
184
 
185
+ # 핡심 μ˜μ—­ μŠ€ν¬λ¦°μƒ·
186
+ print("μŠ€ν¬λ¦°μƒ· 캑처 μ‹œμž‘...")
187
  try:
188
  main_content = page.locator('main, .main-content, [data-ve-type="main"], .trends-wrapper')
189
  if main_content.count() > 0 and main_content.first.is_visible():
190
+ screenshot_bytes = main_content.first.screenshot(timeout=15000)
191
+ print("메인 컨텐츠 μŠ€ν¬λ¦°μƒ· 캑처됨")
192
  else:
193
+ screenshot_bytes = page.screenshot(timeout=20000)
194
+ print("전체 νŽ˜μ΄μ§€ μŠ€ν¬λ¦°μƒ· 캑처됨")
195
  except Exception as e:
196
  print(f"μŠ€ν¬λ¦°μƒ· 캑처 μž¬μ‹œλ„: {e}")
197
+ screenshot_bytes = page.screenshot(timeout=20000)
198
  print("μž¬μ‹œλ„ μŠ€ν¬λ¦°μƒ· 캑처 μ™„λ£Œ")
199
 
 
200
  screenshot = Image.open(io.BytesIO(screenshot_bytes))
201
+ print("Google Trends μŠ€ν¬λ¦°μƒ· μ™„λ£Œ")
202
  return screenshot
203
 
204
  except Exception as e:
205
+ print(f"Google Trends μŠ€ν¬λ¦°μƒ· μ‹€νŒ¨: {str(e)}")
206
+ print(f"상세 였λ₯˜: {traceback.format_exc()}")
207
  return None
208
  finally:
209
+ if page:
210
+ try:
 
211
  page.close()
212
+ except:
213
+ pass
214
+
215
+ def capture_ezme_trends() -> Optional[Image.Image]:
216
+ """rank.ezme.net μŠ€ν¬λ¦°μƒ· 캑처 (동적 λ‘œλ”© λŒ€μ‘)"""
217
+ page = None
218
+
219
+ try:
220
+ print("rank.ezme.net μŠ€ν¬λ¦°μƒ· μ‹œμž‘...")
221
+ browser = get_shared_browser()
222
+ if not browser:
223
+ print("λΈŒλΌμš°μ € μ΄ˆκΈ°ν™” μ‹€νŒ¨")
224
+ return None
225
+
226
+ page = browser.new_page()
227
+ page.set_viewport_size({"width": 1400, "height": 1200})
228
+ page.set_extra_http_headers({
229
+ "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
230
+ "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",
231
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
232
+ })
233
+
234
+ url = "https://rank.ezme.net/diff/"
235
+ print(f"νŽ˜μ΄μ§€ 접속: {url}")
236
+
237
+ # νŽ˜μ΄μ§€ λ‘œλ”© (networkidle λŒ€κΈ°λ‘œ JavaScript λ‘œλ”© μ™„λ£ŒκΉŒμ§€ λŒ€κΈ°)
238
+ try:
239
+ page.goto(url, wait_until="networkidle", timeout=45000)
240
+ print("νŽ˜μ΄μ§€ λ‘œλ”© μ™„λ£Œ")
241
+ except:
242
+ # networkidle μ‹€νŒ¨μ‹œ domcontentloaded둜 μž¬μ‹œλ„
243
+ page.goto(url, wait_until="domcontentloaded", timeout=30000)
244
+ print("κΈ°λ³Έ νŽ˜μ΄μ§€ λ‘œλ”© μ™„λ£Œ")
245
+
246
+ # JavaScript μ‹€ν–‰ 및 데이터 λ‘œλ”© λŒ€κΈ°
247
+ print("동적 컨텐츠 λ‘œλ”© λŒ€κΈ°μ€‘...")
248
+ time.sleep(8) # 더 κΈ΄ λŒ€κΈ° μ‹œκ°„
249
+
250
+ # ν…Œμ΄λΈ” 데이터가 λ‘œλ“œλ˜μ—ˆλŠ”μ§€ 확인
251
+ try:
252
+ # μ‹œκ°„λ³„ 데이터 ν…Œμ΄λΈ” μš”μ†Œ λŒ€κΈ°
253
+ page.wait_for_selector("table, .table, td", timeout=10000)
254
+ print("ν…Œμ΄λΈ” 데이터 λ‘œλ”© 확인됨")
255
+ except:
256
+ print("ν…Œμ΄λΈ” μš”μ†Œλ₯Ό 찾을 수 μ—†μŒ, 계속 μ§„ν–‰")
257
+
258
+ # νŽ˜μ΄μ§€ μ€‘κ°„μœΌλ‘œ 슀크둀 (μ‹œκ°„λ³„ 검색어 데이터 μ˜μ—­)
259
+ try:
260
+ page.evaluate("window.scrollTo(0, document.body.scrollHeight * 0.4)")
261
+ time.sleep(3)
262
+ print("슀크둀 μ™„λ£Œ")
263
  except Exception as e:
264
+ print(f"슀크둀 μ‹€νŒ¨: {e}")
265
+
266
+ # 전체 νŽ˜μ΄μ§€ μŠ€ν¬λ¦°μƒ·
267
+ print("μŠ€ν¬λ¦°μƒ· 캑처 μ‹œμž‘...")
268
+ screenshot_bytes = page.screenshot(full_page=True, timeout=20000)
269
+ screenshot = Image.open(io.BytesIO(screenshot_bytes))
270
+ print("rank.ezme.net μŠ€ν¬λ¦°μƒ· μ™„λ£Œ")
271
+ return screenshot
272
+
273
+ except Exception as e:
274
+ print(f"rank.ezme.net μŠ€ν¬λ¦°μƒ· μ‹€νŒ¨: {str(e)}")
275
+ print(f"상세 였λ₯˜: {traceback.format_exc()}")
276
+ return None
277
+ finally:
278
+ if page:
279
+ try:
280
+ page.close()
281
+ except:
282
+ pass
283
 
284
+ def analyze_google_trends_with_claude(image: Image.Image) -> str:
285
+ """Google Trends 이미지 Claude 뢄석"""
286
  if image is None:
287
+ return "Google Trends 뢄석할 이미지가 μ—†μŠ΅λ‹ˆλ‹€."
288
 
289
  try:
290
  client = get_claude_client()
291
 
 
292
  buffer = io.BytesIO()
293
  image.save(buffer, format="PNG")
294
  image_base64 = base64.b64encode(buffer.getvalue()).decode()
295
 
 
296
  prompt = """Google Trends μ‹€μ‹œκ°„ 인기 검색어 μŠ€ν¬λ¦°μƒ·μ„ λΆ„μ„ν•΄μ£Όμ„Έμš”.
297
 
298
  λ‹€μŒ ν•­λͺ©λ“€μ„ μ€‘μ‹¬μœΌλ‘œ ν•œκ΅­μ–΄λ‘œ λΆ„μ„ν•΄μ£Όμ„Έμš”:
 
320
  - λ§ˆν¬λ‹€μš΄ ν˜•μ‹μ„ μ‚¬μš©ν•˜μ—¬ **ꡡ은 글씨**, *κΈ°μšΈμž„*, `μ½”λ“œ` λ“±μœΌλ‘œ μ€‘μš”ν•œ λ‚΄μš©μ„ κ°•μ‘°ν•΄μ£Όμ„Έμš”
321
  - 검색어λͺ…, 수치, μΉ΄ν…Œκ³ λ¦¬λͺ… λ“± 핡심 μ •λ³΄λŠ” **ꡡ은 글씨**둜 ν‘œμ‹œ
322
  - κΈ‰μƒμŠΉλ₯ μ΄λ‚˜ νŠΉλ³„ν•œ μˆ˜μΉ˜λŠ” `λ°±ν‹±`으둜 κ°μ‹Έμ„œ κ°•μ‘°
323
+ - μƒμœ„ κ²€μƒ‰μ–΄λŠ” λ°˜λ“œμ‹œ κ²€μƒ‰λŸ‰ μˆœμ„œλŒ€λ‘œ 1μœ„λΆ€ν„° 10μœ„κΉŒμ§€ μ •λ ¬"""
324
+
325
+ response = client.messages.create(
326
+ model="claude-3-5-sonnet-20241022",
327
+ max_tokens=1200,
328
+ messages=[
329
+ {
330
+ "role": "user",
331
+ "content": [
332
+ {
333
+ "type": "text",
334
+ "text": prompt
335
+ },
336
+ {
337
+ "type": "image",
338
+ "source": {
339
+ "type": "base64",
340
+ "media_type": "image/png",
341
+ "data": image_base64
342
+ }
343
+ }
344
+ ]
345
+ }
346
+ ]
347
+ )
348
+
349
+ return response.content[0].text
350
+
351
+ except Exception as e:
352
+ return f"Google Trends Claude 뢄석 μ‹€νŒ¨: {str(e)}"
353
+
354
+ def analyze_ezme_trends_with_claude(image: Image.Image) -> str:
355
+ """rank.ezme.net 이미지 Claude 뢄석"""
356
+ if image is None:
357
+ return "rank.ezme.net 뢄석할 이미지가 μ—†μŠ΅λ‹ˆλ‹€."
358
+
359
+ try:
360
+ client = get_claude_client()
361
+
362
+ buffer = io.BytesIO()
363
+ image.save(buffer, format="PNG")
364
+ image_base64 = base64.b64encode(buffer.getvalue()).decode()
365
+
366
+ prompt = """ν•œκ΅­ ν¬ν„Έμ‚¬μ΄νŠΈλ³„ μ‹œκ°„λ³„ μ‹€μ‹œκ°„ 검색어 데이터λ₯Ό λΆ„μ„ν•΄μ£Όμ„Έμš”.
367
+
368
+ λ‹€μŒ ν•­λͺ©λ“€μ„ μ€‘μ‹¬μœΌλ‘œ ν•œκ΅­μ–΄λ‘œ λΆ„μ„ν•΄μ£Όμ„Έμš”:
369
+
370
+ ### A. 도메인별 μ£Όμš” 검색어
371
+ **Zum (쀌) μ£Όμš” 검색어:**
372
+ - μƒμœ„ 검색어와 μ§„μž… 횟수
373
+ - μ£Όμš” 관심 λΆ„μ•Ό
374
+
375
+ **Nate (λ„€μ΄νŠΈ) μ£Όμš” 검색어:**
376
+ - μƒμœ„ 검색어와 μ§„μž… 횟수
377
+ - μ£Όμš” 관심 λΆ„μ•Ό
378
+
379
+ **Google ν•œκ΅­ μ£Όμš” 검색어:**
380
+ - μƒμœ„ 검색어와 μ§„μž… 횟수
381
+ - μ£Όμš” 관심 λΆ„μ•Ό
382
+
383
+ ### B. μ‹œκ°„λŒ€λ³„ νŠΈλ Œλ“œ λ³€ν™”
384
+ - 졜근 λͺ‡ μ‹œκ°„ λ™μ•ˆμ˜ λ³€ν™” νŒ¨ν„΄
385
+ - μ‹œκ°„λŒ€λ³„ μ£Όμš” 이슈 λ³€ν™”
386
+
387
+ ### C. 검색어 μ§„μž… 횟수 뢄석
388
+ - κ°€μž₯ 높은 μ§„οΏ½οΏ½ 횟수λ₯Ό κΈ°λ‘ν•œ 검색어듀
389
+ - μ§€μ†μ μœΌλ‘œ μƒμœ„κΆŒμ„ μœ μ§€ν•˜λŠ” 검색어듀
390
+
391
+ ### D. 포털별 관심사 차이점
392
+ - 각 ν¬ν„Έμ‚¬μ΄νŠΈλ³„ νŠΉμ„± 뢄석
393
+ - μ‚¬μš©μžμΈ΅λ³„ 관심사 차이
394
+
395
+ **μ€‘μš” μ§€μ‹œμ‚¬ν•­:**
396
+ - λ§ˆν¬λ‹€μš΄ ν˜•μ‹μ„ μ‚¬μš©ν•˜μ—¬ **ꡡ은 글씨**둜 검색어λͺ… κ°•μ‘°
397
+ - μ§„μž… νšŸμˆ˜λŠ” `λ°±ν‹±`으둜 κ°μ‹Έμ„œ ν‘œμ‹œ (예: `μ§„μž…νšŸμˆ˜: 15회`)
398
+ - μ‹œκ°„λ³„ 데이터가 μžˆλ‹€λ©΄ ꡬ체적으둜 μ–ΈκΈ‰
399
+ - 각 ν¬ν„Έμ˜ νŠΉμ§•μ„ λͺ…ν™•νžˆ κ΅¬λΆ„ν•˜μ—¬ 뢄석"""
400
 
401
  response = client.messages.create(
402
  model="claude-3-5-sonnet-20241022",
403
+ max_tokens=1200,
404
  messages=[
405
  {
406
  "role": "user",
 
422
  ]
423
  )
424
 
425
+ return response.content[0].text
 
 
426
 
427
  except Exception as e:
428
+ return f"rank.ezme.net Claude 뢄석 μ‹€νŒ¨: {str(e)}"
429
+
430
+ def combine_analysis_results(google_analysis: str, ezme_analysis: str) -> str:
431
+ """Google Trends와 ezme 뢄석 κ²°κ³Ό 톡합 (였λ₯˜ 상황 λŒ€μ‘)"""
432
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
433
+
434
+ # 성곡/μ‹€νŒ¨ μƒνƒœ 확인
435
+ google_failed = "뢄석할 이미지가 μ—†μŠ΅λ‹ˆλ‹€" in google_analysis or "μΊ‘μ²˜μ— μ‹€νŒ¨" in google_analysis
436
+ ezme_failed = "뢄석할 이미지가 μ—†μŠ΅λ‹ˆλ‹€" in ezme_analysis or "μΊ‘μ²˜μ— μ‹€νŒ¨" in ezme_analysis
437
+
438
+ # μƒνƒœ λ©”μ‹œμ§€ 생성
439
+ if google_failed and ezme_failed:
440
+ status_msg = "**λͺ¨λ“  데이터 μ†ŒμŠ€μ˜ μˆ˜μ§‘μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.** μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."
441
+ elif google_failed:
442
+ status_msg = "**Google Trends 데이터 μˆ˜μ§‘μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.** ν•œκ΅­ 포털 λ°μ΄ν„°λ§Œ μ œκ³΅λ©λ‹ˆλ‹€."
443
+ elif ezme_failed:
444
+ status_msg = "**ν•œκ΅­ 포털 데이터 μˆ˜μ§‘μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.** Google Trends λ°μ΄ν„°λ§Œ μ œκ³΅λ©λ‹ˆλ‹€."
445
+ else:
446
+ status_msg = "**λͺ¨λ“  데이터 μ†ŒμŠ€λ₯Ό μ„±κ³΅μ μœΌλ‘œ μˆ˜μ§‘ν–ˆμŠ΅λ‹ˆλ‹€.**"
447
+
448
+ combined_result = f"""# μ‹€μ‹œκ°„ 검색어 톡합 뢄석
449
+
450
+ **μ΅œμ’… μ—…λ°μ΄νŠΈ:** {timestamp}
451
+
452
+ {status_msg}
453
 
454
+ ---
455
+
456
+ ## Google Trends κΈ€λ‘œλ²Œ 동ν–₯
457
+
458
+ {google_analysis}
459
+
460
+ ---
461
+
462
+ ## ν•œκ΅­ 포털 μ‹€μ‹œκ°„ 검색어
463
+
464
+ {ezme_analysis}
465
+
466
+ ---
467
+
468
+ ## μ’…ν•© 뢄석 및 μΈμ‚¬μ΄νŠΈ
469
+
470
+ ### μ£Όμš” 곡톡 이슈
471
+ - Google Trends와 ν•œκ΅­ ν¬ν„Έμ—μ„œ κ³΅ν†΅μ μœΌλ‘œ μƒμœ„κΆŒμ— λ‚˜νƒ€λ‚˜λŠ” 검색어듀을 톡해 ν˜„μž¬ κ°€μž₯ 뜨거운 이슈λ₯Ό νŒŒμ•…ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
472
+
473
+ ### ν”Œλž«νΌλ³„ νŠΉμ„±
474
+ - **Google Trends**: 전세계적 관심사와 μž₯기적 νŠΈλ Œλ“œ 반영
475
+ - **ν•œκ΅­ 포털 (Zum, Nate, Google)**: ν•œκ΅­ μ‚¬μš©μžμ˜ μ‹€μ‹œκ°„ 관심사와 μ¦‰μ‹œμ„± μžˆλŠ” 이슈 반영
476
+
477
+ ### μ‹œκ°„λŒ€λ³„ 검색 νŒ¨ν„΄
478
+ - 각 μ‹œκ°„λŒ€λ³„λ‘œ λ‚˜νƒ€λ‚˜λŠ” 검색어 λ³€ν™”λ₯Ό 톡해 ν•œκ΅­ μ‚¬μš©μžλ“€μ˜ 일상 νŒ¨ν„΄κ³Ό 관심사 λ³€ν™”λ₯Ό κ΄€μ°°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
479
+
480
+ ### 데이터 μˆ˜μ§‘ 정보
481
+ - **μˆ˜μ§‘ 방식**: Playwrightλ₯Ό ν†΅ν•œ μŠ€ν¬λ¦°μƒ· + Claude AI 뢄석
482
+ - **μ—…λ°μ΄νŠΈ μ£ΌκΈ°**: 1μ‹œκ°„λ§ˆλ‹€ μžλ™ μ—…λ°μ΄νŠΈ
483
+ - **뢄석 λ²”μœ„**: Google Trends(κΈ€λ‘œλ²Œ) + rank.ezme.net(ν•œκ΅­ 포털 3κ³³)
484
+ """
485
+
486
+ return combined_result
487
+
488
+ def perform_parallel_analysis() -> Tuple[bool, str]:
489
+ """병렬 처리둜 톡합 뢄석 μˆ˜ν–‰ (κ°œμ„ λœ 였λ₯˜ 처리)"""
490
  global analyzed_data, last_update_time, is_updating
491
 
492
  is_updating = True
493
  print(f"\n{'='*60}")
494
+ print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 톡합 μ‹€μ‹œκ°„ νŠΈλ Œλ“œ 뢄석 μ‹œμž‘")
495
  print(f"{'='*60}")
496
 
497
  try:
498
  # λΈŒλΌμš°μ € μ„€μΉ˜ 확인
499
+ print("1. λΈŒλΌμš°μ € μ„€μΉ˜ 확인...")
500
  install_browsers()
501
 
502
+ # 병렬 μŠ€ν¬λ¦°μƒ· 캑처
503
+ print("2. 병렬 μŠ€ν¬λ¦°μƒ· 캑처 μ‹œμž‘...")
 
504
 
505
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
506
+ print(" - Google Trends μŠ€ν¬λ¦°μƒ· μž‘μ—… μ‹œμž‘")
507
+ google_future = executor.submit(capture_google_trends)
 
508
 
509
+ print(" - rank.ezme.net μŠ€ν¬λ¦°μƒ· μž‘μ—… μ‹œμž‘")
510
+ ezme_future = executor.submit(capture_ezme_trends)
 
 
 
511
 
512
+ print(" - 두 μž‘μ—… μ™„λ£Œ λŒ€κΈ° 쀑...")
513
+ google_screenshot = google_future.result()
514
+ ezme_screenshot = ezme_future.result()
515
+
516
+ # μŠ€ν¬λ¦°μƒ· κ²°κ³Ό 확인
517
+ print("3. μŠ€ν¬λ¦°μƒ· κ²°κ³Ό 확인...")
518
+ google_success = google_screenshot is not None
519
+ ezme_success = ezme_screenshot is not None
520
+
521
+ print(f" - Google Trends μŠ€ν¬λ¦°μƒ·: {'성곡' if google_success else 'μ‹€νŒ¨'}")
522
+ print(f" - rank.ezme.net μŠ€ν¬λ¦°μƒ·: {'성곡' if ezme_success else 'μ‹€νŒ¨'}")
523
+
524
+ if not google_success and not ezme_success:
525
+ error_msg = "λͺ¨λ“  μŠ€ν¬λ¦°μƒ· μΊ‘μ²˜μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."
526
+ print(f" ERROR: {error_msg}")
527
+ return False, error_msg
528
+
529
+ print("4. Claude 뢄석 μ‹œμž‘...")
530
+
531
+ # Claude 뢄석 (순차 처리 - API μ œν•œ)
532
+ if google_success:
533
+ print(" - Google Trends 뢄석 쀑...")
534
+ google_analysis = analyze_google_trends_with_claude(google_screenshot)
535
+ print(" - Google Trends 뢄석 μ™„λ£Œ")
536
  else:
537
+ google_analysis = "Google Trends μŠ€ν¬λ¦°μƒ· μΊ‘μ²˜μ— μ‹€νŒ¨ν•˜μ—¬ 뢄석할 수 μ—†μŠ΅λ‹ˆλ‹€."
 
538
 
539
+ if ezme_success:
540
+ print(" - rank.ezme.net 뢄석 쀑...")
541
+ ezme_analysis = analyze_ezme_trends_with_claude(ezme_screenshot)
542
+ print(" - rank.ezme.net 뢄석 μ™„λ£Œ")
543
+ else:
544
+ ezme_analysis = "rank.ezme.net μŠ€ν¬λ¦°μƒ· μΊ‘μ²˜μ— μ‹€νŒ¨ν•˜μ—¬ 뢄석할 수 μ—†μŠ΅λ‹ˆλ‹€."
545
+
546
+ print("5. 톡합 뢄석 κ²°κ³Ό 생성...")
547
+
548
+ # 톡합 뢄석 κ²°κ³Ό 생성
549
+ combined_analysis = combine_analysis_results(google_analysis, ezme_analysis)
550
+
551
+ # κ²°κ³Ό μ €μž₯
552
+ analyzed_data["ν†΅ν•©μ‹€μ‹œκ°„"] = {
553
+ 'analysis': combined_analysis,
554
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
555
+ 'google_analysis': google_analysis,
556
+ 'ezme_analysis': ezme_analysis,
557
+ 'google_success': google_success,
558
+ 'ezme_success': ezme_success
559
+ }
560
+
561
+ last_update_time = datetime.now()
562
+
563
+ success_count = sum([google_success, ezme_success])
564
+ print(f"톡합 뢄석 μ™„λ£Œ - {last_update_time.strftime('%Y-%m-%d %H:%M:%S')}")
565
+ print(f"성곡λ₯ : {success_count}/2 μ†ŒμŠ€")
566
+
567
+ return True, combined_analysis
568
+
569
  except Exception as e:
570
+ error_msg = f"톡합 뢄석 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜: {str(e)}"
571
+ print(f"ERROR: {error_msg}")
572
  print(f"상세 였λ₯˜: {traceback.format_exc()}")
573
+ return False, error_msg
574
  finally:
575
  is_updating = False
576
  print(f"{'='*60}\n")
 
584
 
585
  if not is_updating:
586
  print(f"[μŠ€μΌ€μ€„λŸ¬] μžλ™ μ—…λ°μ΄νŠΈ μ‹œμž‘")
587
+ perform_parallel_analysis()
588
  else:
589
  print(f"[μŠ€μΌ€μ€„λŸ¬] 이미 μ—…λ°μ΄νŠΈ μ€‘μ΄λ―€λ‘œ κ±΄λ„ˆλœ€")
590
 
 
592
  print(f"[μŠ€μΌ€μ€„λŸ¬] 였λ₯˜: {e}")
593
  time.sleep(300) # 5λΆ„ ν›„ μž¬μ‹œλ„
594
 
595
+ def get_cached_analysis_result() -> tuple:
596
+ """μ €μž₯된 톡합 뢄석 κ²°κ³Ό λ°˜ν™˜"""
597
  global analyzed_data, last_update_time
598
 
599
+ if "ν†΅ν•©μ‹€μ‹œκ°„" in analyzed_data:
600
+ data = analyzed_data["ν†΅ν•©μ‹€μ‹œκ°„"]
601
  status = f"μ΅œμ‹  데이터 (μ—…λ°μ΄νŠΈ: {data['timestamp']})"
602
  return data['analysis'], status
603
  else:
 
606
  else:
607
  status = "아직 뢄석 데이터가 μ€€λΉ„λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."
608
 
609
+ return "**뢄석 데이터가 아직 μ€€λΉ„λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.**\n\nμž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.", status
610
 
611
  def create_interface():
612
  """Gradio μΈν„°νŽ˜μ΄μŠ€ 생성"""
613
 
614
  def show_cached_analysis():
615
+ """μ €μž₯된 톡합 뢄석 κ²°κ³Ό ν‘œμ‹œ"""
616
+ if "ν†΅ν•©μ‹€μ‹œκ°„" in analyzed_data:
617
+ data = analyzed_data["ν†΅ν•©μ‹€μ‹œκ°„"]
618
+ status = f"οΏ½οΏ½οΏ½μ‹œλœ 톡합 뢄석 κ²°κ³Ό (μ—…λ°μ΄νŠΈ: {data['timestamp']})"
619
  return data['analysis'], status
620
  else:
621
  if last_update_time:
 
623
  else:
624
  status = "아직 뢄석 데이터가 μ€€λΉ„λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."
625
 
626
+ return "**μ €μž₯된 톡합 뢄석 데이터가 μ—†μŠ΅λ‹ˆλ‹€.**\n\n'μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ' λ²„νŠΌμ„ λˆŒλŸ¬μ£Όμ„Έμš”.", status
627
 
628
  def perform_live_update():
629
  """μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ μˆ˜ν–‰"""
630
  if is_updating:
631
  return "**ν˜„μž¬ μ—…λ°μ΄νŠΈκ°€ μ§„ν–‰ μ€‘μž…λ‹ˆλ‹€.**\n\nμž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.", "μ—…λ°μ΄νŠΈ μ§„ν–‰ 쀑..."
632
 
633
+ print("μ‹€μ‹œκ°„ 톡합 μ—…λ°μ΄νŠΈ μš”μ²­λ¨")
634
+ success, result = perform_parallel_analysis()
635
 
636
+ if success and "ν†΅ν•©μ‹€μ‹œκ°„" in analyzed_data:
637
+ data = analyzed_data["ν†΅ν•©μ‹€μ‹œκ°„"]
638
  return data['analysis'], f"μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ μ™„λ£Œ - {datetime.now().strftime('%H:%M:%S')}"
639
  else:
640
  return "**μ—…λ°μ΄νŠΈμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.**\n\nμž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.", f"μ—…λ°μ΄νŠΈ μ‹€νŒ¨ - {datetime.now().strftime('%H:%M:%S')}"
641
 
642
  # Gradio μΈν„°νŽ˜μ΄μŠ€ ꡬ성
643
  with gr.Blocks(
644
+ title="톡합 μ‹€μ‹œκ°„ 검색어 뢄석",
645
  theme=gr.themes.Soft(),
646
  css="""
647
  @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
 
659
  border: 1px solid #e9ecef;
660
  }
661
 
662
+ .markdown-content h1 {
663
+ color: #1d4ed8;
664
+ margin-bottom: 15px;
665
+ }
666
+
667
+ .markdown-content h2 {
668
  color: #2563eb;
669
+ margin-top: 25px;
670
+ margin-bottom: 12px;
671
+ }
672
+
673
+ .markdown-content h3 {
674
+ color: #3b82f6;
675
  margin-top: 20px;
676
  margin-bottom: 10px;
677
  }
 
683
  """
684
  ) as interface:
685
 
686
+ gr.Markdown("# 톡합 μ‹€μ‹œκ°„ 검색어 뢄석")
687
+ gr.Markdown("Google Trends와 ν•œκ΅­ ν¬ν„Έμ‚¬μ΄νŠΈ 검색어λ₯Ό 톡합 λΆ„μ„ν•©λ‹ˆλ‹€.")
688
 
689
  with gr.Row():
690
  with gr.Column(scale=1):
691
  gr.Markdown("### 뢄석 μ˜΅μ…˜")
692
 
693
  with gr.Column():
694
+ cached_btn = gr.Button("톡합 검색어 뢄석", variant="primary", size="lg")
695
+ gr.Markdown("μ €μž₯된 톡합 뢄석 κ²°κ³Όλ₯Ό μ¦‰μ‹œ ν‘œμ‹œν•©λ‹ˆλ‹€.")
696
 
697
  gr.Markdown("---")
698
 
699
  with gr.Column():
700
  live_btn = gr.Button("μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ", variant="secondary", size="lg")
701
+ gr.Markdown("Google Trends + ν•œκ΅­ 포털 μ΅œμ‹  λ°μ΄ν„°λ‘œ μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€. (μ•½ 1λΆ„ μ†Œμš”)")
702
 
703
  gr.Markdown("---")
704
 
 
709
  )
710
 
711
  # Apeach 이미지 ν‘œμ‹œ
712
+ try:
713
+ gr.Image(
714
+ value="apeach.png",
715
+ label=None,
716
+ show_label=False,
717
+ interactive=False,
718
+ height=200,
719
+ width=200
720
+ )
721
+ except:
722
+ pass
723
 
724
  with gr.Column(scale=2):
725
  analysis_output = gr.Markdown(
726
+ value="**뢄석 λ²„νŠΌμ„ λˆŒλŸ¬μ£Όμ„Έμš”.**\n\n- **톡합 검색어 뢄석**: μ €μž₯된 κ²°κ³Ό μ¦‰μ‹œ 확인\n- **μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ**: μ΅œμ‹  λ°μ΄ν„°λ‘œ μƒˆλ‘œ 뢄석",
727
  elem_classes=["markdown-content"],
728
+ height=700
729
  )
730
 
731
  # 이벀트 μ—°κ²°
 
744
  def main():
745
  """메인 ν•¨μˆ˜"""
746
  print("=" * 60)
747
+ print("톡합 μ‹€μ‹œκ°„ 검색어 뢄석 μ„œλΉ„μŠ€ μ‹œμž‘!")
748
  print("=" * 60)
749
 
750
  # API ν‚€ 확인
 
756
  else:
757
  print("Claude API ν‚€ 확인됨")
758
 
759
+ # μ’…λ£Œμ‹œ λΈŒλΌμš°μ € 정리
760
+ atexit.register(cleanup_browser)
761
+
762
+ # 초기 톡합 뢄석 μˆ˜ν–‰
763
+ print("초기 톡합 뢄석 μ‹œμž‘...")
764
+ initial_success, _ = perform_parallel_analysis()
765
 
766
  if initial_success:
767
+ print("초기 톡합 뢄석 μ™„λ£Œ")
768
  else:
769
  print("초기 뢄석 μ‹€νŒ¨, μ„œλΉ„μŠ€λŠ” 계속 μ§„ν–‰λ©λ‹ˆλ‹€.")
770
 
 
790
  except Exception as e:
791
  print(f"μΈν„°νŽ˜μ΄μŠ€ μ‹œμž‘ μ‹€νŒ¨: {e}")
792
  print(f"상세 였λ₯˜: {traceback.format_exc()}")
793
+ finally:
794
+ cleanup_browser()
795
 
796
  if __name__ == "__main__":
797
  main()