티스토리 뷰
안녕하세요~
오랜만에 인사드리겠습니다.
어떤 글을 써야 할지 몰라 방황하고 있었습니다....
최근에 넥슨에서 게임 데이터 API를 제공해 준다는 소식을 듣고 슬쩍 가서 보니까 메이플스토리 API가 존재하더라고요~
어릴 때 메이플스토리를 생각하고 봤지만, 하이퍼 스탯, 어빌리티, 장착심볼 등등.... 제가 알던 메이플은 이제 없는 거 같아요...😥😥
그래도 제 눈에 띄었던 건 무릉도장 랭킹 API였습니다.
무릉도장 랭킹에 등재된 캐릭터 능력치를 예측 모델에 학습해 도착 층수를 예측할 수 있지 않을까 싶어 무릉도장랭킹 API를 기반으로 필요한 데이터들을 추출해 보겠습니다.
그리고 API 활용에 조금이나마 도움이 될 수 있게 코드도 공유해 보았습니다~
제가 생각한 프로세스는 아래와 같습니다.
- 무릉도장에 랭킹 정보를 호출해 수집한다.
- 추출된 무릉도장 데이터에서 캐릭터 식별자(OCID)를 추출
- OCID를 기반으로 능력치 및 스킬 등 데이터를 추출
이번 포스팅은 1번 2번을 프로세스 코드 설명으로 마무리하겠습니다.
참고로 캐릭터 식별자(OCID)는 캐릭터 고유 아이디인데 캐릭터 정보 대다수는 OCID를 통해 추출한다고 보시면 되고, 주의할 점으로는 특정 캐릭터들의 OCID는 존재하지 않아 OCID가 추출이 안 될 수도 있습니다.
이제 코드를 보겠습니다!
코드는 파이선 기반이고 API, 메이플 직업, 조회일, 난이도만 입력하면 값이 도출될 수 있게 코드를 짰습니다.
먼저 전체 코드를 보여드리고 하나하나 설명하겠습니다.
import warnings
warnings.filterwarnings(action='ignore')
import requests
import pandas as pd
from tqdm import tqdm
import time
from urllib.parse import quote
#API 자동교체 함수
def find_word_index(api, search_word):
if api.index(search_word) == len(api) -1 : #API 리스트가 끝까지 도달 하였을 경우 처음으로 복귀
api_2 = api[0]
print(f'{api_2} 변경')
else :
index = api.index(search_word) # 다음 API로 변환
api_2 = api[index + 1]
print(f'{api_2} 변경')
return api_2
#도장 랭킹 추출
def dojang_rank_ocid(api,collection_date,difficulty,maple_class) :
maple_class_2 = quote(maple_class)# 퍼센트인코딩으로 변환
input_df = pd.DataFrame()
result = None
result = api[0] if not result else find_word_index(api, search_word)
search_word = result
outer_loop = True
stop_count = 0
print('랭킹 추출 시작')
for day in tqdm(collection_date) :
sub_input_df = pd.DataFrame()
for page in range(1,20) :
headers = {"x-nxopen-api-key": result}
urlString = f"https://open.api.nexon.com/maplestory/v1/ranking/dojang?date={day}&difficulty={difficulty}&class={maple_class_2}&page={page}"
response = requests.get(urlString, headers = headers)
time.sleep(0.25)
if response.status_code == 429:
while True :
stop_count += 1
result = api[0] if not result else find_word_index(api, search_word)
search_word = result
headers = {"x-nxopen-api-key": result}
time.sleep(0.25)
response = requests.get(urlString, headers = headers)
if response.status_code == 200 or response.status_code == 400 :
stop_count = 0
break
if stop_count == (len(api) +1) *2 :
outer_loop= False
break
if not outer_loop:
break
if response.status_code == 200:
if len(response.json().get('ranking')) != 0 :
new_ranking = pd.DataFrame(response.json().get('ranking'))
else :
break
elif response.status_code == 400:
new_ranking = pd.DataFrame()
sub_input_df= pd.concat([sub_input_df, new_ranking], ignore_index=True)
if not outer_loop:
print('랭킹 종료!')
break
input_df= pd.concat([input_df, sub_input_df], ignore_index=True)
print('랭킹 추출 종료')
# 캐릭터명 퍼센트 인코딩
print('캐릭터명 퍼센트 인코딩 시작')
ocid_tmp = []
for i in tqdm(input_df['character_name']) :
ocid_tmp.append(quote(i))
input_df['encoding_character_name'] = ocid_tmp
print('캐릭터명 퍼센트 인코딩 종료')
result = None
result = api[0] if not result else find_word_index(api, search_word)
search_word = result
ocid_tmp = []
print('ocid 추출 시작')
for i in tqdm(input_df['encoding_character_name']) :
headers = {"x-nxopen-api-key": result}
urlString = f'https://open.api.nexon.com/maplestory/v1/id?character_name={i}'
time.sleep(0.25)
response = requests.get(urlString, headers = headers)
if response.status_code == 429:
while True :
stop_count += 1
result = api[0] if not result else find_word_index(api, search_word)
search_word = result
headers = {"x-nxopen-api-key": result}
time.sleep(0.25)
response = requests.get(urlString, headers = headers)
if response.status_code == 200 or response.status_code == 400 :
stop_count = 0
break
if stop_count == (len(api) +1) *1.4 :
outer_loop= False
break
if not outer_loop:
break
if not outer_loop:
print('ocid 종료!')
break
if response.status_code == 200:
ocid_tmp.append(response.json().get('ocid'))
elif response.status_code == 400:
ocid_tmp.append(None)
input_df.loc[:len(ocid_tmp)-1,'ocid']= ocid_tmp
print('ocid 추출 종료')
return input_df
api = [] # 제공받은 API를 '제공받은 API 값' 형식으로 1개 혹은 2개 이상 입력 ex ['api_1', 'api_2',.....]
collection_date = []# 수집하고 싶은 날짜를 1개 혹은 2개 이상을 'YYYY-mm-dd' 형식으로 기입 ex '2023-01-02'
rank_df = dojang_rank_ocid(api,
collection_date,
1,#메이플에서 난이도로 0(일반),1(통달)로 설정
'메이플 직업')#메이플 Open api에서 제시한 직업명 그대로 작성
제가 구현한 무릉도장 랭킹은 아래와 같은 특징을 가지고 있습니다.
- 사용자가 입력한 날짜에 맞게 무릉도장 추출이 가능
- 전체 월드(서버) 각 직업의 무릉도장 랭킹을 추출
2번으로 프로세스를 구축한 이유는 직접 호출해 본 결과 전체 월드로 한 직업을 호출해도 모든 데이터를 수집할 수 있었기 때문에 월드별로 호출 시 API 호출 낭비라고 생각했습니다.
먼저 입력 부분부터 보겠습니다.
api = [] # 제공받은 API를 '제공받은 API 값' 형식으로 1개 혹은 2개 이상 입력 ex ['api_1', 'api_2',.....]
collection_date = []# 수집하고 싶은 날짜를 1개 혹은 2개 이상을 'YYYY-mm-dd' 형식으로 기입 ex '2023-01-02'
rank_df = dojang_rank_ocid(api,
collection_date,
1,#메이플에서 난이도로 0(일반),1(통달)로 설정
'메이플 직업')#메이플 Open api에서 제시한 직업명 그대로 작성
API는 한 아이디로 게임당 최대 3개 발급할 수 있습니다.
그리고 개발 애플리케이션 타입(개발 단계, 서비스 단계)에 따라 호출량, 초당 호출 속도가 달라집니다.
저는 개발 단계로 3개의 메이플 API를 신청했기에 0.2초당 한건의 속도로 하루에 3,000건을 호출할 수 있습니다.
api 변수는 사용자가 api 신청해서 받은 값을 리스트 형식으로 입력할 수 있고, date 변수는 날짜를 입력하는 곳으로 '2022-12-01'과 같이 리스트 형식으로 입력할 수 있습니다.
그리고 dojang_rank_ocid함수에는 api, date, 1, '메이플 직업'으로 구성되어 있습니다.
1로 되어 있는 부분은 0도 들어갈 수 있으며, 0과 1은 무릉도장 입장레벨을 의미합니다.
일반(0)은 레벨 105 ~ 200, 통달(1)은 201 이상으로 구분되어 레벨에 맞게 무릉도장에 입장이 된다고 합니다.
즉 코드 사용자는 일반 무릉도장의 랭킹을 원하면 0, 통달 무릉도장 랭킹을 원하면 1을 입력하시면 됩니다.
그리고 '메이플 직업' 부분은 메이플 내에 존재하는 직업을 의미합니다.
직업 입력 시 아래 제시해 준 표에 값을 똑같이 입력해야 합니다. (안 그러면 추출이 안 돼요....)
초보자-전체 전직 | 전사-전체 전직 | 전사-검사 | 전사-파이터 |
전사-페이지 | 전사-스피어맨 | 전사-크루세이더 | 전사-나이트 |
전사-버서커 | 전사-히어로 | 전사-팔라딘 | 전사-다크나이트 |
마법사-전체 전직 | 마법사-매지션 | 마법사-위자드(불독) | 마법사-위자드(썬콜) |
마법사-클레릭 | 마법사-메이지(불독) | 마법사-메이지(썬콜) | 마법사-프리스트 |
마법사-아크메이지(불독) | 마법사-아크메이지(썬콜) | 마법사-비숍 | 궁수-전체 전직 |
궁수-아처 | 궁수-헌터 | 궁수-사수 | 궁수-레인저 |
궁수-저격수 | 궁수-보우마스터 | 궁수-신궁 | 궁수-아처(패스파인더) |
궁수-에인션트아처 | 궁수-체이서 | 궁수-패스파인더 | 도적-전체 전직 |
도적-로그 | 도적-어쌔신 | 도적-시프 | 도적-허밋 |
도적-시프마스터 |
도적-나이트로드 |
도적-섀도어 |
도적-세미듀어 |
도적-듀어러 |
도적-듀얼마스터 |
도적-슬래셔 |
도적-듀얼블레이더 |
해적-전체 전직 |
해적-해적 |
해적-인파이터 |
해적-건슬링거 |
해적-캐논슈터 |
해적-버커니어 |
해적-발키리 |
해적-캐논블래스터 |
해적-바이퍼 |
해적-캡틴 |
해적-캐논마스터 |
기사단-전체 전직 |
기사단-노블레스 |
기사단-소울마스터 |
기사단-플레임위자드 |
기사단-윈드브레이커 |
기사단-나이트워커 |
기사단-스트라이커 |
기사단-미하일 |
아란-전체 전직 |
에반-전체 전직 |
레지스탕스-전체 전직 |
레지스탕스-시티즌 |
레지스탕스-배틀메이지 |
레지스탕스-와일드헌터 |
레지스탕스-메카닉 |
레지스탕스-데몬슬레이어 |
레지스탕스-데몬어벤져 |
레지스탕스-제논 |
레지스탕스-블래스터 |
메르세데스-전체 전직 |
팬텀-전체 전직 |
루미너스-전체 전직 |
카이저-전체 전직 |
엔젤릭버스터-전체 전직 |
초월자-전체 전직 |
초월자-제로 |
은월-전체 전직 |
프렌즈 월드-전체 전직 |
프렌즈 월드-키네시스 |
카데나-전체 전직 |
일리움-전체 전직 |
아크-전체 전직 |
호영-전체 전직 |
아델-전체 전직 |
카인-전체 전직 |
라라-전체 전직 |
칼리-전체 전직 |
함수를 자세히 설명하면 많이 길어질 거 같아 프로세스만 설명하고 넘어가겠습니다. (절대 귀찮아서가 아닙니다...😅😅)
참고로 저는 개발단계에서 관점으로 코드를 구축하였기에 개발단계에서만 일어나는 현상일 수 있습니다.
NEXON Open API에서 제시해 준 초당 최대 허용량에 근접하게 호출을 진행하면 호출을 진행하는 도중 API호출량 초과 (응답코드 429)라는 에러가 나옵니다.(0.25초 간격으로 호출하면 약 80건 정도 진행 후 응답코드 429가 나왔습니다.)
할당받은 API 호출을 다 쓰기 전에 종료가 되는 거죠.
자세한 에러코드 리스트는 아래 사진을 참고해 주세요.
건당 0.8초 정도 간격으로 호출을 하면 끊김이 없이 일 최대 허용량을 호출할 수 있었던 거 같은데, 저는 빨리빨리를 선호해서 0.25초 간격으로 호출할 수 있는 코드로 만들었습니다.
호출 프로세스는 아래와 같습니다.
- api 리스트에 할당한 첫 번째 변수로 API 호출 진행(건당 0.25초)
- API 호출량 초과(응답코드 429) 에러 발생 시 api리스트에서 다음 변수로 진행(계속 반복)
- api리스트 맨 마지막에서 에러발생 시 1번을 다시 진행
- api리스트에 존재하는 변수 *2 만큼 연속적으로 응답코드 429가 발생하면 할당받은 api 호출량을 다 썼다고 판단하여 종료
이상으로 이번 블로그를 마무리 하겠습니다.
블로그 내용 중 궁금하거나 혹은 코드 실행이 안되면 오류 사진과 같이 쪽지 혹은 댓글 주세요~
코드를 수정 혹은 오류 원인을 찾아 답변드리겠습니다.
다음엔 종합능력치 추출로 인사드리겠습니다.
감사합니다~
'데이터수집하러왔어요' 카테고리의 다른 글
환율데이터 크롤링하고 갈래?(Investing,requests, BeautifulSoup) (0) | 2023.05.02 |
---|
- Total
- Today
- Yesterday
- BeautifulSoup
- 메이플
- 환율
- 음식 배달
- Investing
- 비즈니스분석
- 비즈니스 분석
- 데이터
- 메이플API
- Requests
- 비즈니스 데이터분석
- 음식배달
- NEXONAPI
- Crawling
- maplestory
- 크롤링
- 데이터분석
- 라스트마일분석
- 음식
- 라스트마일 분석
- OpenAPI
- 라스트마일
- 코호트분석
- Lastmile
- 무릉도장
- 배달
- 포지션매트릭스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |