어느 정도 BOJ 조회 Python 코드가 작성됐으니, 이젠 이 함수들을 REST API로 바꿔봅시다!
저는 FastAPI를 이용해 기존 코드를 API로 구현했습니다. 이번에는 FastAPI 프로젝트 구조에 대해 포스팅해보겠습니다.
프로젝트 구조
app/
│── main.py # FastAPI 실행 진입점
│── config.py # 환경 변수 관리
│── services/
│ ├── selenium_driver.py # WebDriver 세션 유지
│ ├── auth.py # 로그인 기능 + 쿠키 저장
│ ├── scraper.py # 제출 내역 크롤링 & 코드 가져오기
│── routers/
│ ├── auth_routes.py # 로그인 관련 API 라우트
│ ├── submission_routes.py # 제출 내역 및 코드 조회 API 라우트
│── models/
│ ├── schemas.py # Pydantic 데이터 모델 정의
│── requirements.txt # 필요한 라이브러리 목록
services 디렉토리
services는 외부 서비스나 내부 비즈니스 로직을 처리하는 코드들을 모아둔 폴더입니다.
selenium_driver.py
Selenium WebDriver 세션을 생성하고 유지하는 모듈입니다. Selenium을 통해 BOJ 페이지에 접속하고, 필요한 데이터를 가져올 때 재사용할 수 있도록 WebDriver 인스턴스를 관리합니다.
from selenium import webdriver
import time
driver = None
def get_driver():
global driver
if driver is None:
options = webdriver.ChromeOptions()
# options.add_argument("--headless") # GUI 없이 실행
# options.add_argument("--disable-gpu")
# options.add_argument("--window-size=1920x1080")
# options.add_argument("--no-sandbox")
# options.add_argument("--disable-dev-shm-usage")
# options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36")
driver = webdriver.Chrome(options=options)
return driver
def fetch_url(url):
get_driver()
driver.get(url)
time.sleep(3) # 페이지 로드 대기
page_source = driver.page_source
return page_source
boj_auth.py
BOJ 로그인 기능과 쿠키 저장 로직을 담당합니다. BOJ에 로그인한 상태를 유지하기 위해 세션 쿠키를 잘 관리해야 하므로, 이 모듈에서는 로그인 처리와 세션 유지에 관한 메서드를 구현합니다.
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from app.services.selenium_driver import get_driver
from app.config import BASE_URL
def login(boj_id: str, boj_pwd: str):
driver = get_driver()
driver.get(f"{BASE_URL}/signin")
try:
# 최대 10초 동안 로그인 필드가 나타날 때까지 대기
id_input = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.NAME, "login_user_id"))
)
id_input.send_keys(boj_id)
pw_input = driver.find_element(By.NAME, "login_password")
pw_input.send_keys(boj_pwd)
pw_input.send_keys(Keys.RETURN)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.LINK_TEXT, "로그아웃"))
)
print(f"{boj_id} 로그인 성공")
return True
except Exception as e:
print(f"로그인 실패: {e}")
driver.quit()
return False
html_parser.py
크롤링된 제출 내역으로부터 특정 제출 번호에 대한 코드를 가져오는 메인 로직이 담긴 파일입니다.
from bs4 import BeautifulSoup
def parse_submission_row(columns):
submission_id = columns[0].text.strip() # 제출 ID
user_id = columns[1].find("a").text.strip() # 사용자 아이디
problem_id = columns[2].find("a").text.strip() # 문제 번호
problem_title = columns[2].find("a").get("title", "").strip() # 문제 제목
result = columns[3].find("span").text.strip() # 채점 결과
memory = columns[4].text.strip() + "KB" # 메모리 사용량
time = columns[5].text.strip() + "ms" # 실행 시간
language = columns[6].text.strip() # 사용 언어
code_length = columns[7].text.strip() + "B" # 코드 길이
return {
"submission_id": submission_id,
"user_id": user_id,
"problem_id": problem_id,
"problem_title": problem_title,
"result": result,
"memory": memory,
"time": time,
"language": language,
"code_length": code_length,
}
def parse_submission_list(html_content):
soup = BeautifulSoup(html_content, "html.parser")
submission_list = {}
for row in soup.select("tr"):
cols = row.find_all("td")
if len(cols) == 0:
continue
info = parse_submission_row(cols)
submission_list[info["problem_id"]] = info
return submission_list
def parse_source_code(html_content):
soup = BeautifulSoup(html_content, "html.parser")
code_box = soup.find("textarea", {"class": "codemirror-textarea"})
source_code = code_box.text.strip() if code_box else "코드 없음"
return source_code
routers 디렉토리
routers는 실제로 API 라우트가 정의되는 폴더입니다.
boj.py
BOJ 로그인 기능과 제출 내역 조회 및 제출 코드를 조회하는 API 라우트가 이곳에 정의됩니다.
from fastapi import APIRouter, HTTPException
from app.services.auth import login
from app.services.html_parser import parse_submission_list, parse_source_code
from app.services.selenium_driver import fetch_url
from app.models.schemas import BojLoginRequest
from app.config import BASE_URL
router = APIRouter(prefix="/boj", tags=["Boj"])
@router.post("/login")
def login_api(request: BojLoginRequest):
if login(request.boj_id, request.boj_pwd):
return {"message": "Login successful", "status_code": 200}
else:
return {"message": "Login failed", "status_code": 401}
@router.get("/submissions/{boj_id}")
def get_submission_list(boj_id: str):
status_url = f"{BASE_URL}/status?user_id={boj_id}&result_id=4"
html_content = fetch_url(status_url)
submission_list = parse_submission_list(html_content)
if not submission_list:
raise HTTPException(status_code=404, detail="No submission list")
return submission_list
@router.get("/submissions/code/{submission_id}")
def get_source_code(submission_id: str):
status_url = f"{BASE_URL}/source/{submission_id}"
html_content = fetch_url(status_url)
source_code = parse_source_code(html_content)
if not source_code:
raise HTTPException(status_code=404, detail="No source code")
return source_code
models 디렉토리
이 디렉토리에는 주로 데이터 모델을 정의하는 파일들이 들어갑니다. FastAPI와 함께 사용되는 Pydantic 모델이나, DB 모델이 있다면 이 폴더에서 관리합니다.
schemas.py
Pydantic을 이용해 데이터 모델(스키마)을 정의한 파일입니다. FastAPI에서 요청이나 응답으로 주고받을 데이터 형태를 명확히 기술함으로써, 입력 데이터 검증과 자동 문서화가 이뤄집니다.
from pydantic import BaseModel
class BojLoginRequest(BaseModel):
boj_id: str
boj_pwd: str
main.py
FastAPI 애플리케이션의 진입점 파일입니다. FastAPI 객체를 생성하고, routers 폴더 내에 정의된 라우트를 연결(등록)합니다. 또, 서버 실행을 위한 uvicorn.run 함수를 호출할 때 필요한 설정도 이곳에서 구성합니다.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import boj_routers
app = FastAPI()
app.include_router(boj_routers.router)
@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
config.py
환경 변수를 관리하는 모듈입니다. 예를 들어 API 토큰, DB 접속 정보, Selenium 설정과 같은 민감한 정보를 .env 파일 또는 운영 환경에서 받을 수 있도록 구성할 수 있습니다.
import os
BASE_URL = "https://www.acmicpc.net"
'백엔드' 카테고리의 다른 글
[BOJ-AutoSync] 4. Github OAuth Apps 연동하기 (0) | 2025.03.16 |
---|---|
[BOJ-AutoSync] 2. selenium으로 BOJ 로그인하고 코드 불러오기 (0) | 2025.03.14 |
[BOJ-AutoSync] 1. requests와 bs4로 BOJ 크롤링하기 (0) | 2025.03.14 |
[BOJ-AutoSync] 0. 프로젝트 기획 (0) | 2025.03.14 |