HanJun commited on
Commit
0299c67
·
verified ·
1 Parent(s): 62f5ee3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -24
app.py CHANGED
@@ -48,14 +48,21 @@ def get_claude_client():
48
  return anthropic.Anthropic(api_key=api_key)
49
 
50
  def install_browsers():
51
- """Playwright 브라우저 설치"""
52
  try:
53
  print("Playwright 브라우저 설치 중...")
54
  os.system("playwright install chromium")
55
- print("Playwright 브라우저 설치 완료!")
 
 
 
 
 
 
 
56
  return True
57
  except Exception as e:
58
- print(f"브라우저 설치 실패: {e}")
59
  return False
60
 
61
  def init_browser():
@@ -92,7 +99,10 @@ def init_browser():
92
  '--disable-features=TranslateUI',
93
  '--disable-ipc-flooding-protection',
94
  '--disable-plugins',
95
- '--disable-images'
 
 
 
96
  ]
97
  )
98
 
@@ -104,9 +114,17 @@ def init_browser():
104
 
105
  # User-Agent 설정
106
  page.set_extra_http_headers({
107
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
 
108
  })
109
 
 
 
 
 
 
 
 
110
  print("Google Trends 페이지로 이동 중...")
111
  page.goto("https://trends.google.com/trending?geo=KR", wait_until="networkidle", timeout=30000)
112
 
@@ -170,25 +188,61 @@ def apply_filters(geo: str, time_range: str, category: str) -> bool:
170
  try:
171
  print(f"필터 적용: 지역={geo}")
172
 
173
- # 페이지 새로고침으로 시작
174
- page.reload(wait_until="networkidle", timeout=30000)
175
- time.sleep(3)
176
-
177
- # 필터 적용은 URL 파라미터로 처리
178
  geo_map = {
179
  "KR": "KR", "US": "US", "JP": "JP",
180
  "GB": "GB", "DE": "DE", "FR": "FR"
181
  }
182
 
183
- # URL 구성
184
  base_url = "https://trends.google.com/trending"
185
  geo_param = geo_map.get(geo, "KR")
186
  url = f"{base_url}?geo={geo_param}"
187
 
188
  print(f"필터 적용된 URL로 이동: {url}")
189
  page.goto(url, wait_until="networkidle", timeout=30000)
 
 
190
  time.sleep(3)
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  print("필터 적용 완료")
193
  return True
194
 
@@ -198,7 +252,7 @@ def apply_filters(geo: str, time_range: str, category: str) -> bool:
198
  return False
199
 
200
  def capture_screenshot() -> Optional[Image.Image]:
201
- """현재 페이지의 스크린샷 캡처"""
202
  global page
203
 
204
  # 브라우저 준비 상태 확인
@@ -212,10 +266,41 @@ def capture_screenshot() -> Optional[Image.Image]:
212
  # 페이지가 완전히 로드될 때까지 대기
213
  time.sleep(2)
214
 
215
- # 스크린샷 캡처
216
- screenshot_bytes = page.screenshot(full_page=True)
217
- screenshot = Image.open(io.BytesIO(screenshot_bytes))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
 
219
  print("스크린샷 캡처 완료")
220
  return screenshot
221
 
@@ -310,8 +395,52 @@ def refresh_trends_page():
310
  except Exception as e:
311
  return f"페이지 새로고침 실패: {str(e)}"
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  def main_process(geo: str, time_range: str, category: str) -> Tuple[Optional[Image.Image], str]:
314
- """메인 처리 함수"""
315
  try:
316
  # 1. 브라우저 준비 확인
317
  if not ensure_browser_ready():
@@ -369,6 +498,9 @@ def create_interface():
369
  gr.Markdown("# Google Trends 실시간 인기 검색어 분석기")
370
  gr.Markdown("Google Trends의 실시간 인기 검색어를 캡처하고 Claude AI로 분석합니다.")
371
 
 
 
 
372
  with gr.Row():
373
  with gr.Column(scale=1):
374
  geo_dropdown = gr.Dropdown(
@@ -389,27 +521,57 @@ def create_interface():
389
  label="카테고리 선택"
390
  )
391
 
