📰

[리그오브레전드 데이터 분석] 롤 데이터 조회용 좌표 생성

작성일
2023/02/07
분류
탐색

배경

제품용 API도 발급 받았으니, 이제는 API 호출을 대용량으로 보낼 수 있게 되었다!
이제 분석을 위한 큰 데이터를 저장할 차례다.
그리고 지금까지 API를 탐색해본 결과, 롤 한 판 씩마다 고유의 queue_id를 갖고 있다는 것을 알 수 있었다. ex) KR_6324298945
그럼 랭크 게임 10000 개의 데이터를 수집하려면, 끝의 숫자만 10000번 바꿔가면서 해당 게임 API를 불러오면 되지 않을까? 라는 안일한 생각을 해봤다. ex) KR_6324298945 부터 KR_6325308945
index=0 # 수집할 게임 수 초기값 df3=pd.DataFrame(columns=['index','match_id','game_tier','date','year','month','day','hour','minute','second']) for i in range (6324298945,6324308945): # 1씩 10000번 증가시키며 10000번 호출 match_id = 'KR_' + str(i) summoner_api = 'https://asia.api.riotgames.com/lol/match/v5/matches/'+match_id+'?api_key='+api_key r = requests.get(summoner_api,headers=request_header) try: if pd.DataFrame(r.json())['info']['queueId']==420: timestamp=pd.DataFrame(r.json())['info']['gameStartTimestamp'] d = datetime.fromtimestamp(int(timestamp/1000)).strftime("%Y-%m-%d %H:%M:%S") d = datetime.strptime(d,"%Y-%m-%d %H:%M:%S") year = d.year month = d.month day = d.day hour = d.hour minute = d.minute second = d.second puuid=r.json()['metadata']['participants'] summoner_api = 'https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/'+puuid[0]+'?api_key='+api_key r = requests.get(summoner_api,headers=request_header) user_id=r.json()['id'] summoner_api = 'https://kr.api.riotgames.com/lol/league/v4/entries/by-summoner/'+user_id+'?api_key='+api_key r = requests.get(summoner_api,headers=request_header) game_tier = r.json()[0]['tier']+'-'+r.json()[0]['rank'] index+=1 df3=pd.concat([df3,pd.DataFrame([[index, match_id, game_tier, d, year, month, day, hour, minute, second]], columns=['index','match_id','game_tier', 'date','year','month','day','hour','minute','second'])]) print(index, datetime.now()) except: pass if index == 100 : # 랭크 게임 100개 수집되면 종료 break
Python
복사
요로코롬 100개의 랭겜이 쌓였다!
그리고 참고로 queue_id가 증가한다고(match_id) 게임 시작 시간(date)이 정비례로 증가하지는 않았다.
아무래도 게임 자체가 시작되는 시간으로 queue_id가 부여되지만, 그 10명의 유저가 그 게임에 로딩이 끝나고 진짜 게임이 시작되는 시간으로 타임 스탬프가 찍혀서 정확하게 정비례 관계가 되지 않는 듯하다.
근데 문제는 이 랭크 게임이 한 번 찍힐 때마다 해당 시간이 출력되도록 세팅해놓았는데, 1 게임이 찍히는데 몇 초씩 걸려서, 결과적으로 1분 동안 약 20 게임밖에 저장하지 못했다.
10000개의 데이터를 탐색하려면 이런.. 83시간이 걸린다..
그렇다고 이 raw 데이터를 전부 개인적으로 저장해두기에도 비용적으로 부담되고, 그렇다고 매번 10000개의 데이터를 탐색해서 분석을 진행하는 것도 매우 비효율적인 셈이다.
어떻게 하면 API를 가장 효율적으로 쓰면서, 내 저장 공간은 쓰지 않을 수 있을까..
우선 내가 롤 데이터를 분석한다는 건 어떤 RAW 데이터가 필요할 것이다.
어떤 패치 때의 특정 챔피언어떤 라인에 갔었는지 좌표 리스트만 내가 확보하고 있다면, 분석할 때 해당 좌표 리스트에 대한 api만 쏙쏙 쏴서 불러온다면, 데이터 확보하는 시간을 최소화하면서도 내가 모든 게임 데이터를 항상 수집해둬야 할 필요를 줄일 수 있지 않을까?
그럼 모든 랭크 게임에 대한 챔피언, 라인, 게임 티어, 패치 버전, 날짜, 승패 데이터 좌표를 만들어보자! 라는 결론

