LOL Timestamp

자훈·2023년 11월 2일
0
post-thumbnail

타임 스탬프 데이터 구조를 분석하고, 데이터 구조가 어떤 형식으로 이루어져있는지 내가 원하는 데이터에 접근하려면 어떻게 해야하는지에 대한 가이드 라인을 잡을 예정이다.

📌 timeline

timeline 에 관한 데이터는 APIS에서 MATCH-V5 태그에 있다. 해당 데이터에 접근하려면, 경기에 대한 고유아이디를 입력해야한다. 고유 아이디는 api접근을 통해서도 찾을 수 있지만, 인게임 내 전적기록에서도 있기 때문에 필요하다면 바로 사용하여도 상관없다. URL 구성은 매치 데이터 뒤에 엔드포인트로 /timeline 만 더해주면 된다.

원래 query param으로 API를 넣었었는데, 보안을 위해 header param에 데이터를 넣고 있다.
요청을 보내게 되면, 아래와 같은 headers 데이터를 받게 되는데, 이 안에 API를 추가해주면 된다.

{
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 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": 'your_api'
}

📌 데이터 구조 알아보기

Introduction

데이터 구조를 뜯어보는 것과 관련한, 블로그를 서칭해봤는데 조금 자세하게 나온 곳이 있긴 하였으나, 만약 본인이 딕셔너리나 리스트를 보는 것이 익숙하지 않은 사람이라면, 혹은 아무것도 찾아보지 않고 matchline 데이터를 한 번 서치해보았다면, 어떤 데이터에 어떻게 접근해야하는 것이지? 에 대한 고민이 있을 것이다. 지금부터 데이터구조가 어떻게 이루어져있는지 하나 하나의 객체들에 접근해보며 파악해나갈 것이다.

Code explannation

async def match_timeline(match_id):
    matchTimeline = f'{matchData_Url}{match_id}/timeline'
    type_list = []
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
        async with session.get(matchTimeline, headers=headers) as response:
            data1 = (await response.json())

이전의 포스팅을 보신 분은 알겠지만, fastapi로 바꾸면서 데이터 크롤링을 하는 방식도 기존의 request를 사용하는 것이 아닌, aiohttp로 바꾸었다. 비동기 방식으로 병렬적 데이터 크롤링이 가능하게 해주는 모듈이므로, 기능에 대한 이해를 하고 싶다면 이전 포스팅을 참조 하길 바란다.

해당 코드를 실행해보면

딕셔너리 키가 이렇게 존재한다고 나와있다. info 데이터 말고도 metadata 큰 딕셔너리 키도 있는데, 이는 게임 내적인 내용과는 관련없는 내용이어서 info로 접근하였다. 이 부분은 찾아보길 바란다. frames 데이터가 중요한 인게임 데이터들을 가지고 있는 데이터기에, 여기에 접근을 해야하는데 그냥 바로 접근하게 되면 에러가 뜬다. 왜냐하면 데이터 구조가 리스트 안의 딕셔너리 형식으로 되어있기 때문이다. 그럼 각각의 프레임 단위 데이터들에는 어떻게 접근을 해야할까??

data2 = (await response.json())['info']['frames'][1].keys()
print(data2)


frames 키에 접근해보면, frames라는 자료구조 안에 있는 키 값들이 나오는데, 주의할 점이 있다.

data2 = (await response.json())['info']['frames'].keys()
            print(data2)

participants의 딕셔너리 키값에 저장되어 있는 1에서 10번까지의 번호들의 의미는
1~5: 블루팀 아이디
6~10: 레드팀 아이디
puuid랑은 다른 개념이며, 아직 구체적으로 어떤 의미인지는 분석하지 않았으나 한다면 수정으로 업로드 하겠다.

위의 형식으로 바로 접근하게 되면 키값을 반환하지 않고 에러가 나게 된다.
(에러는 꼭 직접 마주해보길 바란다.)
그 이유는 프레임이라는 데이터는 리스트 안에 딕셔너리 데이터를 품고있는 형식이기 때문이다. 무슨소린지 모르겠다면 아래의 데이터 구조를 보자.

Data Structure

각 데이터들이 정의 된 자료형을 입력하여 조금 시각화 하였고, 기본적으로 사용할 데이터는 events에 정의된 데이터를 사용할 예정이다. 해당 키값들은 모든 events 에 대해 반복문을 사용하여, 수집하고 set을 이용해 중복을 제거한 리스트 변수를 만들어 알아냈다. 아래가 그 코드이다.

