Data

크롤링 데이터 전처리(naver)

Developer trainee_J ^~^ 2023. 7. 11. 15:45

지난 포스팅에서 네이버 스포츠 -> 야구 -> 일정/결과 -> 일자별 경기결과 진입까지 완료했었다. 

이번 포스팅은 일자별 경기결과 데이터를 크롤링하여 전처리 후 DB에 적재하는 과정을 다뤄보고자 한다.

 

크롤링을 하면서 가장 먼저 작업한 것은 크롤링 페이지에 내가 원하는 정보가 있는지와 어느 부분을 가져와야하는지 였다.

분석

 

화면에 나와있는 데이터는 위 사진과 같았고 데이터는 적당히 있는 것 같았으나 문제가 2개 있었다. 

 

1. '타석' 컬럼이 없다. 
'타석'컬럼은 규정타석을 계산하기위해 필요한 지표이다. 만약 이 데이터가 없다면  

A선수 : 10타석 9타수 5안타, B선수 : 1타석 1타수 1안타  두명의 선수 중 주간 타율 TOP 10을 계산할 때 B선수가 더 높은 순위에 랭크되게 된다. 물론 타율은 B선수가 높지만 1타수 1안타는 표본이 너무 적었고 실제 KBO에서도 '규정타석'이라는 지표를 만들어 표본이 적은 선수들을 걸러내게 된다. '타석' 컬럼이 없어서 어떻게 해야할지 고민하던 순간 옆쪽에 이닝별 타자의 기록을 사용하기로 했다. 

1.1 고려사항 
타자의 기록은  해당 타자가 어떤걸했는지 알려주는데, 중요한것은 '이닝'별로 데이터가 들어가있다는 것이다. 

각 이닝은 \n으로 구분되어있어서 \n을 기준으로 split 해주면 될 것 같았지만 위 사진 4번타자 오스틴의 기록을 보면 한 이닝에 두개의 기록이 들어있음을 확인할 수 있다. 이런경우는 타순이 한바퀴를 돈 경우이며 흔한일은 아니지만 예외처리가 필요해보였다. 

 

2. 선수 이름 데이터밖에 없어서 이름이 같을 경우 데이터가 중복이 될 수 있다.

statiz는 선수의 생년월일을 제공하여 이 문제를 회피했었지만 naver는 데이터 안에 생년월일이 없었다. 다행이도 선수 table 데이터 안에 선수의 ID값이 존재했었고 이 ID를 활용하기로 했다. 

 

수행

1. 데이터 전처리

 

#선수하나하나의 기록 저장용
    temp_list = []

    # 전체 선수의 기록 저장
    temp_list2 = []

    # 타석 수 저장
    tpa_list = []
    
    # 구단명 저장
    team_list = []
    cnt2 = 0
    for hometeam in batting_list:
        team = hometeam.split(' ')[0]
        team_list.append(team)
        for player_list in hometeam.split('\n번 타자\n')[1:]:
            #모든 선수 list에 넣기
            for i in player_list.split('교체\n'):
                for j in i.split('\n\n')[0].replace('\n', '\t').split('\t'):
                    if j != '':
                        temp_list.append(j)
                for k in temp_list[10:]:
                    if '/' in k:
                        cnt2 += 1
                tpa_list.append([len(temp_list[10:]) + cnt2,team])
                temp_list2.append(temp_list[0:10])
                temp_list = []
                cnt2 = 0

temp_list에 각 선수 개개인이 append되는 형식이고 9번 인덱스 까지는 선수이름부터 타율까지 고정된 데이터가 들어있어서 따로 분리를 해줬다. temp_list의 10번 인덱스 부터는 분석 1번에서 사용될 타석 데이터가 들어있었다. 

타석데이터는 10번 인덱스부터의 len과 '/'를 가지는 경우 그 개수를 더해주는 형식으로 진행했다. 

 

batting = pd.DataFrame(temp_list2, columns= ['player_name','P','AB','R','H','RBI','HR','BB','SO','AVG'])
tpa = pd.DataFrame(tpa_list,columns= ['TPA','TEAM'])
batting_result = pd.concat([batting,tpa], axis=1)
temp_list2 = []

이제 temp_list2과  tpa_list를 df 형식으로 변환하여 batting_result에 담아뒀다. 

'오스틴'선수의 TPA(타석)값을 보면 정상적으로 5로 들어가있음을 확인할 수 있다. 

 

2. 원정팀 데이터 가져오기 

경기결과 페이지는 동적 웹으로 원정팀 데이터를 가져오려면 button을 눌러줘야한다. 

