Networks/Project

SK networks AI Camp - Toy Project 크롤링 코드(2) 및 전처리 리뷰

코딩하는 Español되기 2024. 8. 15. 18:00

이전에 진행했던 코드에서 챔피언에 관한 정보가 들어 있지 않고 단순히 챔피언 레벨만 입력이 되어 있었습니다.

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 파일을 전처리한 이 후 코드 리뷰를 진행하겠습니다.