import os
import random
import base64
import requests
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import WebDriverException, TimeoutException
from PIL import Image
from io import BytesIO
from datetime import datetime
import gradio as gr
from typing import Tuple
import time
from pathlib import Path # 추가
# 스크린샷 캐시 디렉토리 설정
CACHE_DIR = Path("screenshot_cache")
CACHE_DIR.mkdir(exist_ok=True)
# 전역 변수로 스크린샷 캐시 선언
SCREENSHOT_CACHE = {}
def get_cached_screenshot(url: str) -> str:
"""캐시된 스크린샷 가져오기 또는 새로 생성"""
cache_file = CACHE_DIR / f"{base64.b64encode(url.encode()).decode()}.png"
if cache_file.exists():
with open(cache_file, "rb") as f:
return base64.b64encode(f.read()).decode()
return take_screenshot(url)
def take_screenshot(url):
"""웹사이트 스크린샷 촬영 함수 (로딩 대기 시간 추가)"""
if url in SCREENSHOT_CACHE:
return SCREENSHOT_CACHE[url]
if not url.startswith('http'):
url = f"https://{url}"
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--window-size=1080,720')
try:
driver = webdriver.Chrome(options=options)
driver.get(url)
# 명시적 대기: body 요소가 로드될 때까지 대기 (최대 10초)
try:
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
except TimeoutException:
print(f"페이지 로딩 타임아웃: {url}")
# 추가 대기 시간을 2초로 증가
time.sleep(2) # 1초에서 2초로 변경
# JavaScript 실행 완료 대기
driver.execute_script("return document.readyState") == "complete"
# 스크린샷 촬영
screenshot = driver.get_screenshot_as_png()
img = Image.open(BytesIO(screenshot))
buffered = BytesIO()
img.save(buffered, format="PNG")
base64_image = base64.b64encode(buffered.getvalue()).decode()
# 캐시에 저장
SCREENSHOT_CACHE[url] = base64_image
return base64_image
except WebDriverException as e:
print(f"스크린샷 촬영 실패: {str(e)} for URL: {url}")
return None
except Exception as e:
print(f"예상치 못한 오류: {str(e)} for URL: {url}")
return None
finally:
if 'driver' in locals():
driver.quit()
def get_card(item: dict, index: int, card_type: str = "space") -> str:
"""통합 카드 HTML 생성"""
item_id = item.get('id', '')
author, title = item_id.split('/', 1)
likes = format(item.get('likes', 0), ',')
created = item.get('createdAt', '').split('T')[0]
# 메타데이터 처리
tags = item.get('tags', [])
pipeline_tag = item.get('pipeline_tag', '')
license = item.get('license', '')
# 카드 타입별 특수 처리
if card_type == "space":
url = f"https://huggingface.co/spaces/{item_id}"
type_icon = "🎯"
type_label = "SPACE"
bg_content = f"""
background-image: url(data:image/png;base64,{get_cached_screenshot(url) if get_cached_screenshot(url) else ''});
background-size: cover;
background-position: center;
"""
meta_html = ""
else:
# models와 datasets를 위한 메타데이터 표시
url = f"https://huggingface.co/{item_id}" if card_type == "model" else f"https://huggingface.co/datasets/{item_id}"
type_icon = "🤖" if card_type == "model" else "📊"
type_label = "MODEL" if card_type == "model" else "DATASET"
bg_color = "#6e8efb" if card_type == "model" else "#ff6b6b"
meta_html = f"""
{' '.join([f'''
#{tag}
''' for tag in tags[:5]])}
{f'''
🔧 Pipeline: {pipeline_tag}
''' if pipeline_tag else ''}
{f'''
📜 License: {license}
''' if license else ''}
"""
bg_content = f"""
background: linear-gradient(135deg, {bg_color}, {bg_color}dd);
padding: 15px;
color: white;
"""
return f"""
#{index + 1}
{type_icon} {type_label}
{meta_html}
{title}
👤 {author}
❤️ {likes}
📅 {created}
"""
def get_trending_spaces(progress=gr.Progress()) -> Tuple[str, str]:
"""트렌딩 스페이스 가져오기"""
url = "https://huggingface.co/api/spaces"
try:
progress(0, desc="Fetching spaces data...")
response = requests.get(url)
response.raise_for_status()
spaces = response.json()
# 상위 10개만 선택 (원본 순서 유지)
top_spaces = spaces[:10]
progress(0.1, desc="Creating gallery...")
html_content = """
"""
for idx, space in enumerate(top_spaces):
html_content += get_card(space, idx, "space")
progress((0.1 + 0.9 * idx/10), desc=f"Loading space {idx+1}/10...")
html_content += "
"
progress(1.0, desc="Complete!")
return html_content, "Gallery refresh complete!"
except Exception as e:
error_html = f'Error: {str(e)}
'
return error_html, f"Error: {str(e)}"
def get_models(progress=gr.Progress()) -> Tuple[str, str]:
"""인기 모델 가져오기"""
url = "https://huggingface.co/api/models"
try:
progress(0, desc="Fetching models data...")
response = requests.get(url)
response.raise_for_status()
models = response.json()
# 상위 10개만 선택
top_models = models[:10]
progress(0.1, desc="Creating gallery...")
html_content = """
"""
for idx, model in enumerate(top_models):
model_id = model.get('id', '')
author = model_id.split('/')[0]
title = model_id.split('/')[-1]
likes = format(model.get('likes', 0), ',')
downloads = format(model.get('downloads', 0), ',')
created = model.get('createdAt', '').split('T')[0]
url = f"https://huggingface.co/{model_id}"
screenshot = get_cached_screenshot(url)
html_content += get_card(model, idx, "model")
progress((0.1 + 0.9 * idx/10), desc=f"Loading model {idx+1}/10...")
html_content += "
"
progress(1.0, desc="Complete!")
return html_content, "Models gallery refresh complete!"
except Exception as e:
error_html = f'Error: {str(e)}
'
return error_html, f"Error: {str(e)}"
def get_datasets(progress=gr.Progress()) -> Tuple[str, str]:
"""인기 데이터셋 가져오기"""
url = "https://huggingface.co/api/datasets"
try:
progress(0, desc="Fetching datasets data...")
response = requests.get(url)
response.raise_for_status()
datasets = response.json()
# 상위 10개만 선택
top_datasets = datasets[:10]
progress(0.1, desc="Creating gallery...")
html_content = """
"""
for idx, dataset in enumerate(top_datasets):
dataset_id = dataset.get('id', '')
author = dataset_id.split('/')[0]
title = dataset_id.split('/')[-1]
likes = format(dataset.get('likes', 0), ',')
downloads = format(dataset.get('downloads', 0), ',')
created = dataset.get('createdAt', '').split('T')[0]
url = f"https://huggingface.co/datasets/{dataset_id}"
screenshot = get_cached_screenshot(url)
html_content += get_card(dataset, idx, "dataset")
progress((0.1 + 0.9 * idx/10), desc=f"Loading dataset {idx+1}/10...")
html_content += "
"
progress(1.0, desc="Complete!")
return html_content, "Datasets gallery refresh complete!"
except Exception as e:
error_html = f'Error: {str(e)}
'
return error_html, f"Error: {str(e)}"
def create_interface():
"""Gradio 인터페이스 생성"""
with gr.Blocks(title="HuggingFace Trending Board") as interface:
gr.Markdown("# 🤗 HuggingFace Trending Board")
with gr.Tabs() as tabs:
# Spaces 탭
with gr.Tab("🎯 Trending Spaces"):
gr.Markdown("Shows top 10 trending spaces on Hugging Face")
with gr.Row():
spaces_refresh_btn = gr.Button("Refresh Spaces", variant="primary")
spaces_gallery = gr.HTML()
spaces_status = gr.Markdown("Ready")
# Models 탭
with gr.Tab("🤖 Trending Models"):
gr.Markdown("Shows top 10 trending models on Hugging Face")
with gr.Row():
models_refresh_btn = gr.Button("Refresh Models", variant="primary")
models_gallery = gr.HTML()
models_status = gr.Markdown("Ready")
# Datasets 탭
with gr.Tab("📊 Trending Datasets"):
gr.Markdown("Shows top 10 trending datasets on Hugging Face")
with gr.Row():
datasets_refresh_btn = gr.Button("Refresh Datasets", variant="primary")
datasets_gallery = gr.HTML()
datasets_status = gr.Markdown("Ready")
# Event handlers
spaces_refresh_btn.click(
fn=get_trending_spaces,
outputs=[spaces_gallery, spaces_status],
show_progress=True
)
models_refresh_btn.click(
fn=get_models,
outputs=[models_gallery, models_status],
show_progress=True
)
datasets_refresh_btn.click(
fn=get_datasets,
outputs=[datasets_gallery, datasets_status],
show_progress=True
)
# 초기 로드
interface.load(
fn=get_trending_spaces,
outputs=[spaces_gallery, spaces_status]
)
interface.load(
fn=get_models,
outputs=[models_gallery, models_status]
)
interface.load(
fn=get_datasets,
outputs=[datasets_gallery, datasets_status]
)
return interface
if __name__ == "__main__":
try:
demo = create_interface()
demo.launch(
share=True,
inbrowser=True,
show_api=False
)
except Exception as e:
print(f"Error launching app: {e}")