본문 바로가기

게임/리그오브레전드 - Riot API

리그오브레전드 게임 데이터 수집기 만들기 [4-2화: 데이터 수집기_Riot-API 사용 함수들]

 

 #롤 기본 정보

 주석 첫 줄에 적혀있듯 챔피언, 시즌, 큐타입 등 롤에 대한 기본적인 정보를 가져오는 부분입니다. 내부에 보면 keytochamp라는 변수가 있습니다. API를 쓰다보면 챔피언이 챔피언 이름이 아닌 번호로 나오는 경우가 많습니다({champin: 3} 이런식으로요). 그런 경우를 대비해 번호를 챔피언 이름으로 바꿔줄 때 쓰기위한 변수입니다.

 

 다른 것들은 솔직히 어디에 쓰는지 잘 기억이 안납니다. 괜히 지웠다 어디 오류날까봐 수정 못하고 있습니다 ㅠㅠ

 

 참고로 whole_champions = requests.get( ... )  부분에 url 들어가 있는 부분에 보면 11.3.1이라고 되어있는데 그거 롤 버전입니다. 그래서 최소 신챔 나왔을 때는 저 부분을 최신 버전에 맞게 수정해줘야합니다. 안그러면 게임 기록에는 신챔 번호가 있는데 keytochamp에는 신챔이 없어서 오류가 발생할 수 있습니다.

#챔피언, 시즌, 큐타입 등 롤에대한 기본적인 정보를 가져옵니다.
#keytochamp={챔피언key:챔피언이름}
def phase1():
	print('롤 기본 데이터 수집')
	#챔피언 이상한 정보도 들어있는 드래곤 딕딕딕구조 {키:{},키:{}}
	#dict_keys(['type', 'format', 'version', 'data'])
	global keytochamp
	keytochamp={}
	#버전바뀔때마다 교체해줘야합니다. 적어도 신챔 나올때는 교체해줘야됩니다.
	#http://ddragon.leagueoflegends.com/cdn/10.2.1/data/ko_KR/champion.json -> http://ddragon.leagueoflegends.com/cdn/11.3.1/data/ko_KR/champion.json
	whole_champions=requests.get("http://ddragon.leagueoflegends.com/cdn/11.3.1/data/ko_KR/champion.json").json()
	for champ in list(whole_champions['data'].keys()):
		keytochamp[whole_champions['data'][champ]['key']]=whole_champions['data'][champ]['name']

	#시즌정보 / [{}]
	season_data = requests.get("http://static.developer.riotgames.com/docs/lol/seasons.json").json()

	#큐타입정보[{}]
	#{'queueId': 420, 'map': "Summoner's Rift", 'description': '5v5 Ranked Solo games'}
	queqe_data = requests.get("http://static.developer.riotgames.com/docs/lol/queues.json").json()

	###################여기까지기본정보받아오는곳

 #소환사명을 인자로 받고 해당 계정의 accountId를 리턴합니다.

 

 계정의 아이디를 accountId로 바꾸는 부분입니다. 여기서 말고는 API 사용에 있어 닉네임은 거의 사용이 되지 않습니다.

거의 모든 게 accountId를 요구합니다.



#계정의 게임아이디를 accountId로 바꿉니다
#가령 우주짱짱맨 -> ABCD-1234-ASDF 이런식입니다
#accountId가 있어야 해당 계정의 게임 내역을 가져올 수 있습니다.
def summonerName_to_accountId(summonerName):
	#print(summonerName,'의 accountId 요청')
	global api_key
	url = 'https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-name/'+summonerName+'?api_key='+api_key
	base_data = req_api(url)
	return base_data['accountId']

 


 #계정의 accountId를 인자로 받고 해당 계정의 가장 최근 게임의 gameId를 리턴합니다.

 

 각각의 판마다 고유한 gameId를 가지고 있습니다. 이걸 가지고 누가 이겼는지, 챔피언은 뭐가 등장했는지 등을 알 수 있습니다. 각각의 판에 부여되는 ID라 이걸 가지고 중복자료를 검토할 수도 있습니다. 여러모로 중요한 부분입니다.