타격 데이터를 가져올 때와 마찬가지로 button의 xpath를 가져와서 execute_script함수를 이용하여 button을 눌러줬다. 

try:    
    button = driver.find_element(By.XPATH, button_xpath)
    driver.execute_script("arguments[0].click();", button)
except:
    button = driver.find_element(By.XPATH, button_xpath2)
    driver.execute_script("arguments[0].click();", button)

이렇게 되면 크롬창의 화면데이터가 바뀌게 되고 같은 xpath를 사용하여 데이터를 긁어오면 홈팀이 아닌 원정팀 정보가 크롤링된다.  데이터 전처리부분을 함수로 구성해둬서 같은 함수에 똑같이 넣기만하면 된다. 

 

3. 선수 ID값 가져오기 

선수 ID값을 가져오는 방법도 매우 간단했다.

def id_split(info, team, types):
    id_list = []
    if types == 'batting':
        for i,j in zip(info,team):
            for ID in i.split('playerId=')[1:]:
                id_list.append([ID.split('"')[0], ID.split('class="PlayerRecord_name__1W_c0">')[1].split('</span><span class="PlayerRecord_position__3SBbd">')[0],j])

    elif types == 'pitching':
        for i,j in zip(info,team):
            for ID in i.split('playerId=')[1:]:
                id_list.append([ID.split('"')[0], ID.split('class="PlayerRecord_name__1W_c0">')[1].split('</span>')[0],j])
    result = pd.DataFrame(id_list,columns=['player_ID','player_name','TEAM'])
    return result

이 함수를 만들어 사용했으며 'playerID=' 기준으로 split해준 뒤 ID, player_name, TEAM데이터를 리스트에 저장하여 df 형식으로 바꿔줬다. 그런 후 batting_result df와 merge하여 완전한 df를 만들어 줬다. 

 

 

투수 쪽은 특별한 로직일 들어갈 필요가 없어서 split함수를 이용하여 데이터를 크롤링했다. 

여기까지 코드를 완성했을 때 특별한 문제가 없을 거라고 생각했다. 하지만 그럴리가 없다는 점..

전체 데이터를 넣는 과정에서 4월 23일 두산 vs KT 경기에서 자꾸 에러가 발생했다. 에러내용은 해당 xpath에 제대로된 데이터가 들어있지 않다는 것이었다. 분명 뭔가 이상했다. 여태 다른 경기는 잘됐는데 왜 안될까 생각해보니 

우선 이 경기는 1:1 무승부로 끝난 '노 디시전'이었다. 하지만 '노 디시전'이라 해서 데이터가 비어있을리 없었고, 똑같이 '노 디시전'인 5월 22일 한화 vs LG경기는 정상적으로 크롤링이 되었다. 

4월 23일 두산 vs KT 경기의 타격 table xpath를 확인해보니 

batting_xpath = '//*[@id="content"]/div/div/section[2]/div[2]/div/div[5]/div[2]/div[1]/div[2]/div/div/table'
batting_xpath2 = '//*[@id="content"]/div/div/section[2]/div[2]/div/div[4]/div[2]/div[1]/div[2]/div/div/table'

batting_xpath2 와 같이 div하나가 5에서 4로 바뀌어 있었다. 

 

이 두개의 차이점을 찾아보니 베스트 플레이어 결과 및 홀드와 같은 주요 지표를 달성한 선수가 없어서 해당 div가 생성되지 않았던 것이었고 그로인해 div가 하나씩 밀렸던 것이다.  즉 같은 '노 디시전'이더라도 홀드 와 같은 특수 지표가 없다면 div가 하나씩 밀리게 된다는 것이다. 정말 예상치 못한 부분이어서 후딱 try~ except문을 추가하여 예외처리를 진행해줬다. 

 

마치며

이번 포스팅을 통해 statiz와 naver 크롤링을 둘다 다뤄봤는데 확실히 동적 웹 크롤링이 더 어려운것 같다. 지금 당장은 데이터가 제대로 긁어질지라도 naver 스포츠 화면이 조금이라도 바뀌면 문제가 생길 수 있는 아슬아슬한 코드이다. 하지만 지금 당장 뚜렷한 해결방법이 없어서 이대로 사용해야한다는게 불안하지만 우선 크롤링이 된다는것에 감사해야겠다. 

 

'Data' 카테고리의 다른 글

일자별 데이터를 주별 데이터로 변환하기  (0) 2023.05.12
크롤링 데이터 전처리 (2)  (0) 2023.05.03
크롤링 데이터 전처리 (1)  (0) 2023.04.25