392
- analyze_btn = gr.Button("분석 시작", variant="primary")
393
- refresh_btn = gr.Button("페이지 새로고침", variant="secondary")
394
- reinit_btn = gr.Button("브라우저 재초기화", variant="secondary")
395
- status_btn = gr.Button("브라우저 상태 확인", variant="secondary")
 
 
 
 
396
 
397
  with gr.Column(scale=2):
398
  screenshot_output = gr.Image(label="Google Trends 스크린샷")
399
  analysis_output = gr.Textbox(
400
  label="Claude AI 분석 결과",
401
  lines=10,
402
- max_lines=20
 
403
  )
404
  status_output = gr.Textbox(label="상태 메시지")
405
 
406
  # 이벤트 핸들러
407
- analyze_btn.click(
408
- fn=main_process,
 
 
 
 
 
 
 
 
 
 
409
  inputs=[geo_dropdown, time_dropdown, category_dropdown],
410
- outputs=[screenshot_output, analysis_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  )
412
 
 
413
  refresh_btn.click(
414
  fn=refresh_trends_page,
415
  outputs=status_output
 
48
  return anthropic.Anthropic(api_key=api_key)
49
 
50
  def install_browsers():
51
+ """Playwright 브라우저 및 한글 폰트 설치"""
52
  try:
53
  print("Playwright 브라우저 설치 중...")
54
  os.system("playwright install chromium")
55
+
56
+ print("한글 폰트 설치 중...")
57
+ # 한글 폰트 설치
58
+ os.system("apt-get update")
59
+ os.system("apt-get install -y fonts-nanum fonts-nanum-coding fonts-nanum-extra")
60
+ os.system("fc-cache -fv")
61
+
62
+ print("Playwright 브라우저 및 폰트 설치 완료!")
63
  return True
64
  except Exception as e:
65
+ print(f"브라우저/폰트 설치 실패: {e}")
66
  return False
67
 
68
  def init_browser():
 
99
  '--disable-features=TranslateUI',
100
  '--disable-ipc-flooding-protection',
101
  '--disable-plugins',
102
+ '--disable-images',
103
+ '--force-device-scale-factor=1',
104
+ '--font-render-hinting=medium',
105
+ '--disable-font-subpixel-positioning'
106
  ]
107
  )
108
 
 
114
 
115
  # User-Agent 설정
116
  page.set_extra_http_headers({
117
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
118
+ "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8"
119
  })
120
 
121
+ # 한글 폰트 설정을 위한 CSS 추가
122
+ page.add_style_tag(content="""
123
+ * {
124
+ font-family: 'Nanum Gothic', 'Malgun Gothic', '맑은 고딕', 'Noto Sans CJK KR', 'Apple SD Gothic Neo', sans-serif !important;
125
+ }
126
+ """)
127
+
128
  print("Google Trends 페이지로 이동 중...")
129
  page.goto("https://trends.google.com/trending?geo=KR", wait_until="networkidle", timeout=30000)
130
 
 
188
  try:
189
  print(f"필터 적용: 지역={geo}")
190
 
191
+ # 지역 설정을 위한 URL 구성
 
 
 
 
192
  geo_map = {
193
  "KR": "KR", "US": "US", "JP": "JP",
194
  "GB": "GB", "DE": "DE", "FR": "FR"
195
  }
196
 
 
197
  base_url = "https://trends.google.com/trending"
198
  geo_param = geo_map.get(geo, "KR")
199
  url = f"{base_url}?geo={geo_param}"
200
 
201
  print(f"필터 적용된 URL로 이동: {url}")
202
  page.goto(url, wait_until="networkidle", timeout=30000)
203
+
204
+ # 페이지 로딩 완료 대기
205
  time.sleep(3)
206
 
