SK networks AI Camp - Toy Project 크롤링 코드(2) 및 전처리 리뷰
이전에 진행했던 코드에서 챔피언에 관한 정보가 들어 있지 않고 단순히 챔피언 레벨만 입력이 되어 있었습니다.
img의 alt값으로 챔피언 정보가 들어 있다는 것을 알게 되었고 alt값을 뽑아오도록 코드를 작성하였습니다.
2024.08.10 - [Networks/Project] - SK networks AI Camp - Toy Project 크롤링 코드 리뷰
SK networks AI Camp - Toy Project 크롤링 코드 리뷰
현재 코드를 짜서 화면에 구현하는 것까지 완료하였습니다. 코드를 리뷰하면서 지금까지 진행한 걸 다시 적어보면서 코드를 다시 한번 공부해 보는 시간을 가지겠습니다.(* 크롤링하다가 오류
joowon582.tistory.com
이전에 진행했던 코드가 모듈화가 되어있었기에 뽑아오는 데이터만 수정해줘서 코드를 금방 작성하고 완료 가능했습니다.
나머지 함수들은 그대로 사용(click_month, select_month, find_month_child, find_day_child, find_set_child, go_to_2023, go_to_2024, push_2024_game 등)했습니다.
push_2024_set 함수에서 XPath에 해당하는 데이터를 text로 받는 부분만 수정.
뽑아야 하는 데이터의 XPath 경로로 변경
[push_2024_set 함수]
○ play_time_data : 경기 소요 시간이 들어 있는 부분의 경로
○ all_ban_data : 벤픽으로 선택한 챔피언 이미지가 있는 부분 경로
○ all_play_data : 선수들이 선택한 챔피언, 스펠, 룬, 아이템의 이미지가 있는 부분의 경로
○ img 태그에서 alt 값 추출
● all_ban_data와 all_play_data만 이미지에서 alt값 추출
● parent_element 변수에 해당 요소를 찾아서 저장
● img_tags 변수에 parent_element 에 있는 img 태그를 찾기(By.Tag_NAME 으로 태그 이름으로 찾기)
● for문을 돌려서 img_tag에 img_tags 요소들을 넣어서 'alt' attribute를 찾아서 리스트에 추가
○ 트러블
● No such Element error가 계속 발생
● 멈춘 부분에서 XPath를 찾아보니 기존 변수에 선언해준 XPath와 달랐음
- error 발생 시(except 사용) play_time_data를 다른 XPath 태그로 재선언 해줘서 해결
- all_ban_data, all_play_data는 XPath가 동일
# 게임 내에서 set 번호 누르기
def push_2024_set(p_child):
set_num = find_set_child(p_child)
driver.implicitly_wait(5)
time.sleep(1.5)
# /html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[1]/div/div[1]/div[2]/div[1]
play_time_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[2]/div/div[1]/div[2]'
# /html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[1]/div/div[1]/div[3]
all_ban_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[2]/div/div[1]/div[3]'
# /html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div[2]/div/ul
all_play_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div[2]/div/ul'
for k in range(1, set_num + 1):
try:
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul/button[{}]'.format(k)).click()
driver.implicitly_wait(10)
time.sleep(7)
except NoSuchElementException:
driver.refresh()
driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/main/div[1]/div[2]/ul/button[{}]'.format(k)).click()
driver.implicitly_wait(10)
time.sleep(5)
finally:
# 값 추가
try:
lck_2024_data.append(driver.find_element(By.XPATH, play_time_data).text)
# 지정된 경로에서 먼저 해당 요소를 찾습니다
parent_element = driver.find_element(By.XPATH, all_ban_data)
# 해당 요소 내에서 모든 img 태그를 찾습니다
img_tags = parent_element.find_elements(By.TAG_NAME, 'img')
# 각 img 태그의 alt 속성을 추출하여 리스트에 추가합니다
for img_tag in img_tags:
alt_text = img_tag.get_attribute('alt')
lck_2024_data.append(alt_text)
# print(lck_2024_data)
# 지정된 경로에서 먼저 해당 요소를 찾습니다
parent_element = driver.find_element(By.XPATH, all_play_data)
# 해당 요소 내에서 모든 img 태그를 찾습니다
img_tags = parent_element.find_elements(By.TAG_NAME, 'img')
# 각 img 태그의 alt 속성을 추출하여 리스트에 추가합니다
for img_tag in img_tags:
alt_text = img_tag.get_attribute('alt')
lck_2024_data.append(alt_text)
lck_2024_data.append('='*50)
driver.implicitly_wait(5)
time.sleep(1.5)
except NoSuchElementException:
driver.refresh()
play_time_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[1]/div/div[1]/div[2]/div[1]'
lck_2024_data.append(driver.find_element(By.XPATH, play_time_data).text)
all_ban_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[1]/div/div[1]/div[3]'
# 지정된 경로에서 먼저 해당 요소 찾기
parent_element = driver.find_element(By.XPATH, all_ban_data)
# 해당 요소 내에서 모든 img 태그를 찾기
img_tags = parent_element.find_elements(By.TAG_NAME, 'img')
# 각 img 태그의 alt 속성을 추출하여 리스트에 추가
for img_tag in img_tags:
alt_text = img_tag.get_attribute('alt')
lck_2024_data.append(alt_text)
# print(lck_2024_data)
# /html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[2]/div/div[2]/div[1]/div[2]/div/ul
all_play_data = '/html/body/div[1]/div[2]/div/main/div[2]/div/div[2]/div[1]/div/div[2]/div[1]/div[2]/div/ul'
# 지정된 경로에서 먼저 해당 요소 찾기
parent_element = driver.find_element(By.XPATH, all_play_data)
# 해당 요소 내에서 모든 img 태그를 찾기
img_tags = parent_element.find_elements(By.TAG_NAME, 'img')
# 각 img 태그의 alt 속성을 추출하여 리스트에 추가
for img_tag in img_tags:
alt_text = img_tag.get_attribute('alt')
lck_2024_data.append(alt_text)
lck_2024_data.append('='*50)
driver.implicitly_wait(5)
time.sleep(1.5)
# finally:
# lck_2024_data_2.append(game_time)
# lck_2024_data_2.append(detail)
# lck_2024_data_2.append('='*50)
# 끝나면 다시 원래 화면으로 돌아가기
driver.get(url)
driver.implicitly_wait(5)
time.sleep(2)
크롤링이 끝난 후 파일명은 기존 파일과 구분하여 LCK_년도_alt_data_월.json 파일로 저장
아래 사진과 같이 저장된 파일에서 데이터가 정렬이 안되어 있어 보기 편하게 정리하여 저장
* 리스트 하나에 모든 정보가 들어가 있어서 보기가 불편함
데이터 전처리 후 파일을 저장(모듈화하여 코드 작성)
○ 전처리 전 데이터 형식
● data[0] : 경기시간 \n 팀1 \n 킬스코어1 \n 킬스코어2 \n 팀2 \n 팀 플레이어 \n MVP
● data[1:6] : 1 ~ 5까지 벤 챔피언 5개
● data[6:11] : 6 ~ 10까지 벤 챔피언 5개
● data[11] : 팀1
● data[12] : 챔피언
● data[13:15] : 13, 14 스펠
● data[15:17] : 15 주룬, 16 보조룬
● data[17:24] : 17 ~ 22 아이템 6개 + 23 와드
● 트러블 : 선수의 아이템 갯수가 다름 why? 아이템 창 빈 곳의 경우 img가 존재하지 않음
- 그래서 리스트로 나눠서 담아놓고 전처리를 진행
- '=' * 50을 기준으로 나눠서 저장
○ 정리할 데이터 양식
● List[i][0] : 경기 시간
● List[i][1] : 경기 스코어; 팀명1 킬스코어1 킬스코어2 팀명2
● List[i][2] : MVP 선수 정보 : MVP 팀 닉네임
● List[i][3] : 벤픽한 챔피언 10개
● List[i][4] : 챔피언/룬/스펠/아이템 정보
[(맨 앞에만 팀명)챔프 스펠1 스펠2 룬1 룬2 아이템]* 5 [(맨 앞에만 팀명)챔프 스펠1 스펠2 룬1 룬2 아이템]* 5
※ 아이템 창을 꽉 채워서(6개+1개:와드) 안 한 경우도 있음
○ 함수 살펴보기
● parse_first_string : 첫 번째 문자열 처리 함수
- 받은 인자(data_str)을 '\n'으로 구분하여 parts 변수에 저장
- game_time 변수에 parts의 0번째 인덱스를 넣어 시간 저장
- game_score 변수에 공백을 추가하여 parts[1]부터 parts[4]까지 넣어 하나의 문자열로 경기 스코어를 저장
- mvp_team_player 변수에 MVP 팀 선수명 형식으로 저장
- game_time, game_score, mvp_team_player을 return
● combine_strings : 문자열을 공백으로 받은 인자(count)만큼 결합
- 받은 인자 data의 시작 인덱스 부터 count개 만큼 공백을 추가하여 결합
● make_champion_information : '=' * 50개 전까지의 데이터를 공백으로 결합
- 받은 인자(start_idx)부터 받은 인자(data)의 길이만큼 for문을 돌림
- 만약 받은 data의 j번째가 구분자('='*50으로 인자로 전달받음)랑 같으면 값과 인덱스 번호 j를 return
- 예외로 구분자가 없는 경우 ''와 마지막 인덱스인 len(data)를 return
● process_data : 결과 리스트 생성
- split_list : 결과를 저장할 리스트
- data의 길이까지 while문을 반복
- temp_list : 임시 리스트는 while문 안에 둬서 매번 초기화
- game_time, game_score, mvp_team_player 변수에 parse_first_string 함수가 return한 값을 저장
- 임시 리스트인 temp_list에 하나씩 저장
- combined_string에 값을 넘겨 combine_strings 함수가 return한 문자열을 저장하고 temp_list에 저장
- combined_until_separator와 next_idx 변수에 make_champion_information 함수의 return 값을 저장
- 임시 리스트 temp_list에 combined_until(챔피언, 룬, 스펠, 아이템 정보) 저장
- 결과 리스트인 split_list에 임시 리스트에 저장
- i 변수에 next_idx + 1을 하여 다음 게임 정보가 시작하는 인덱스를 저장
- while문이 끝난 후 결과를 저장한 split_list를 return
import json
def parse_first_string(data_str):
"""첫 번째 문자열 처리"""
parts = data_str.split('\n')
# print(parts)
game_time = parts[0]
game_score = ' '.join(parts[1:5])
mvp_team_player = f"{parts[-1]} {parts[-2]}"
return game_time, game_score, mvp_team_player
def combine_strings(data, start_idx, count):
"""문자열을 공백으로 count개 결합"""
return ' '.join(data[start_idx:start_idx+count])
def make_champion_information(data, start_idx, separator):
""" '='*50 전까지의 데이터를 공백으로 결합"""
for j in range(start_idx, len(data)):
if data[j] == separator:
return ' '.join(data[start_idx:j]), j
return '', len(data) # 구분자를 찾지 못한 경우
def process_data(data):
"""결과 리스트 생성"""
split_list = []
i = 0
while i < len(data):
temp_list = []
# print(i)
# print(data[i]) #############################
# 첫 번째 문자열을 나누기 => 시간, 스코어, MVP로
game_time, game_score, mvp_team_player = parse_first_string(data[i])
temp_list.append(game_time)
temp_list.append(game_score)
temp_list.append(mvp_team_player)
# 벤픽
combined_string = combine_strings(data, i+1, 10)
temp_list.append(combined_string)
# 챔피언과 아이템 정보
combined_until_separator, next_idx = make_champion_information(data, i+11, '='*50)
temp_list.append(combined_until_separator)
# 완성된 서브리스트를 결과 리스트에 추가
split_list.append(temp_list)
i = next_idx + 1
#print('='*50)
return split_list
● Json을 열고 파일을 저장하는 함수
- open_json과 save_json 함수에 이름을 바꿔가면서 파일을 열고 파일을 저장하기 위해 format을 사용
- 연도(y), 월(i)를 넣어가면서 for문을 사용하여 한 번에 할 예정
# Json 열기
def open_json(y, i, data):
with open('LCK_{}_alt_data_{}.json'.format(y, i), "r") as f:
data = json.load(f)
return data
# Json 저장하기
def save_json(y,i, split_list):
# json 파일로 저장
with open('Pre_LCK_{}_alt_data_{}.json'.format(y,i), 'w', encoding='utf-8') as f :
json.dump(split_list, f)
○ 함수를 사용하여 결과 받기
● 2023년 데이터
- 1월부터 8월까지 존재(5월은 존재하지 않음)
- open_json 함수에 2023(y), 월(i : 1 ~ 8), data(빈 리스트)를 줘서 해당 json 파일을 저장한 data 리스트를 return 받음
- process_data에 저장한 data를 넘겨주고 split_list(저장된 리스트)를 반환 받아 split_list에 저장
- save_json에 2023(y), 월(i : 1 ~ 8), split_list(데이터)를 넘겨주고 json 파일로 저장
● 2024년 데이터
- 1월부터 7월까지 존재(5월은 존재하지 않음)
- open_json 함수에 2024(y), 월(i : 1 ~ 7), data(빈 리스트)를 줘서 해당 json 파일을 저장한 data 리스트를 return 받음
- process_data에 저장한 data를 넘겨주고 split_list(저장된 리스트)를 반환 받아 split_list에 저장
- save_json에 2024(y), 월(i : 1 ~ 7), split_list(데이터)를 넘겨주고 json 파일로 저장
● 트러블
- 2024년 4월 데이터를 받아와 parse_first_stirng 함수에서 나누는 도중에 List Index Out of Range 에러 발생
- 어디서 에러가 발생했는 지 보기 위해 j를 인자에 추가하여 print
- 49번째에서 에러발생 → JSON 파일을 살펴보니 첫 줄에 경기 시간만 들어옴
- Crawling 중간에 에러가 발생한 것으로 보임
- 누락된 값이 얼마 되지 않는 것으로 확인하여 그냥 데이터를 같은 형식으로 수정
- 6/26 T1 2 0 DRX 경기부터 에러발생
- 바꾼 데이터(in Json)
성공한 데이터들 예시
"24:07\nKT\n3\n18\nHLE\nHLE Zeka\nMVP",
실패한 데이터
"34:24",
변경해준 데이터들
6/26 T1 2 0 DRX 경기
○ 1set
"34:24\nT1\n11\n7\nDRX\nT1 Oner\nMVP",
○ 2set
"21:08\nT1\n9\n2\nDRX\nT1 Keria\nMVP",
6/26 19:30 HLE 0 2 GEN 경기
○ 1set
"32:53\nGEN\n14\n6\nHLE\nGEN Peyz\nMVP",
○ 2set
"42:08\nHLE\n16\n19\nGEN\nGEN Canyon\nMVP",
6/27 17:00 NS 0 2 DK 경기
○ 1set
"25:17\nNS\n7\n21\nDK\nDK Kingen\nMVP",
○ 2set
"26:30\nDK\n13\n7\nNS\nDK Lucid\nMVP",
6/27 19:30 KT 2 1 BRO 경기
○ 1set
"29:15\nKT\n20\n13\nBRO\nKT PerfecT\nMVP",
○ 2set
"37:56\nBRO\n18\n17\nKT\nBRO YoungJae\nMVP",
○ 3set
"25:18\nKT\n26\n4\nBRO\nKT Pyosik\nMVP",
# 2023년 데이터
for i in range(1, 9):
split_list = list()
data = list()
if i == 5:
continue
else:
data = open_json(2023, i, data)
split_list = process_data(data)
save_json(2023, i, split_list)
# 2024년 데이터
for i in range(1, 8):
split_list = list()
data = list()
if i == 5:
continue
else:
data = open_json(2024, i, data)
split_list = process_data(data)
save_json(2024, i, split_list)
○ 출력 결과
저장한 Pre_LCK_년도_alt_data_월.json 파일을 전처리한 이 후 코드 리뷰를 진행하겠습니다.