NBA 통계자료는 NBA공식 홈페이지에서 API를 이용해 얻을 수 있습니다.
저는 이 자료를 매일 한 번씩 얻어와 데이터베이스에 저장해놓고, 파이프라인을 구축해 이를 홈페이지로 가져와 띄울 것이므로, 먼저 NBA 통계자료를 를 저장해놓을 데이터베이스를 하나 만들어놓겠습니다.
DBMS은 MySQL을 이용할 것이므로, MySQL에 접속해 NBA 통계자료를 담을 DB를 하나 만들어놓습니다.
데이터베이스의 이름은 nba_stats
로 하기로 하죠.
콘솔 창으로 MySQL에 접속해 다음의 쿼리문을 날립니다.
CREATE DATABASE 'nba_stats';
SHOW DATABASES;
확인 결과 자료를 저장해 놓을 DB가 잘 만들어졌습니다 !
다음으로, 얻고 싶은 자료는 어떻게 얻을 수 있는지 고민해보겠습니다.
일차적으로 생각난 방법은 NBA 공식 홈페이지에서 들어가, 통계 페이지의 자료를 API를 통해 스크래핑해오는 방법인데요.
NBA 공식 홈페이지로 들어가 보니, Player 메뉴에 제가 원하는 정보가 있었습니다.
(물론 추후에 다른 항목들의 자료도 페이지에 추가하면 좋을 것 같습니다!)
먼저, 출전 시간, 승률, 경기당 득점 등의 1차 스텟을 의미하는 Traditional Stats 메뉴에 들어가 API를 어디서 찾을 수 있을지 알아보겠습니다.
메뉴로 들어가니 다음과 같은 화면이 나왔는데요,
관리자 화면을 통해 API Endpoint로 활용할 Requset URL을 찾았습니다.
이제 우리는 Python으로 request문을 통해 필요한 정보를 얻어 올 수 있게 되었습니다.
이번 프로젝트에서는 간단한 ORM인 PeeWee를 이용하기로 했습니다.
Peewee를 이용하는 방법은 아래의 친절하게 작성된 공식 메뉴얼을 참고했습니다.
http://docs.peewee-orm.com/en/latest/peewee/database.html
ORM의 효용은 여러 가지가 있지만, 이번 프로젝트에서 SQL문을 직접 짜지 않고 ORM을 이용하는 이유는 다음 두 가지입니다.
ORM이 무엇인지 궁금하신 분들은 아래의 페이지를 참고하시기 바랍니다.
https://velog.io/@toezilla/DB-ORM%EC%9D%B4%EB%9E%80
클래스와 객체를 미리 만들어 놓는 작업을 진행해보겠습니다.
[settings.py](http://settings.py)
다음으로, PeeWee에서는 데이터베이스의 Table을 Model의 class로 정의합니다. 따라서, 여러 Model들로 필요한 테이블을 만들텐데, 우선 recommendation 대로 BaseModel을 하나 정의하고, 이를 확장해 다른 모델들을 만들어보겠습니다.
BaseModel.py
BaseModel을 정의합니다.
from peewee import *
from settings import Settings
settings = Settings()
class BaseModel(Model):
class Meta:
database = settings.db
PlayerGeneralTraditional.py
playergeneraltraditional stats을 담을 Table을 위한 Model class를 정의합니다.
nba 통계 사이트에서 API에 대한 접근을 특정 헤더로만 허용해서, headers
에 이 내용을 담고 request문을 날릴 때 이용했습니다.
season_list
에 통계 자료가 존재하는 1996-1997년도부터 올해까지 시즌을 담고, request에 대한 url을 날릴 때 for문을 돌려 모든 시즌의 자료를 DB에 저장했습니다.
Playoff 데이터는 따로 관리해야 하므로, 추후에 season type을 Playoff로 바꾸어 모델을 하나 더 만들어 각자 다른 테이블로 관리할 예정입니다.
import requests
from settings import Settings
from Models import PlayerGeneralTraditionalTotals
settings = Settings()
settings.db.create_tables([PlayerGeneralTraditionalTotals], safe=True)
headers = {
'Connection': 'keep-alive',
'Accept': 'application/json, text/plain, */*',
'x-nba-stats-token': 'true',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36',
'x-nba-stats-origin': 'stats',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'cors',
'Referer': 'https://stats.nba.com/',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9',
}
season_list = [
'1996-97',
'1997-98',
'1998-99',
'1999-00',
'2000-01',
'2001-02',
'2002-03',
'2003-04',
'2004-05',
'2005-06',
'2006-07',
'2007-08',
'2008-09',
'2009-10',
'2010-11',
'2011-12',
'2012-13',
'2013-14',
'2014-15',
'2015-16',
'2016-17',
'2017-18',
'2018-19',
'2019-20',
'2020-21',
'2021-22'
]
per_mode = 'Totals'
# season_type = 'Playoffs'
season_type = 'Regular+Season'
for season_id in season_list:
player_info_url = 'https://stats.nba.com/stats/leaguedashplayerstats?College=&Conference=&Country=&DateFrom=&DateTo=&Division=&DraftPick=&DraftYear=&GameScope=&GameSegment=&Height=&LastNGames=0&LeagueID=00&Location=&MeasureType=Base&Month=0&OpponentTeamID=0&Outcome=&PORound=0&PaceAdjust=N&PerMode={}&Period=0&PlayerExperience=&PlayerPosition=&PlusMinus=N&Rank=N&Season={}&SeasonSegment=&SeasonType={}&ShotClockRange=&StarterBench=&TeamID=0&TwoWay=0&VsConference=&VsDivision=&Weight='.format(
per_mode, season_id, season_type)
# json response
response = requests.get(url=player_info_url, headers=headers).json()
# pulling just the data we want
player_info = response['resultSets'][0]['rowSet']
for row in player_info:
player = PlayerGeneralTraditionalTotals(
season_id=season_id,
player_id=row[0],
player_name=row[1],
team_id=row[2],
team_abbreviation=row[3],
age=row[4],
gp=row[5],
w=row[6],
l=row[7],
w_pct=row[8],
min=row[9],
fgm=row[10],
fga=row[11],
fg_pct=row[12],
fg3m=row[13],
fg3a=row[14],
fg3_pct=row[15],
ftm=row[16],
fta=row[17],
ft_pct=row[18],
oreb=row[19],
dreb=row[20],
reb=row[21],
ast=row[22],
tov=row[23],
stl=row[24],
blk=row[25],
blka=row[26],
pf=row[27],
pfd=row[28],
pts=row[29],
plus_minus=row[30],
nba_fantasy_pts=row[31],
dd2=row[32],
td3=row[33],
gp_rank=row[34],
w_rank=row[35],
l_rank=row[36],
w_pct_rank=row[37],
min_rank=row[38],
fgm_rank=row[39],
fga_rank=row[40],
fg_pct_rank=row[41],
fg3m_rank=row[42],
fg3a_rank=row[43],
fg3_pct_rank=row[44],
ftm_rank=row[45],
fta_rank=row[46],
ft_pct_rank=row[47],
oreb_rank=row[48],
dreb_rank=row[49],
reb_rank=row[50],
ast_rank=row[51],
tov_rank=row[52],
stl_rank=row[53],
blk_rank=row[54],
blka_rank=row[55],
pf_rank=row[56],
pfd_rank=row[57],
pts_rank=row[58],
plus_minus_rank=row[59],
nba_fantasy_pts_rank=row[60],
dd2_rank=row[61],
td3_rank=row[62],
cfid=row[63],
cfparams=row[64])
player.save()
print("Done inserting player general traditional season total data to the DB!")
우리가 이용할 전체 class들을 담을 파일입니다.
우선은 BaseModel과 PlayerGeneralTraditionalTotals 모델만 만들었으므로, 두 모델만 init 파일에 담도록 합시다.
모델이 추가되면 이 파일 안에 잊지 말고 담아야 합니다.
from .BaseModel import BaseModel
from .PlayerGeneralTraditionalTotals import PlayerGeneralTraditionalTotals
데이터베이스가 잘 생성되었는지 확인해봅니다.
먼저 SQL로!
12307개의 데이터가 잘 저장되었군요.
다음엔 주피터 노트북을 통해 실제 데이터를 데이터프레임 형식으로 가져와 눈으로 확인해봅시다!
참고로, 데이터프레임으로 바꾸기 위한 모듈은 pyMySQL을 이용했습니다.
데이터가 예상한 형식대로 잘 저장되었군요.
다음에는 Playoff 테이블을 같은 과정을 통해 만들어보고,
올해 NBA 플레이오프 일정에 맞춰서 매일 업데이트되는 통계자료를 DB로 가져오는 방법을 고민해보겠습니다.