좌표 생성 (본론)

# api_key = 본인 API KEY 입력 import requests import pandas as pd from pandas.io.json import json_normalize import json import time import numpy as np from datetime import datetime request_header={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36", "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "Accept-Charset": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "https://developer.riotgames.com", "X-Riot-Token": api_key} game_list = pd.DataFrame(columns = ['match_id','game_version','game_date','game_tier','champion_name','position','win','my_collect_date']) collect_cnt = 0 for i in range (6334298945,6334308945): # 게임 10000 경기 (랭겜,일반,칼바람 등등 전부 포함) match_id = 'KR_' + str(i) summoner_api = 'https://asia.api.riotgames.com/lol/match/v5/matches/'+match_id+'?api_key='+api_key r = requests.get(summoner_api,headers=request_header) # print(pd.DataFrame(r.json())['info']['queueId']) try: if pd.DataFrame(r.json())['info']['queueId']==420: timestamp=pd.DataFrame(r.json())['info']['gameStartTimestamp'] d = datetime.fromtimestamp(int(timestamp/1000)).strftime("%Y-%m-%d %H:%M:%S") d = datetime.strptime(d,"%Y-%m-%d %H:%M:%S") game_version = pd.DataFrame(r.json())['info']['gameVersion'] puuid=r.json()['metadata']['participants'] summoner_api = 'https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/'+puuid[0]+'?api_key='+api_key r1 = requests.get(summoner_api,headers=request_header) user_id=r1.json()['id'] summoner_api = 'https://kr.api.riotgames.com/lol/league/v4/entries/by-summoner/'+user_id+'?api_key='+api_key r2 = requests.get(summoner_api,headers=request_header) game_tier = r2.json()[0]['tier']+'-'+r2.json()[0]['rank'] for user in pd.DataFrame(r.json())['info']['participants'] : user_champion = user['championName'] user_position = user['teamPosition'] win = user['win'] game_list = pd.concat([game_list,pd.DataFrame([[match_id,game_version,d,game_tier,user_champion,user_position,win,datetime.now()]],columns = ['match_id','game_version','game_date','game_tier','champion_name','position','win','my_collect_date'])]) collect_cnt+=1 print(match_id, datetime.now()) except: pass if collect_cnt == 10000 : # 랭겜이 10000 경기 수집되어도 중지 print('finish!') break
Python
복사
게임 10000 경기 중에 랭크 게임만 골라서 챔피언, 라인, 게임 티어, 패치 버전, 날짜, 승패 데이터만 쌓아보았다.
이 10000 경기에는 랭겜, 일반, 칼바람, 사용자 게임 등등이 전부 포함되어 있는데, 이게 하나하나 json을 탐색해보지 않고서는 이 경기가 랭크 게임이었는지 알 수가 없다.
즉, 랭크 게임만 불러오는 api가 없어서, 이렇게 다 한 번씩 훑어봐야한다.
그걸 확인하는 부분이 pd.DataFrame(r.json())['info']['queueId']==420 이다.
자세한 내용은 여기를 참조!
자고 일어나니 10000개를 훑어 놓았다! 정확히는 1시간 16분이 걸렸다.
데이터는 16010 row가 쌓였다.
한 게임당 10개의 챔피언이 있으니, 일부러 10개의 row가 생기도록 만들었다.
나중에 챔피언 단위로 조건을 줘서 검색할 때는 어차피 중복이 안 일어나면서 전체 데이터를 조회할 수 있기 때문에 가장 효율적이라 생각했음
결과적으로 10000개의 게임 중 랭크 게임은 1601개였다.
이로써 1분에 랭크 게임 20 경기를 수집할 수 있다는 것도 확실해졌다. ( 내 노트북, development API 기준으로 )

좌표 써보기