test = (await response.json())['info']['frames']
            for i in range(len(test)):
                for j in range(len(test[i]['events'])):
                    type_list.append(test[i]['events'][j]['type'])
            data_list = set(type_list) # 셋을 사용하려면, 다른 객체에 저장해서 사용해라.
            print(data_list)

이렇게 하면, events에 어떤 유형의 데이터들이 정의되어있는지 확인할 수 있고, 각 데이터 마다 timestamp, x y position 등 다양한 자료들이 있기 떄문에 분석에 굉장히 용이하다. 이를 활용하여 상황복잡도 모델링을 할 예정이다.

events type list

  • Building_kill : 포탑(타워),억제기, 넥서스 파괴
  • Champion_kill : 상대 챔피언 처치
  • Champion_special_kill
  • Elite_monster_kill : 엘리트 몬스터(전령, 드래곤, 바론) 처치
  • Item_destroyed : 아이템 파괴
  • Item_purchase : 아이템 구매
  • Item_sold : 아이템 판매
  • Item_undo : 중요하지 않은 정보
  • Skill_level_up : 스킬 레벨업
  • Ward_kill : 와드 파괴
  • Ward_placed : 와드 설치
  • Turret_plate_destroyed : 포탑 방패 파괴
  • objective_bounty_prestart : 오브젝트 현상금 시작 (불리함지표)
  • objective_bounty_finish : 오브젝트 현상금 끝
  • dragon_soul_given : 드래곤 영혼 휙득

events에는 type 말고도 여러가지 딕셔너리 키들이 존재하지만, 접근하기 용이한 방법이 type이여서 따로 저장해서 분류하였다. 해당 event에 대한 assist, timestamp에 대한 추가적인 정보들도 다 들어있다.

📌 전체 코드

async def match_timeline(match_id):
    matchTimeline = f'{matchData_Url}{match_id}/timeline'
    type_list = []
    async with ㅍaiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
        async with session.get(matchTimeline, headers=headers) as response:
            data1 = (await response.json())['info'].keys()
            print(data1)
            data2 = (await response.json())['info']['frames'][1].keys()
            print(data2)
            test = (await response.json())['info']['frames']
            for i in range(len(test)):
                for j in range(len(test[i]['events'])):
                    type_list.append(test[i]['events'][j]['type'])
            data_list = set(type_list) # 셋을 사용하려면, 다른 객체에 저장해서 사용해라.
            print(data_list)

            Start_timestamp = (await response.json())['info']['frames'][0]['events']
            print(Start_timestamp)

            frame1 = (await response.json())['info']['frames'][1]['events']
            print(frame1)

            min1_timestamp = (await response.json())['info']['frames'][1]['timestamp']
            print(min1_timestamp)

            participantData = (await response.json())['info']['frames'][0]['participantFrames'].keys()
            print(participantData)

            data5 = (await response.json())['info']['frames'][0]['participantFrames']['1']
            print(data5)


            # 참여자 고유아이디에 대한 정보
            # particiapants_data = (await response.json())['info']['participants'][0].keys()
           

코드를 직접 찍어내보며 어떤 데이터들이 나오는지 확인해보길 바란다. 데이터를 나누는 것에 익숙하고, 데이터를 구성하는 것에 익숙해져야 체계적인 정리가 가능할 것이다.

📌 그래프 시각화

async def match_timeline(match_id):
    matchTimeline = f'{matchData_Url}{match_id}/timeline'
    timestamps = []
    kills = []
    total_kills = 0

    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
        async with session.get(matchTimeline, headers=headers) as response:
            data = (await response.json())['info']['frames']

            df = pd.DataFrame(columns=['timestamp_minute', 'kills'])

            interval = 1  # 1분 간격
            current_interval = 0
            kills_in_interval = 0
            for frame in data:
                for event in frame['events']:
                    if event['type'] == 'CHAMPION_KILL':
                        timestamp_minute = event['timestamp'] // 60000
                        if timestamp_minute >= current_interval + interval:
                            timestamps.append(current_interval)
                            kills.append(kills_in_interval)
                            current_interval = timestamp_minute - (timestamp_minute % interval)
                            kills_in_interval = 1
                            total_kills += 1
                        else:
                            kills_in_interval += 1
                            total_kills += 1
  print(total_kills)
  plt.plot(timestamps, kills)
  plt.xlabel("Time (minutes)")
  plt.ylabel("Kills")
  plt.title("Kills Over Time (1minute-intervals)")
  plt.grid(True)
  plt.show()
  return None

그래프를 시각화 해보았다. 킬 데이터와 타임스탬프를 이용하였다.

이제 어떤 데이터를 변수로 놓고 시각화 할지 정하기만하면 된다.

0개의 댓글