이번주가 시험기간이었다... 생각보다 준비를 잘했다고 생각했는데, 너무 안일한 것도 있었던 것 같다. 전공시험들은 대부분 다 잘봤는데, 객체지향프로그래밍의 경우 시험을 치면서, 내가 알고 있는 부분들이 너무 얕은 부분이라는 사실을 깨닫게 되었다. 모르는 사실들도 아니고, 수업시간에 보긴 했으나, 백엔드를 구성하는데 있어 그리 중요한 것이 아니라 생각하거나 혹은 이미 아는 내용이라 생각하여 가벼이 넘겼던 개념들이 나왔다. 아직도 갈길은 멀다고 느꼈지만, 알지 못해 풀지 못한 문제기에 기말고사때는 더 열심히 꼼꼼하게 오류가 발생하는 부분이나, 실수에 관한 것들을 준비하여 시험을 더 잘 칠 수 있도록 노력해야겠다.
기능을 공부하고 업로드하는데 다소 시간이 좀 걸리긴 했는데, 그래도 매주 크롤링 관련 내용을 공부하고 업로드 하는 취지를 가지고 있는 블로그이기 때문에, 내용이 다소 빈약하고 사소한 기능일지라도 찾아서 공부하고자 했다. 그래서 결국 찾은게 aiohttp
라는 것이었고(구글링하다가 굉장히 우연치않게 찾게됨) 이를 통해, 이전에 비해 데이터를 무려 4배빨리 가져올 수 있게 되었다. 처음에는 flask
와의 데이터 크롤링 속도차이가 얼마 차이가 나지않아, 내가 fastapi
를 잘 쓰고 있는지에 대한 의문이 들었지만 지금은 당당하게 말할 수 있다.
aiohttp 란 asyncio 라이브러리를 기반으로 하며,비동기 코드를 작성하여 네트워크 연결, 웹 서버 및 클라이언트와의 상호작용을 효율적으로 다룰 수 있게 해준다.
여러 작업을 동시에 처리하도록 만들어주고, I/O 바운드 작업의 성능을 향상시킬 수 있다. 동시성을 향상시킨다는 것.
데이터 크롤링이나 스크랩 면에서도 굉장히 효율적이고, 빠른 속도를 보여준다.
단순히 request를 사용하는 것 보다 빠른 속도를 보여준다. 관련 내용은 비교자료를 통해 설명해주도록 하겠다.
아무래도 매치 데이터를 불러오는 부분이 데이터를 가장 많이 크롤링해오는 부분이기 때문에, 이곳에서 효율성을 가지려면, 가져오는 데이터를 병렬적으로 처리해야했다. 하나를 가져오는 동안 기다리는 것이 아닌, 요청을 거의 동시에 쏴서, 데이터를 한 데 모으는 것이다. 그것과 관련된 코드를 보여주도록 하겠다.
async def test(puuid):
start = time.time()
match_id = await url(puuid)
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
data = [match_data(match, session, puuid) for match in match_id]
datas = await asyncio.gather(*data)
end = time.time()
print(end - start)
return datas
처음에는 SSL오류가 발생했다. SSL에 대해서 깊이 있게 알아보지는 않았다. 도메인 인증과 관련된 내용이었던 것 같은데, 지금은 우리가 test단계이고 배포가 아니기 때문에, 우선 SSL과 관련된 커넥션 설정은 False로 차단한 후 진행하였다.
with as
문법을 통해 session
을 해당 구문 내에서만 사용하도록 설정하였고, ClientSession
을 사용하여 여러 HTTP요청을 보내고 관리할 수 있다. 그리고 변수를 session
에 할당한다. 블록을 빠져나오게 되면 자원이 자동으로 정리되고, 리소스 누수를 방지할 수 있다.
gather(*data)
에서 데이터를 얻어오는 작업인 data
변수에 할당된
match_data
함수에 대한 결과값들을 병렬적으로 모아서 한번에 받아, datas에 할당하여 리턴해준다. 이게 굉장한 속도차이를 만들어 낸다.
수정 전 인게임 데이터 크롤링 시간 4.054414749145508
수정 후 인게임 데이터 크롤링 시간 0.6068227291107178
굉장히 눈으로만 보기에도 유의미한 차이가 난다. 드디어 매우 빠른 속도로 데이터를 가져올 수 있게 되었다! !
import time
from config import *
from models import User
import aiohttp
import asyncio
'''
mvc model 에서 controller 부분으로 구성
'''
headers = {
"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": 본인의 개발자 api를 사용하세요
}
async def get_summoner_info(summoner_name):
base_url = f"{summonerV4}{summoner_name}"
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
async with session.get(base_url, headers=headers) as response:
if response.status == 200:
data = await response.json()
return data
else:
return None
async def get_rank_data(id):
leagueV4_url = f'{leagueV4}{id}'
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
async with session.get(leagueV4_url, headers=headers) as response:
summoner_rank = await response.json()
for entry in summoner_rank:
rank['queue_type'] = entry["queueType"]
rank['tier'] = entry["tier"]
rank['rank'] = entry["rank"]
rank['leaguepoints'] = entry["leaguePoints"]
rank['wins'] = entry["wins"]
rank['losses'] = entry["losses"]
return rank
async def caculate(k, d, a):
try:
kda = (k + a) / d
kda = round(kda, 2)
except ZeroDivisionError:
kda = 999
return kda
# 데이터 로직
def save_info(db, summoner_id, summoner_puuid, summoner_name):
db_user = User(id=summoner_id,
puuid=summoner_puuid,
name=summoner_name)
db.add(db_user)
db.commit()
def get_existing_user(db, user_name):
return db.query(User).filter(User.name == user_name).first()
async def match_data(match_id, session, puuid):
match_url = f'{matchL}{match_id}'
async with session.get(match_url, headers=headers) as response:
Match_Dto = (await response.json())['info']['participants']
match_datas = []
for participant in Match_Dto:
match_data = {
'champname': "",
'Win': "",
'Kills': "",
'Deaths': "",
'Assists': "",
'Kda': "",
'PhysicalDtoC': "",
'MagicDtoC': "",
}
if participant['puuid'] == puuid:
match_data['champname'] = participant['championName']
match_data['Kills'] = participant['kills']
match_data['Deaths'] = participant['deaths']
match_data['Assists'] = participant['assists']
match_data['Kda'] = await caculate(match_data['Kills'], match_data['Deaths'], match_data['Assists'])
match_data['Win'] = 'Win' if participant['win'] else 'Loss'
match_data['PhysicalDtoC'] = participant['physicalDamageDealtToChampions']
match_data['MagicalDtoC'] = participant['magicDamageDealtToChampions']
match_datas.append(match_data)
return match_datas
async def test(puuid):
start = time.time()
match_id = await url(puuid)
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
data = [match_data(match, session, puuid) for match in match_id]
datas = await asyncio.gather(*data)
end = time.time()
print(end - start)
return datas
async def url(puuid):
match_url = f'{matchnumL}{puuid}/ids?queue=420&type=ranked&start=0&count=10'
match_id = await fetch_match_id(match_url)
return match_id
async def fetch_match_id(url):
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
async with session.get(url, headers=headers) as response:
return await response.json()