이제 이런 식으로 저장된 좌표 데이터를, 사용하는 입장으로 사용해보자!
요즘 핫한 케이틀린 서폿 데이터를 봐보자 ㅋㅋ
검색 조건
이번 패치 (13.1)
챔프 : 케이틀린
라인 : 서폿
game_list[(game_list['game_version']=='13.1.489.3737')&(game_list['champion_name']=='Caitlyn')&(game_list['position']=='UTILITY')]
Python
복사
game_list[(game_list['game_version']=='13.1.489.3737')&(game_list['champion_name']=='Caitlyn')&(game_list['position']=='UTILITY')]['win'].value_counts()
Python
복사
오우야 서폿 케틀 승률 36%.. 1601 경기 중 22건이니 픽률은 1.3%다. (표본은 너무 적으니 재미로만!)
암튼 내가 궁극적으로 지금 확인하고 싶은 건
이 좌표 리스트로 내가 원하는 데이터를 불러오는 게 확실히 빨라지는가
어느 정도로 빨라지는가이다.
서폿 케틀은 표본 수가 너무 적으니
요즘 많이 나오는 바루스, 진, 애쉬가 있는 경기 전부 뽑고 해당 게임 kda와 상대 라이너 챔프를 조회하는데 얼마나 걸리는지 봐보자
jhin_ashe_varus = game_list[(game_list['game_version']=='13.1.489.3737')&(game_list['champion_name'].isin(['Jhin','Ashe','Varus']))][['match_id','champion_name','position']] jhin_ashe_varus
Python
복사
game_data = pd.DataFrame(columns = ['match_id','game_version','game_date','game_tier','champion_name','position','versus','kill','death','assist','win','my_collect_date']) for i in range (len(jhin_ashe_varus)) : look_match_id = jhin_ashe_varus['match_id'].iloc[i] # 내가 수집했던, 더 구체적으로 수집할 match_id look_champion = jhin_ashe_varus['champion_name'].iloc[i] # 내가 수집했던, 더 구체적으로 수집할 champion look_position = jhin_ashe_varus['position'].iloc[i] # 내가 수집했던, 더 구체적으로 수집할 position summoner_api = 'https://asia.api.riotgames.com/lol/match/v5/matches/'+match_id+'?api_key='+api_key r = requests.get(summoner_api,headers=request_header) # print(pd.DataFrame(r.json())['info']['queueId']) timestamp=pd.DataFrame(r.json())['info']['gameStartTimestamp'] d = datetime.fromtimestamp(int(timestamp/1000)).strftime("%Y-%m-%d %H:%M:%S") d = datetime.strptime(d,"%Y-%m-%d %H:%M:%S") game_version = pd.DataFrame(r.json())['info']['gameVersion'] puuid=r.json()['metadata']['participants'] summoner_api = 'https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/'+puuid[0]+'?api_key='+api_key r1 = requests.get(summoner_api,headers=request_header) user_id=r1.json()['id'] summoner_api = 'https://kr.api.riotgames.com/lol/league/v4/entries/by-summoner/'+user_id+'?api_key='+api_key r2 = requests.get(summoner_api,headers=request_header) game_tier = r2.json()[0]['tier']+'-'+r2.json()[0]['rank'] for user in pd.DataFrame(r.json())['info']['participants'] : if user['teamPosition'] == look_position : # 10개 챔프 중 내가 수집할 position (상대 포지션 정보도 봐야하니) if user['championName'] == look_champion : # 해당 챔프인 경우 kda 수집 kill = user['kills'] death = user['deaths'] assist = user['assists'] user_champion = user['championName'] user_position = user['teamPosition'] win = user['win'] else : # position은 같은데 해당 챔프는 아닌 경우 (=상대 챔피언인 경우) versus = user['championName'] game_data = pd.concat([game_data,pd.DataFrame([[match_id,game_version,d,game_tier,user_champion,user_position,versus,kill,death,assist,win,datetime.now()]],columns = ['match_id','game_version','game_date','game_tier','champion_name','position','versus','kill','death','assist','win','my_collect_date'])]) collect_cnt+=1 print(match_id, datetime.now()) if collect_cnt == 10000 : print('finish!') break
Python
복사
중간에 코드 잘 짜졌나 확인해보는데,
애쉬 서폿 상대가 샤코에 이즈에 자크….?
맞라이너 (상대팀 서폿) 가 들어가야 되는데 다른 라이너가 들어가고 있는듯한 느낌이 들었다.
심지어 이즈 서폿은 심하잖아 아무리 봐도 이상했다.
아니 이왜진? ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
11일전 (1.29) 실버 1에 서폿 애쉬 5킬 9뎃 15어시 상대 이즈 서폿 맞네…
이런 귀한 경기가 하필 먼저 찍힌겨… (실버에서는 이런 서폿들이 일상인가?)
데이터도 문제가 없다는 걸 확인했고, 데이터 1095건을 다시 수집하는데 약 18분이 걸렸다.
좌표 데이터가 있다는 전제하에, 1시간에 약 3500개를 모을 수 있는 셈이다.
3시간에 10000개면,, 분석 주제만 미리 정해놓으면 충분히 기다릴 수 있는 시간이다.
앞으로 좌표를 계속 수집해보기로 하자.
일단 이왕 데이터를 훑을 때 최대한 많은 정보는 담아두는 것이 좋으니, 상대 챔피언 정보까지만 추가해서 좌표를 수집하기로 했다.
이제 이 코드를 한 번 하루 종일 돌려보고, 정말 하루 종일 좌표 데이터 수집이 가능한지 (api 요청 허용량을 초과하지는 않을지)
그리고 하루 종일 돈다면, 그게 실제 게임이 쌓이는 속도보다 얼마나 차이나는지를 알아보도록 하자.
실제 게임이 쌓이는 속도보다 느린 경우
최대한 하루 종일 쌓아서 이후에 분석에 최대한 써먹는다.
실제 게임이 쌓이는 속도보다 빠른 경우
하루에 얼만큼 돌려야 속도가 맞는지 파악해서 필요한만큼만 코드를 돌리는 적정선을 찾는다 → 이후 분석에 전부 써먹는다

