백엔드 배포 기간
2024.12.20 (금) ~ 2024.12.23 (월)
배포 플랫폼 : 무료 파이썬 호스팅 사이트 pythonanywhere 이용
단계별 설명
1. 회원가입
2. 파이썬 코드 수정
3. pythonanywhere에 맞게 수정
1. 회원가입
- 사이트 상단의 pricing & signup을 클릭
- 무료버전 사용시 create a beginner account라는 파란색 버튼을 클릭
- 우리 사이트의 경우 가벼운 사이드 프로젝트인데다가, 많은 코드를 요구하지 않기 때문에, 무료버전으로도 충분하여 무료버전으로 생성
- 무료버전 사용시 create a beginner account라는 파란색 버튼을 클릭
- 계정 생성 시
- username : 도메인의 이름이 되는 곳이므로 중요함
- 환경 설정
- Web 카테고리 선택
- Add a new web app 버튼 클릭
- 파이썬 버전 설정 (내가 개발한 환경의 파이썬 버전 --> 3.9)
- Framework 설정 -> Flask 선택 (우리 개발환경)
- Flask 기본 설정이 완료되면 ~/mysite라는 폴더가 생성 됨
- ~ : 홈 디렉터리에 대한 경로를 축약하는 bash의 용어
2. 파이썬 코드 수정
# 라이브러리 import
from flask import Flask, Response, request # flask 모듈에서 필요한 클래스와 객체를 가져옴
import pandas as pd # 데이터 처리를 위한 pandas 라이브러리
import numpy as np # 샘플링과 수학적 계산을 위한 numpy 라이브러리
import json # JSON 응답을 생성하기 위한 라이브러리
from collections import OrderedDict # 열 순서를 유지하기 위한 OrderedDict 클래스
from flask_cors import CORS
app = Flask(__name__) # flask 앱 초기화
CORS(app)
# 데이터 파일 경로 (추후 인터넷 URL로 변경 예정)
DATA_URL = "https://raw.githubusercontent.com/BTBIIT/lottery/refs/heads/main/data/lottoDB.csv"
##############################################################################################################################################
# 함수 선언부 #
##############################################################################################################################################
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 목 적 : 데이터를 로드하고 정렬하는 함수
# 메서드 : CSV 파일을 pandas 데이터프레임으로 로드 후 최신 날짜 기준으로 정렬
# 반환값 : sorted_data -> 최신 날짜로 정렬된 data 프레임
def load_and_sort_data():
# CSV 파일을 pandas 데이터프레임으로 로드
lotto_data = pd.read_csv(DATA_URL)
# 최신 날짜 기준으로 정렬 (마지막 줄이 첫 번째 줄로 올라옴)
sorted_data = lotto_data.iloc[::-1].reset_index(drop=True)
return sorted_data
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 목 적 : 특정한 확률을 적용하여 숫자 6개를 뽑는 함수 (1회 추출)
# 메서드 : 추첨 데이터에서 필요한 열만 추출 -> 모든 숫자를 1차원 배열로 변환 ->
# 숫자별 등장 횟수 계산 -> 총 추첨 횟수를 통한 각 숫자의 확률 계산(빈도 수) ->
# 확률에 기반해 6개의 숫자를 샘플링하여 추출 -> 리스트로 반환
# 반환값 : 리스트로 반환된 1회의 추출 숫자 수
def single_draw(data):
# 추첨 데이터에서 필요한 열만 추출
numbers = data[["one", "two", "three", "four", "five", "six"]]
# 모든 숫자를 1차원 배열로 변환
all_numbers = numbers.values.flatten()
# 숫자별 등장 횟수 계싼
number_counts = pd.Series(all_numbers).value_counts().sort_index()
# 총 추첨 횟수
total_draws = len(data)
# 각 숫자의 확률 계산 (빈도 수 계산)
number_probabilities = number_counts / (6 * total_draws)
# 확률에 기반하여 6개의 숫자를 샘플링
sampled_numbers = np.random.choice(
number_counts.index, size=6, replace=False, p=number_probabilities.values
)
# 리스트로 반환
return sampled_numbers.tolist()
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 목 적 : 특정 범위 횟수안에서 등장한 숫자를 가지고 숫자 6개를 뽑는 함수
# 메서드 : 특정 범위를 선택 (최근 n회의 데이터 선택) -> 이후 single_draw와 동일
def limited_draw(data, recent_count):
# 최근 n회 데이터만 선택
recent_data = data.head(recent_count)
numbers = recent_data[["one", "two", "three", "four", "five", "six"]]
all_numbers = numbers.values.flatten()
# 숫자별 빈도 계산 및 확률 계산
number_counts = pd.Series(all_numbers).value_counts().sort_index()
total_draws = len(recent_data)
number_probabilities = number_counts / (6 * total_draws)
# 샘플링
sampled_numbers = np.random.choice(
number_counts.index, size=6, replace=False, p=number_probabilities.values
)
return sampled_numbers.tolist()
##############################################################################################################################################
# API 반환부 #
##############################################################################################################################################
# Backend 코드 수정
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 수정일 : 2024-12-12
# 목 적 : 복권 데이터를 정렬된 형식으로 반환하는 API
# 입력값 : 없음
# 수정 전 메서드 : 1. 데이터를 로드하고 최신 날짜 기준으로 정렬 / 2. 열 순서를 유지하여 JSON 포맷으로 변환
# 수정 후 메서드 : 1. 데이터를 로드 후 페이지 번호와 한 페이지당 항목 수를 쿼리파라미터로 받음
# 2. 데이터의 시작과 끝 인덱스 계산 / 3. 데이터가 없을 경우 에러처리 / 4. JSON 포맷으로 변환하여 전송
# 반환값 : JSON 형식으로 정렬된 복권 데이터
@app.route("/api/data", methods=["GET"])
def get_paginated_data():
# 최신 데이터 로드
data = load_and_sort_data()
if data is None:
return Response(json.dumps({"error": "데이터를 로드할 수 없습니다."}), status=500, mimetype='application/json')
# 페이지 번호와 한 페이지당 항목 수를 쿼리 파라미터로 받기 (기본값: page=1, limit=10)
page = request.args.get("page", default=1, type=int)
limit = request.args.get("limit", default=10, type=int)
# 데이터의 시작 및 끝 인덱스 계산
start_index = (page - 1) * limit
end_index = start_index + limit
# 해당 페이지에 해당하는 데이터 추출
paginated_data = data.iloc[start_index:end_index]
# 데이터가 없을 경우 에러 처리
if paginated_data.empty:
return Response(json.dumps({"error": "더 이상 데이터가 없습니다."}), status=404, mimetype='application/json')
# 열 순서 유지
column_order = ["Index", "date", "one", "two", "three", "four", "five", "six", "bonus"]
if set(column_order) == set(data.columns):
paginated_data = paginated_data[column_order]
# JSON으로 변환 (OrderedDict를 사용해 열 순서 유지)
json_data = [
OrderedDict((col, row[col]) for col in column_order) for _, row in paginated_data.iterrows()
]
# JSON 문자열로 변환 후 응답
return Response(json.dumps(json_data, ensure_ascii=False), mimetype='application/json')
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 목 적 : 확률에 기반하여 숫자 6개를 1회 추출 하는 API
# 입력값 : 없음
# 메서드 : 1. 전체 데이터를 로드 / 2. 숫자 6개를 샘플링하는 single_draw 함수 호출
# 반환값 : JSON 형식으로 반환된 숫자 6개
@app.route("/api/single-draw", methods=["GET"])
def get_single_draw():
data = load_and_sort_data()
draw_result = single_draw(data)
return Response(json.dumps({"single_draw": draw_result}, ensure_ascii=False), mimetype='application/json')
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 목 적 : 확률에 기반하여 숫자 6개를 5번 추출하는 API
# 입력값 : 없음
# 메서드 : 1. 전체 데이터를 로드 / 2. 숫자 6개를 5번 샘플링하여 리스트로 반환
# 반환값 : JSON 형식으로 반환된 숫자 6개를 5번 추출한 결과
@app.route("/api/multiple-draws", methods=["GET"])
def get_multiple_draws():
data = load_and_sort_data()
results = []
for _ in range(5):
results.append(single_draw(data))
return Response(json.dumps({"multiple_draws": results}, ensure_ascii=False), mimetype='application/json')
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 목 적 : 특정 범위 내에서 숫자 6개를 추출하는 API
# 입력값 : n (URL 쿼리 파라미터, 기본값 100 -> 기본값 100 추후 삭제 예정)
# 메서드 : 1. 최근 n회의 데이터를 로드 / 2. 제한된 범위 내에서 숫자 6개를 샘플링하는 limited_draw 함수 호출
# 반환값 : JSON 형식으로 반환된 숫자 6개
@app.route("/api/draw-limited", methods=["GET"])
def get_limited_draw():
# 최근 n회 값 가져오기
n = request.args.get("n", default=100, type=int)
print(f"Received n value: {n}") # n 값 확인
data = load_and_sort_data()
# n회 이상 요청 시 전체 데이터로 제한
if n > len(data):
n = len(data)
# 숫자 추출
draw_result = limited_draw(data, n)
return Response(json.dumps({"limited_draw": draw_result}, ensure_ascii=False), mimetype='application/json')
# 작성자 : 박건혁
# 작성일 : 2024-12-09
# 목 적 : 특정 범위 내에서 숫자 6개를 5번 추출하는 API
# 입력값 : n (URL 쿼리 파라미터, 기본값 100 -> 기본값 100 추후 삭제 예정)
# 메서드 : 1. 최근 n회의 데이터를 로드 / 2. 제한된 범위 내에서 숫자 6개를 5번 샘플링하는 `limited_draw` 함수 호출
# 반환값 : JSON 형식으로 반환된 숫자 6개를 5번 추출한 결과
@app.route("/api/draw-limited-multiple", methods=["GET"])
def get_limited_multiple_draws():
# 최근 n회 값 가져오기
n = request.args.get("n", default=100, type=int)
data = load_and_sort_data()
# n회 이상 요청 시 전체 데이터로 제한
if n > len(data):
n = len(data)
# 5번 추출
results = []
for _ in range(5):
results.append(limited_draw(data, n))
return Response(json.dumps({"limited_multiple_draws": results}, ensure_ascii=False), mimetype='application/json')
# Flask 앱 실행 ## 수정된 부분 -> debug = True -> False로 변경 (배포 시)
if __name__ == "__main__":
app.run(debug=False)
- 배포에 맞게 debug 부분을 False로 수정함
requirements.txt 생성
파이썬 코드에서 필요한 파일 설정들을 모은 라이브러리 명칭 모음
3. pythonanywhere에 맞게 수정
- PythonAnywhere에서 지원하는 Python 버전과 코드가 호환되는지 확인
- requirement.txt를 작성하여 필요한 Python 패키지를 정의
- 파일 업로드
- 1. Files 탭으로 이동
- 2. ~/mysite 디렉터리에 Flask 애플리케이션 코드를 업로드
- 2-1 lotto_service_deploy.py 파일 업로드
- 2-2 requirements.txt 파일 업로드
- 2-3 ~/mysite 폴더 구조 예시
- ~/mysite/
- ├── lotto_service.py
- ├── requirements.txt
- └── flask_app.py (기본 Flask 엔트리포인트)
- ~/mysite/
- WSGI 설정
- Web 탭으로 이동
- WSGO configuration file 링크를 클릭
- WSGI 파일을 수정하여 Flask 앱 경로를 수정
import sys
# 올바른 Python 경로 추가
path = '/home/lottokorea/mysite'
if path not in sys.path:
sys.path.append(path)
# Flask 애플리케이션 로드
from lotto_service_deploy import app as application
- 여기서 lottokorea는 나의 username
- 종속성 설치
- 1. PythonAnywhere의 Dashboard 창에서 Consolas 창으로 이동 후 Bash 창을 열고 다음 명령을 실행하여 requirements.txt 에 정의 된 패키지를 설치 (이슈 발생 부분)
- pip install --user -r ~/mysite/requirements.txt (나는 여기서 실수를 하고 말았다.)ㅇ
- 앱 재시작
- Web 탭으로 이동
- Reload 버튼을 클릭하여 웹 앱을 다시 시작
- 테스트
- 테스트 결과

- 이슈 해결
- 파이썬 3.9버전으로 작성하였고, Flask도 3.9버전에 맞게 설정을 했지만
- bash 창에서 python --version을 쳐보니 3.10 버전으로 출력되는 것을 확인할 수 있었음
- 즉, 내가 작성한 코드는 3.9버전인데 3.10 버전에 맞는 라이브러리들을 설치받아 작동되지 않았던 것
- 수정 코드
- python3.9 -m pip install --user -r ~/mysite/requirements.txt (3.9 버전에 맞게 requirements.txt 안의 패키지를 설치함)
- Web 재시작 후 배포를 태스트 하니 제대로 나오는 것을 확인할 수 있었음.
'사이드프로젝트' 카테고리의 다른 글
| [Mori] 2일차 (0) | 2025.06.26 |
|---|---|
| [Mori] 1일차 (0) | 2025.06.24 |
| [lottery] 추첨 기록 로그 컴포넌트 생성 및 추후 계획 (0) | 2024.12.23 |
| [lottery] 깃 허브 깃 용어 정리 (0) | 2024.12.17 |
| [lottery] 드롭다운 영역 특수 추첨 api 적용 (2) | 2024.12.17 |