#계정의 게임 기록들을 가져옵니다.
def from_accountId_get_gameid(accountId):
	#print('gameid 가져오는중')
	#url = 'https://kr.api.riotgames.com/lol/match/v4/matchlists/by-account/'+accountId+'?queue=420&endIndex='+str(endindex)+'&beginIndex='+str(beginindex)+'&api_key='+api_key
	url ='https://kr.api.riotgames.com/lol/match/v4/matchlists/by-account/'+accountId+'?queue=420&api_key='+api_key

	#gameids.json이 이미 있다면 가져오고 없다면 생성하여 가져옵니다. 중복자료 처리에 사용됩니다.
	try:
		gameids=load_json('gameids')
	except:
		write_json('gameids',[])
		gameids = load_json('gameids')


	data = req_api(url)
	if data!=0 and data!=-1:
		#계정의 게임 기록들은 data라는 dict의 'matches'라는 key로 접근합니다
		for i in range(len(data['matches'])):
			gameid = data['matches'][i]['gameId']
			#5대5 게임인점, 유저를 먼저 모으고 각 유저의 게임기록을 살펴보는점 때문에 이미 조사한 게임이 중복되서 들어오는 경우가 생깁니다.
			#gameids.json은 그런 문제를 해결합니다. 이미 gameids에 있는 gameid라면 continue합니다.
			if gameid in gameids:
				continue
			else:
				#중복자료가 아니고 이번 버전에서 행해진 게임이라면 gameids에 기록하여 다음 중복자료 탐색에 사용하고  str타입으로 gameid를 리턴합니다.
				#gameid는 여러개 받아와도 하나만 사용하는 이유는 최대한 다양한 유저의 자료를 사용해야 치우침이 없을 것 같았습니다.
				gameids.append(gameid)
				write_json('gameids',gameids)
				#매 패치마다 수정해야하는 부분입니다. 에포크밀리초로 지난 패치시점의 게임기록은 기록하지 않습니다.
				#https://www.epochconverter.com/
				if data['matches'][i]['timestamp']<1612342840000:
					print("얘꺼 이번패치 기록 끝")
					return 3
				return str(gameid)
		else:
			print("얘꺼다봄######################################################")
			return 0
	else:
		return 0

 


 #gameids를 인자로 받아 해당 gameid를 가진 판에서 누가 원딜이었고 누가 서폿이었는지 반환합니다.
-아래 get_bottom_final과 get_bottom_full과 세트입니다.
-get_bottom_full은 보기 편하려고 만들었습니다.

 

-라인전이 이뤄지는 15분 정도까지의 각 플레이-어의 시간대별 위치 좌표를 받고
-바텀에 있으면 bot_point를 1올려 bot_point가 가장 높은 사람 둘이 바텀 라이너들이라고 정하는 방식입니다
-원딜과 서폿의 구분은 씨에스의 양으로 구분했습니다.

파란네모와 검정네모에 위치했다면 블루팀 바텀 라이너일 확률이 높겠지용 ㅎㅎ?

-바텀 뿐만 아니라 탑, 미드, 정글 등도 같은 원리로 구분했습니다.(get_mid_full(), get_top_full())
-미드와 정글 구분이 힘들었는데 15분까지 정글 몬스터를 가장 많이 잡은 유저를 정글러로 분류했습니다.