207
+ # 실제 필터 요소들이 있다면 클릭으로 설정
208
+ try:
209
+ # 시간 범위 필터 시도
210
+ if time_range != "24시간":
211
+ time_selector = page.locator('[data-bucket="0"]').first
212
+ if time_selector.is_visible():
213
+ time_selector.click()
214
+ time.sleep(1)
215
+
216
+ # 시간 옵션들 중 선택
217
+ if time_range == "7일":
218
+ page.locator('text="Past 7 days"').first.click()
219
+ elif time_range == "30일":
220
+ page.locator('text="Past 30 days"').first.click()
221
+ time.sleep(2)
222
+
223
+ # 카테고리 필터 시도
224
+ if category != "모든 카테고리":
225
+ category_selector = page.locator('[data-bucket="2"]').first
226
+ if category_selector.is_visible():
227
+ category_selector.click()
228
+ time.sleep(1)
229
+
230
+ # 카테고리 매핑
231
+ category_map = {
232
+ "엔터테인먼트": "Entertainment",
233
+ "스포츠": "Sports",
234
+ "비즈니스": "Business",
235
+ "과학기술": "Science & Tech",
236
+ "건강": "Health"
237
+ }
238
+
239
+ if category in category_map:
240
+ page.locator(f'text="{category_map[category]}"').first.click()
241
+ time.sleep(2)
242
+
243
+ except Exception as filter_error:
244
+ print(f"필터 요소 클릭 실패 (계속 진행): {filter_error}")
245
+
246
  print("필터 적용 완료")
247
  return True
248
 
 
252
  return False
253
 
254
  def capture_screenshot() -> Optional[Image.Image]:
255
+ """현재 페이지의 스크린샷 캡처 (트렌드 테이블 영역만)"""
256
  global page
257
 
258
  # 브라우저 준비 상태 확인
 
266
  # 페이지가 완전히 로드될 때까지 대기
267
  time.sleep(2)
268
 
269
+ # 트렌드 테이블 영역 찾기
270
+ try:
271
+ # 여러 가능한 셀렉터 시도
272
+ selectors = [
273
+ '[data-module="TrendingSearches"]', # 메인 트렌드 모듈
274
+ '.trends-table', # 트렌드 테이블
275
+ '.trending-searches', # 트렌드 검색어 영역
276
+ '[role="main"]', # 메인 콘텐츠 영역
277
+ 'main' # 기본 메인 태그
278
+ ]
279
+
280
+ element = None
281
+ for selector in selectors:
282
+ try:
283
+ element = page.locator(selector).first
284
+ if element.is_visible():
285
+ print(f"캡처 영역 발견: {selector}")
286
+ break
287
+ except:
288
+ continue
289
+
290
+ if element and element.is_visible():
291
+ # 특정 영역만 스크린샷
292
+ screenshot_bytes = element.screenshot()
293
+ print("트렌드 테이블 영역 캡처 완료")
294
+ else:
295
+ # 대안: 전체 페이지에서 상단 부분만 캡처
296
+ print("특정 영역을 찾지 못해 페이지 상단 영역 캡처")
297
+ screenshot_bytes = page.screenshot(clip={"x": 0, "y": 100, "width": 1920, "height": 800})
298
+
299
+ except Exception as selector_error:
300
+ print(f"영역 선택 실패, 전체 페이지 캡처: {selector_error}")
301
+ screenshot_bytes = page.screenshot(full_page=True)
302
 
303
+ screenshot = Image.open(io.BytesIO(screenshot_bytes))
304
  print("스크린샷 캡처 완료")
305
  return screenshot
306
 
 
395
  except Exception as e:
396
  return f"페이지 새로고침 실패: {str(e)}"
397
 