최종 코드 (좌표 데이터 생성)

# api_key = 본인 API KEY 입력 import requests import pandas as pd from pandas.io.json import json_normalize import json import time import numpy as np from datetime import datetime request_header={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36", "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", "Accept-Charset": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "https://developer.riotgames.com", "X-Riot-Token": api_key} game_list = pd.DataFrame(columns = ['match_id','game_version','game_date','game_tier','champion_name','position','win','versus','my_collect_date']) collect_cnt = 0 for i in range (6334298945,6334298975): # 들어갈 숫자는 본인이 판단해서! match_id = 'KR_' + str(i) summoner_api = 'https://asia.api.riotgames.com/lol/match/v5/matches/'+match_id+'?api_key='+api_key r = requests.get(summoner_api,headers=request_header) # print(pd.DataFrame(r.json())['info']['queueId']) try: if pd.DataFrame(r.json())['info']['queueId']==420: timestamp=pd.DataFrame(r.json())['info']['gameStartTimestamp'] d = datetime.fromtimestamp(int(timestamp/1000)).strftime("%Y-%m-%d %H:%M:%S") d = datetime.strptime(d,"%Y-%m-%d %H:%M:%S") game_version = pd.DataFrame(r.json())['info']['gameVersion'] puuid=r.json()['metadata']['participants'] summoner_api = 'https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/'+puuid[0]+'?api_key='+api_key r1 = requests.get(summoner_api,headers=request_header) user_id=r1.json()['id'] summoner_api = 'https://kr.api.riotgames.com/lol/league/v4/entries/by-summoner/'+user_id+'?api_key='+api_key r2 = requests.get(summoner_api,headers=request_header) game_tier = r2.json()[0]['tier']+'-'+r2.json()[0]['rank'] for user in pd.DataFrame(r.json())['info']['participants'] : user_champion = user['championName'] user_position = user['teamPosition'] win = user['win'] for participant in pd.DataFrame(r.json())['info']['participants'] : if participant['teamPosition'] == user_position and participant['championName'] != user_champion : versus_champion = participant['championName'] break game_list = pd.concat([game_list,pd.DataFrame([[match_id,game_version,d,game_tier,user_champion,user_position,versus_champion,win,datetime.now()]],columns = ['match_id','game_version','game_date','game_tier','champion_name','position','versus','win','my_collect_date'])]) collect_cnt+=1 print(match_id, datetime.now()) except: pass if collect_cnt == 10000 : print('finish!') break
Python
복사

좌표 데이터 예시