def get_bottom_base(gameid):
	#print('phase5')
	global api_key
	#일단 데이터 요청
	url = 'https://kr.api.riotgames.com/lol/match/v4/timelines/by-match/'+gameid+'?api_key='+api_key
	#position = req_api(url)
	#position['frames']에 시간별 위치정보 들어있음
	frames=position['frames']
	dongsun = {}
	#participantid는 게임내 참여자 참가번호같은거
	#dongsun['participantid'] ={1분:x,y, 2분:x,y} 이런식으로 저장
	for frame in frames:
		#print(frame['timestamp'])
		partiframe = frame['participantFrames']
		for pf in list(partiframe.keys()):
			participantid = str(partiframe[pf]['participantId'])
			if participantid not in dongsun.keys():
				dongsun[participantid]=[]
			if 'position' not in partiframe[pf].keys():
				continue
			#print(pf,partiframe[pf]['position'])
			dongsun[participantid].append(partiframe[pf]['position'])

	
	#여기부터 ptl.show()까지 시각화
	#for i in range(1,11):
	#	i=str(i)
	#	for j in dongsun[i]:
	#		if i=='3':
	#			plt.scatter(j['x'],j['y'],c='b')
	#		elif i=='10':
	#			plt.scatter(j['x'],j['y'],c='b')
	#		else:
	#			plt.scatter(j['x'],j['y'],c='b')
	#
	#plt.show()
	
	
	#member에 플레이어들 저장할껀데 어떻게 저장할꺼냐면 [바텀점수,바텀점수-딴라인점수,participantid] 이렇게
	member = []
	#i는 participantid
	for i in range(1,11):
		i=str(i)
		#i번 플레이어가 바텀 라인에 있었을경우 계수하기위한 변수 botpoint
		botpoint=0
		#botpoint의 반대
		elsepoint=0
		#몇분시점인지 count로 체크
		count=0
		#dongsun[i][j]는 j시점 i번 플레이어의 위치
		for j in dongsun[i]:
			count+=1
			#15분까지의 위치좌표만 보도록 하겠습니다. 이후에는 라인전이 끝나기때문입니다.
			if count>12:
				break
			#바텀좌표에 있었으면 botpoint에 +1
			if (j['x']>10000 and j['y']<5000) or (j['x']<10000 and j['y']<2000) or (j['x']>13000 and j['y']>0):
				botpoint+=1
			else:
				elsepoint+=1
		#이렇게 나온 botpoint,botpoint-elsepoint,i를 member에 저장
		member.append([botpoint,botpoint-elsepoint,i])
	#member를 botpoint 기준으로 오름차순 정렬
	member.sort()
	#결과에 get_bot()함수의 인자인 gameid 넣어두고
	result = [gameid]
	#botpoint 상위 네명의 participantid를 result에 넣는다
	#pop()이 리스트의 마지막 요소를 꺼내주는 메소드고 [-1]은 아까 member 양식인[botpoint,botpoint-elsepoint,i]에서 i 즉 participantid
	#결론적으로 gameid와 botpoint상위 네명의 participantid가 result에 들어가게된다
	for i in range(4):
		result.append(member.pop()[-1])
	if len(result)!=5:
		return 0
	check = result[1:]
	frame = frames[-1]
	participantFrames = frame['participantFrames']
	know_cs = {}
	for pf in participantFrames.keys():
		for pid in check:
			if str(participantFrames[pf]['participantId'])==pid:
				#봇과 봇씨에스가 맞게 나오나 보려면 해제
				#print(pid,participantFrames[pf]['minionsKilled']+participantFrames[pf]['jungleMinionsKilled'])
				know_cs[pid]=participantFrames[pf]['minionsKilled']+participantFrames[pf]['jungleMinionsKilled']

	return result,know_cs


def get_botttom_final(bot):
	#print('phase6')
	#gameid는 gameid에
	gameid = bot[0][0]
	#여기부터 서폿둘 원딜둘 따로나누는부분
	botcombi=bot[1]
	bot = []
	for i in botcombi.keys():
		bot.append([botcombi[i],i])
	bot.sort()
	supports = bot[:2]
	adcs = bot[2:]
	#여기까지 서폿둘 원딜둘 따로나눔, 이상자료면 원딜 여러명될수도있음

	#url = 'https://kr.api.riotgames.com/lol/match/v4/matches/'+gameid+'?api_key='+api_key
	#data = req_api(url)
	data = ffdata

	teama =data['teams'][0]
	teamb =data['teams'][1]
	
	###승리팀구분, 한쪽만 이기지않으면 오류
	#승리팀들어갈곳
	winteam = 0
	if (teama['win']=='Fail' and teamb['win']=='Win') or (teama['win']=='Win' and teamb['win']=='Fail'):
		if teama['win']=='Win':
			winteam=teama['teamId']
		else:
			winteam=teamb['teamId']

	else:
		return 0
	###
	adc_result =[]
	for adc in adcs:
		pid = adc[1]
		for pt in data['participants']:
			if str(pt['participantId']) ==pid:
				adc_result.append([pt['teamId'],keytochamp[str(pt['championId'])]])
	spt_result =[]
	for spt in supports:
		pid = spt[1]
		for pt in data['participants']:
			if str(pt['participantId']) ==pid:
				spt_result.append([pt['teamId'],keytochamp[str(pt['championId'])]])
	lastdata = []
	for i in adc_result:
		if i[0]==100:
			lastdata.append(i)
	for i in spt_result:
		if i[0]==100:
			lastdata.append(i)

	for i in adc_result:
		if i[0]==200:
			lastdata.append(i)
	for i in spt_result:
		if i[0]==200:
			lastdata.append(i)
	lastdata.append(winteam)
	t1=0
	t2=0
	for i in lastdata[:-1]:
		if i[0]==100:
			t1+=1
		elif i[0]==200:
			t2+=1
		else:
			print('이상 팀 정보')
			return 0
	if t1!=2 or t2!=2:
		print('이상 팀 정보')
		return 0
	if lastdata[-1]!=100 and lastdata[-1]!=200:
		print('이상 팀 정보')
		return 0


	'''
	###여기부터
	try:
		new_before = load_json('new_before')
	except:
		write_json('new_before',[])
		new_before = load_json('new_before')
	new_before.append(lastdata)
	write_json('new_before',new_before)
	##여기까지 삭제예정
	'''
	return lastdata