398
+ def main_process_screenshot(geo: str, time_range: str, category: str) -> Tuple[Optional[Image.Image], str]:
399
+ """스크린샷 캡처만 수행하는 함수"""
400
+ try:
401
+ # 1. 브라우저 준비 확인
402
+ if not ensure_browser_ready():
403
+ return None, "브라우저가 준비되지 않았습니다. '브라우저 재초기화' 버튼을 클릭해주세요."
404
+
405
+ # 2. 필터 적용
406
+ print(f"필터 적용 중: 지역={geo}, 시간={time_range}, 카테고리={category}")
407
+ filter_success = apply_filters(geo, time_range, category)
408
+
409
+ if not filter_success:
410
+ return None, "필터 적용에 실패했습니다."
411
+
412
+ # 3. 스크린샷 캡처
413
+ print("스크린샷 캡처 중...")
414
+ screenshot = capture_screenshot()
415
+
416
+ if screenshot is None:
417
+ return None, "스크린샷 캡처에 실패했습니다."
418
+
419
+ return screenshot, "스크린샷 캡처 완료! Claude 분석을 진행합니다..."
420
+
421
+ except Exception as e:
422
+ error_msg = f"스크린샷 캡처 중 오류 발생: {str(e)}"
423
+ print(error_msg)
424
+ print(traceback.format_exc())
425
+ return None, error_msg
426
+
427
+ def analyze_screenshot_with_claude(screenshot: Image.Image) -> str:
428
+ """Claude API로 스크린샷 분석"""
429
+ if screenshot is None:
430
+ return "분석할 스크린샷이 없습니다."
431
+
432
+ try:
433
+ print("Claude API로 분석 중...")
434
+ analysis_result = analyze_with_claude(screenshot)
435
+ return analysis_result
436
+
437
+ except Exception as e:
438
+ error_msg = f"Claude 분석 중 오류 발생: {str(e)}"
439
+ print(error_msg)
440
+ return error_msg
441
+
442
  def main_process(geo: str, time_range: str, category: str) -> Tuple[Optional[Image.Image], str]:
443
+ """전체 프로세스 (호환성을 위해 유지)"""
444
  try:
445
  # 1. 브라우저 준비 확인
446
  if not ensure_browser_ready():
 
498
  gr.Markdown("# Google Trends 실시간 인기 검색어 분석기")
499
  gr.Markdown("Google Trends의 실시간 인기 검색어를 캡처하고 Claude AI로 분석합니다.")
500
 
501
+ # 상태 저장을 위한 State 컴포넌트
502
+ screenshot_state = gr.State(None)
503
+
504
  with gr.Row():
505
  with gr.Column(scale=1):
506
  geo_dropdown = gr.Dropdown(
 
521
  label="카테고리 선택"
522
  )
523
 
524
+ with gr.Row():
525
+ capture_btn = gr.Button("🎯 스크린샷 캡처", variant="primary")
526
+ analyze_btn = gr.Button("🤖 Claude 분석", variant="secondary", interactive=False)
527
+
528
+ with gr.Row():
529
+ refresh_btn = gr.Button("🔄 페이지 새로고침", variant="secondary")
530
+ reinit_btn = gr.Button("⚙️ 브라우저 초기화", variant="secondary")
531
+ status_btn = gr.Button("📊 브라우저 상태 확인", variant="secondary")
532
 
533
  with gr.Column(scale=2):
534
  screenshot_output = gr.Image(label="Google Trends 스크린샷")
535
  analysis_output = gr.Textbox(
536
  label="Claude AI 분석 결과",
537
  lines=10,
538
+ max_lines=20,
539
+ placeholder="스크린샷을 캡처한 후 'Claude 분석' 버튼을 클릭하세요."
540
  )
541
  status_output = gr.Textbox(label="상태 메시지")
542
 
543
  # 이벤트 핸들러
544
+
545
+ # 스크린샷 캡처 버튼
546
+ def on_capture_click(geo, time_range, category):
547
+ screenshot, message = main_process_screenshot(geo, time_range, category)
548
+ if screenshot is not None:
549
+ # 분석 버튼 활성화
550
+ return screenshot, screenshot, message, gr.update(interactive=True)
551
+ else:
552
+ return None, None, message, gr.update(interactive=False)
553
+
554
+ capture_btn.click(
555
+ fn=on_capture_click,
556
  inputs=[geo_dropdown, time_dropdown, category_dropdown],
557
+ outputs=[screenshot_output, screenshot_state, status_output, analyze_btn]
558
+ )
559
+
560
+ # Claude 분석 버튼
561
+ def on_analyze_click(screenshot):
562
+ if screenshot is None:
563
+ return "분석할 스크린샷이 없습니다. 먼저 '스크린샷 캡처' 버튼을 클릭하세요."
564
+
565
+ analysis_result = analyze_screenshot_with_claude(screenshot)
566
+ return analysis_result
567
+
568
+ analyze_btn.click(
569
+ fn=on_analyze_click,
570
+ inputs=[screenshot_state],
571
+ outputs=[analysis_output]
572
  )
573
 
574
+ # 기타 버튼들
575
  refresh_btn.click(
576
  fn=refresh_trends_page,
577
  outputs=status_output