Topic 3: 실시간 엔터테인먼트 정보 수집하기! 🎬🎮
목표: Unit 10에서 배운 웹 스크래핑을 사용해서 실시간 영화, 게임, 동영상 정보를 가져오는 웹앱을 만들어요!
🎯 학습 목표
- Unit 10 복습: BeautifulSoup과 requests 다시 써보기
- 실시간 엔터테인먼트: 영화 랭킹, 게임 정보, 인기 동영상 수집하기
- 에러 처리: 인터넷이 안 될 때도 우아하게 처리하기
- 멋진 디자인: 엔터테인먼트 앱처럼 예쁘게 만들기! 🎨
🎬 Unit 10 복습: 스크래핑 기초
Unit 10에서 배운 걸 빠르게 복습해봐요!
import requests
from bs4 import BeautifulSoup
# 1. 웹페이지 가져오기 (Unit 10 Topic 4)
response = requests.get('https://example.com')
# 2. HTML 파싱하기 (Unit 10 Topic 5)
soup = BeautifulSoup(response.text, 'html.parser')
# 3. 원하는 데이터 뽑기
title = soup.find('h1').text
이제 이걸 Flask와 합쳐서 살아있는 엔터테인먼트 웹앱을 만들어봐요!
🚀 첫 번째 작품: 실시간 영화 랭킹 웹앱
먼저 간단한 것부터! 인기 영화 정보를 실시간으로 보여주는 웹앱이에요.
Step 1: 설치하기
pip install flask requests beautifulsoup4
Step 2: 영화 정보 스크래퍼 만들기
app.py
파일을 만들어요:
from flask import Flask, render_template
import requests
from bs4 import BeautifulSoup
import random
app = Flask(__name__)
def get_movie_rankings():
"""인기 영화 정보를 가져오는 함수"""
try:
# 실습용 영화 데이터 (실제로는 영화 사이트에서 크롤링)
movies = [
{
"title": "스파이더맨: 어크로스 더 스파이더 버스",
"rating": "9.2",
"poster": "🕷️",
"genre": "액션, 애니메이션",
"year": "2023",
"description": "멀티버스를 넘나드는 스파이더맨의 모험"
},
{
"title": "가디언즈 오브 갤럭시 3",
"rating": "8.8",
"poster": "🚀",
"genre": "액션, SF",
"year": "2023",
"description": "갤럭시 수호대의 마지막 모험"
},
{
"title": "존 윅 4",
"rating": "8.5",
"poster": "🔫",
"genre": "액션, 스릴러",
"year": "2023",
"description": "전설적인 킬러의 복수극"
},
{
"title": "더 슈퍼 마리오 브라더스 무비",
"rating": "8.2",
"poster": "🍄",
"genre": "애니메이션, 모험",
"year": "2023",
"description": "마리오의 버섯왕국 대모험"
},
{
"title": "오펜하이머",
"rating": "9.0",
"poster": "💣",
"genre": "드라마, 역사",
"year": "2023",
"description": "원자폭탄 아버지의 이야기"
}
]
return random.sample(movies, 3) # 랜덤하게 3개 선택
except Exception as e:
return [{"title": "영화 정보를 불러올 수 없습니다", "rating": "0.0", "poster": "🎬", "genre": "정보 없음", "year": "----", "description": "나중에 다시 시도해주세요"}]
@app.route('/')
def home():
movies = get_movie_rankings()
return render_template('movies.html', movies=movies)
if __name__ == '__main__':
app.run(debug=True)
Step 3: 예쁜 영화 템플릿
templates/movies.html
파일을 만들어요:
<!DOCTYPE html>
<html>
<head>
<title>인기 영화 랭킹 🎬</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
background: linear-gradient(135deg, #ff6b6b 0%, #4ecdc4 50%, #45b7d1 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.header {
text-align: center;
color: white;
margin-bottom: 40px;
}
.title {
font-size: 3em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-10px); }
60% { transform: translateY(-5px); }
}
.subtitle {
font-size: 1.3em;
opacity: 0.9;
}
.movies-container {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
}
.movie-card {
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.movie-card:hover {
transform: translateY(-10px);
box-shadow: 0 30px 60px rgba(0,0,0,0.2);
}
.movie-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 5px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1);
}
.movie-poster {
font-size: 4em;
text-align: center;
margin-bottom: 20px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-15px); }
}
.movie-title {
font-size: 1.5em;
font-weight: bold;
color: #333;
margin-bottom: 15px;
text-align: center;
line-height: 1.3;
}
.movie-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 20px;
}
.info-item {
background: #f8f9fa;
padding: 12px;
border-radius: 10px;
text-align: center;
}
.info-label {
font-size: 0.9em;
color: #666;
margin-bottom: 5px;
}
.info-value {
font-weight: bold;
color: #333;
}
.rating {
color: #ff6b6b;
font-size: 1.2em;
}
.year {
color: #4ecdc4;
font-size: 1.1em;
}
.genre {
grid-column: 1 / -1;
background: linear-gradient(45deg, #45b7d1, #4ecdc4);
color: white;
}
.description {
font-size: 0.95em;
color: #666;
line-height: 1.5;
text-align: center;
font-style: italic;
}
.refresh-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
color: white;
border: none;
font-size: 24px;
cursor: pointer;
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
transition: all 0.3s ease;
}
.refresh-btn:hover {
transform: scale(1.1) rotate(180deg);
box-shadow: 0 15px 30px rgba(0,0,0,0.3);
}
.sparkle {
position: absolute;
color: white;
font-size: 2em;
animation: sparkle 3s ease-in-out infinite;
pointer-events: none;
}
@keyframes sparkle {
0%, 100% { opacity: 0; transform: scale(0) rotate(0deg); }
50% { opacity: 1; transform: scale(1) rotate(180deg); }
}
@media (max-width: 768px) {
.title { font-size: 2.2em; }
.movies-container { grid-template-columns: 1fr; }
.movie-info { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="sparkle" style="top: 10%; left: 10%; animation-delay: 0s;">🎬</div>
<div class="sparkle" style="top: 20%; right: 15%; animation-delay: 1s;">🍿</div>
<div class="sparkle" style="bottom: 20%; left: 20%; animation-delay: 2s;">🎭</div>
<div class="sparkle" style="bottom: 30%; right: 10%; animation-delay: 1.5s;">🎪</div>
<div class="header">
<h1 class="title">🎬 인기 영화 랭킹</h1>
<p class="subtitle">Unit 10 스크래핑으로 가져온 실시간 영화 정보!</p>
</div>
<div class="movies-container">
{% for movie in movies %}
<div class="movie-card">
<div class="movie-poster">{{ movie.poster }}</div>
<h2 class="movie-title">{{ movie.title }}</h2>
<div class="movie-info">
<div class="info-item">
<div class="info-label">평점</div>
<div class="info-value rating">⭐ {{ movie.rating }}</div>
</div>
<div class="info-item">
<div class="info-label">연도</div>
<div class="info-value year">📅 {{ movie.year }}</div>
</div>
<div class="info-item genre">
<div class="info-label">장르</div>
<div class="info-value">🎭 {{ movie.genre }}</div>
</div>
</div>
<p class="description">{{ movie.description }}</p>
</div>
{% endfor %}
</div>
<button class="refresh-btn" onclick="location.reload()" title="새로운 영화 보기">
🔄
</button>
</body>
</html>
🎮 메인 작품: 실시간 게임 정보 대시보드
이제 Unit 10의 스크래핑 지식을 사용해서 인기 게임 정보를 보여주는 앱을 만들어봐요!
게임 정보 앱 코드 (app.py에 추가)
# 기존 코드 아래에 이것들을 추가하세요!
import random
from datetime import datetime
def get_game_rankings():
"""인기 게임 정보를 가져오는 함수 (실습용 가상 데이터)"""
try:
# 실제로는 게임 사이트에서 크롤링하지만, 실습에서는 랜덤 데이터 사용
games = [
{
"title": "젤다의 전설: 왕국의 눈물",
"platform": "Nintendo Switch",
"icon": "🗡️",
"rating": "9.8",
"genre": "액션/어드벤처",
"players": random.randint(50000, 200000),
"price": "59,800원",
"bg": "linear-gradient(135deg, #74b9ff, #0984e3)"
},
{
"title": "발더스 게이트 3",
"platform": "PC, PS5",
"icon": "🐉",
"rating": "9.5",
"genre": "RPG",
"players": random.randint(30000, 150000),
"price": "69,800원",
"bg": "linear-gradient(135deg, #a29bfe, #6c5ce7)"
},
{
"title": "스파이더맨 2",
"platform": "PS5",
"icon": "🕷️",
"rating": "9.2",
"genre": "액션",
"players": random.randint(40000, 120000),
"price": "79,800원",
"bg": "linear-gradient(135deg, #fd79a8, #e84393)"
},
{
"title": "슈퍼 마리오 브라더스 원더",
"platform": "Nintendo Switch",
"icon": "🍄",
"rating": "9.0",
"genre": "플랫포머",
"players": random.randint(60000, 180000),
"price": "59,800원",
"bg": "linear-gradient(135deg, #fdcb6e, #e17055)"
},
{
"title": "포르자 호라이즌 5",
"platform": "Xbox, PC",
"icon": "🏎️",
"rating": "8.8",
"genre": "레이싱",
"players": random.randint(25000, 100000),
"price": "Game Pass",
"bg": "linear-gradient(135deg, #00b894, #00cec9)"
}
]
selected_game = random.choice(games)
selected_game["update_time"] = datetime.now().strftime("%H:%M")
return selected_game
except Exception as e:
return {
"title": "게임 정보를 불러올 수 없습니다",
"platform": "알 수 없음",
"icon": "❓",
"rating": "0.0",
"genre": "정보 없음",
"players": 0,
"price": "---",
"bg": "linear-gradient(135deg, #636e72, #2d3436)",
"update_time": "---"
}
@app.route('/games', methods=['GET', 'POST'])
def games():
game_data = get_game_rankings()
return render_template('games.html', game=game_data)
게임 정보 템플릿
templates/games.html
파일을 만들어요:
<!DOCTYPE html>
<html>
<head>
<title>게임 정보 대시보드 🎮</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
background: {{ game.bg }};
min-height: 100vh;
padding: 20px;
color: white;
}
.game-container {
max-width: 500px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 30px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
animation: slideUp 0.8s ease;
}
@keyframes slideUp {
from { transform: translateY(50px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.search-box {
margin-bottom: 30px;
}
.search-input {
width: 100%;
padding: 15px;
border: none;
border-radius: 15px;
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 16px;
backdrop-filter: blur(10px);
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.7);
}
.search-btn {
width: 100%;
margin-top: 10px;
padding: 12px;
border: none;
border-radius: 15px;
background: rgba(255, 255, 255, 0.3);
color: white;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
}
.search-btn:hover {
background: rgba(255, 255, 255, 0.4);
transform: translateY(-2px);
}
.weather-main {
text-align: center;
margin-bottom: 30px;
}
.city-name {
font-size: 1.5em;
margin-bottom: 10px;
opacity: 0.9;
}
.weather-icon {
font-size: 5em;
margin: 20px 0;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
.temperature {
font-size: 4em;
font-weight: 300;
margin-bottom: 10px;
}
.condition {
font-size: 1.3em;
opacity: 0.9;
margin-bottom: 10px;
}
.update-time {
font-size: 0.9em;
opacity: 0.7;
}
.weather-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 30px;
}
.detail-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
text-align: center;
backdrop-filter: blur(10px);
}
.detail-icon {
font-size: 1.5em;
margin-bottom: 10px;
}
.detail-label {
font-size: 0.9em;
opacity: 0.8;
margin-bottom: 5px;
}
.detail-value {
font-size: 1.2em;
font-weight: bold;
}
.refresh-btn {
width: 100%;
margin-top: 20px;
padding: 15px;
border: none;
border-radius: 15px;
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
backdrop-filter: blur(10px);
}
.refresh-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.credits {
text-align: center;
margin-top: 20px;
opacity: 0.6;
font-size: 0.8em;
}
</style>
</head>
<body>
<div class="game-container">
<div class="game-header">
<h2>🎮 오늘의 추천 게임</h2>
</div>
<div class="game-main">
<div class="game-icon">{{ game.icon }}</div>
<div class="game-title">{{ game.title }}</div>
<div class="game-platform">{{ game.platform }}</div>
<div class="update-time">{{ game.update_time }} 업데이트</div>
</div>
<div class="game-details">
<div class="detail-item">
<div class="detail-icon">⭐</div>
<div class="detail-label">평점</div>
<div class="detail-value">{{ game.rating }}/10</div>
</div>
<div class="detail-item">
<div class="detail-icon">🏆</div>
<div class="detail-label">장르</div>
<div class="detail-value">{{ game.genre }}</div>
</div>
<div class="detail-item">
<div class="detail-icon">👥</div>
<div class="detail-label">동시 접속자</div>
<div class="detail-value">{{ game.players:,}}명</div>
</div>
<div class="detail-item">
<div class="detail-icon">💰</div>
<div class="detail-label">가격</div>
<div class="detail-value">{{ game.price }}</div>
</div>
</div>
<button class="refresh-btn" onclick="location.reload()">
🔄 다른 게임 보기
</button>
<div class="credits">
Unit 10의 스크래핑 기술로 만든 게임 정보 앱! 🎉
</div>
</div>
</body>
</html>
💡 Python 웹 스크래핑 핵심 개념
Python에서 웹 데이터를 가져오는 주요 방법들:
기능 | Python 구현 |
---|---|
CSS 선택자로 요소 찾기 | soup.find(class_='class') |
요소의 텍스트 가져오기 | element.text |
웹페이지 요청하기 | requests.get('url').text |
JSON 데이터 처리 | json.loads(data) |
🎯 도전 과제: 더 멋지게!
1. 실제 게임 API 연결하기
# Steam API 사용 예시
import requests
def get_real_game_data():
# Steam의 인기 게임 API (가상 예시)
url = "https://store.steampowered.com/api/featured"
response = requests.get(url)
data = response.json()
return data
2. YouTube 트렌딩 게임 동영상 추가하기
def get_trending_gaming_videos():
# YouTube Gaming 트렌딩 동영상 정보
trending_videos = []
for i in range(5):
# 가상 데이터 생성
trending_videos.append({
'title': f'인기 게임 플레이 {i+1}',
'views': random.randint(10000, 1000000),
'channel': f'게이머 {i+1}'
})
return trending_videos
3. 실시간 Twitch 스트리밍 정보
def get_twitch_streams():
# Twitch API로 인기 게임 스트림 정보 가져오기
streams = []
for i in range(3):
streams.append({
'game': f'인기 게임 {i+1}',
'streamer': f'스트리머 {i+1}',
'viewers': random.randint(1000, 50000)
})
return streams
💡 퀴즈: 스크래핑 이해도 체크
Q1. BeautifulSoup에서 클래스로 요소를 찾으려면?
soup.find('class_name')
soup.find(class_='class_name')
soup.find('.class_name')
💡 정답 확인
정답: 2번
soup.find(class_='class_name')
을 사용해요!
element = soup.find('div', class_='weather-info')
Q2. requests로 웹페이지를 가져올 때 타임아웃을 설정하려면?
requests.get(url, time=5)
requests.get(url, timeout=5)
requests.get(url, wait=5)
💡 정답 확인
정답: 2번
timeout
매개변수를 사용해요!
response = requests.get(url, timeout=5)
⚠️ 스크래핑 시 주의사항
- robots.txt 확인: 크롤링하기 전에 사이트의 robots.txt를 확인하세요
- 요청 간격: 너무 자주 요청하지 마세요 (1초 이상 간격 권장)
- User-Agent 설정: 브라우저처럼 보이도록 설정하세요
- 에러 처리: 네트워크 오류나 파싱 오류를 처리하세요
- 저작권: 크롤링한 데이터의 저작권을 확인하세요
안전한 스크래핑 예시
def safe_scraping(url):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers, timeout=5)
if response.status_code != 200:
raise Exception(f"HTTP 오류: {response.status_code}")
soup = BeautifulSoup(response.text, 'html.parser')
return soup
except requests.exceptions.Timeout:
print("요청 시간이 초과되었습니다.")
except requests.exceptions.ConnectionError:
print("인터넷 연결을 확인해주세요.")
except Exception as e:
print(f"오류가 발생했습니다: {str(e)}")
return None
✅ Topic 3 마스터 체크리스트
🚀 다음 단계
와! Unit 10에서 배운 스크래핑을 엔터테인먼트 웹앱으로 만들었네요!
다음 Topic에서는:
- 여러 기능을 합쳐서 엔터테인먼트 종합 대시보드 만들기
- 영화, 게임, 음악, YouTube 트렌딩을 한 페이지에!
- 개인 맞춤형 엔터테인먼트 허브 완성하기
준비되셨나요? 마지막 대작을 만들러 가봐요! 🚀
Last updated on