def get_bottom_full(gameid):
	#print('바텀 정보 분석중')
	return get_botttom_final(get_bottom_base(gameid))

 다른 라이너에 대한 코드는 원리가 같으니 여기에는 올리지 않겠습니다(맨 뒤 코드 전문 올리는 글에는 있습니다).

  •  get_mid_full() <<미드 정글,
  •  get_top_full()<<탑

두 함수가 다른 라이너에 대한 코드들입니다.


#인자: 원하는 티어의 정보, 리턴: 해당 티어 유저 4000명의 닉네임(소환사명)

 

 게임을 가져오려면 유저가 필요하겠죠? 한 4000명 정도면 훌륭한 표본이라고 생각해 4000명의 아이디를 수집하는 함수를 만들었습니다.

 

 get_4000(metal) 함수의 인자에 원하는 티어금속("BRONZE", "SILVER", "GOLD", ...)을 넣으면 get_user()함수가 해당 티어 1,2,3,4에 속하는 유저를 각각 1000명씩 모읍니다. 

 

 가령, get_4000("GOLD")를 실행하면,

  • 골드1 유저 1000명
  • 골드2 유저 1000명
  • 골드3 유저 1000명
  • 골드4 유저 1000명

을 모읍니다. 완성된 4000명의 유저 리스트는 순서가 랜덤하게 섞여서 리턴됩니다.

#아래 함수 get_4000과 세트입니다. 분석대상인 자료를 수집하는 부분입니다. 
#조사를 원하는 metal의 tier1/2/3/4를 1000명씩 수집해 4000명을 만들었습니다.
def get_user(metal,tier,personnel):
	global api_key
	page=1
	player_list = []
	while True:
		url = 'https://kr.api.riotgames.com/lol/league/v4/entries/RANKED_SOLO_5x5/'+metal+'/'+tier+'?page='+str(page)+'&api_key='+api_key
		base_data  = req_api(url)
		if len(base_data)==0:
			print(metal+tier+'유저가 부족하여 목표치인'+str(personnel)+'보다 적은'+str(len(player_list))+'만 수집 후 리턴')
			return player_list
		for i in range(len(base_data)):
			player_list.append(base_data[i]['summonerName'])
			if len(player_list)==personnel:
				return player_list
		page+=1

def get_4000(metal):
	IV_1000 = get_user(metal,"IV",1000)
	III_1000 = get_user(metal,"III",1000)
	II_1000 = get_user(metal,"II",1000)
	I_1000 = get_user(metal,"I",1000)
	whole = IV_1000+III_1000+II_1000+I_1000
	random_whole = whole[:]
	random.shuffle(random_whole)
	return random_whole

 


 

다했네요~

이번 글에서는 롤 분석기(수집기)에서 Riot API를 사용하는 함수들에 대해 말씀드려봤습니다.

다음 글에는 이 함수들이 어떻게 맞물려 돌아가는지 말씀드리도록 하겠습니다.