본문 바로가기
Analysis Tips

[Python] Beautifulsoup으로 KBO 기록실을 털어보자 -1탄

by dovah. 2020. 6. 21.
반응형

예전에 야구를 참 즐겨봤었다.

지역이 충청이다 보니 한화를 응원했는데, 난 부처가 아닌 것을 알게되었다.

그러면서 야구를 안보게 되었다.

 

그러다 회사때문에 성남으로 이사오고 주변 친구들도 야구를 보게되면서

나도 다시 야구를 보게 되었다.

 

대신 한화가 아닌 KT를 응원하고 있다!!

 

그러다 문득, KBO 역대 기록들을 수집하고 이걸 시각화하는 걸 해보면 재밌지 않을까? 라는 생각에

KBO 크롤러를 만들게 되었다.

 

크롤러는 Beautifulsoup을 사용하는 버전과, Selenium을 사용하는 버전으로 나눠서 만들 예정인데,

오늘 올릴 버전은 Beautifulsoup이다.

 

KBO 기록실 주소는 아래와 같다.

https://www.koreabaseball.com/Record/Player/Runner/Basic.aspx

 

제공하고 있는 데이터는 타자, 투수, 수비, 주루로 4개의 카테고리로 구분되어 있다.

 

아래 그림을 보면 각 카테고리별로  URL이 바뀌는 걸 알 수 있다.

나는 크롤링을 만들때 URL을 최대한 활용할려고 노력하는 편이다.(물론 내 기준)

 

처음에 URL의 HtitterBasic, PitherBasic 등과 같은 부분만 변경했는데,

수비와 주루의 경우 뒤에 부분이 달라서 아예 그냥 다 수정해서 진행했다.

 

import pandas as pd
import requests
from bs4 import BeautifulSoup as bs

url_base = 'https://www.koreabaseball.com/Record/Player/{category}'
category_list = ['HitterBasic/Basic1.aspx', 'PitcherBasic/Basic1.aspx', 
                 'Defense/Basic.aspx', 'Runner/Basic.aspx']
                 
for category in category_list:
    url = url_base.format(category = category)

여기서 나는 format을 사용해서 URL을 건들였는데 그냥 url_base + category를 해도 같은 결과를 얻을 수 있다.

 

 

기록실에 있는 여러 데이터들 중에서 내가 원하는 부분은 바로 블럭처리 되어 있는 테이블이다.

 

검사를 통해서 페이지 소스를 보면 해당 블럭의 경우 div 태그에 class 값이 record_result인 것을 확인 할 수 있다.

 

for category in category_list:
    url = url_base.format(category = category)
    req = requests.get(url)
#### 페이지 접속
    html = req.text
    soup = bs(html, 'html.parser')
#### html 구조로 변환    
    temp_table = soup.find('div', {'class':'record_result'})
#### 데이터 테이블 지정

웹페이지에 접속 한 후 웹페이지 소스를 가져온다.(req.text)

소스를 그냥 사용할 경우

이렇게 알아보기 힘든 형태이기 때문에 html 구조 형식으로 변환해줘야한다.

beautifulsoup 에서 쉽게 변환 할 수 있도록 파서를 제공하고 있는데 이 부분이 bs(html, 'html.parser')이다.

 

이후 soup.find를 사용해 해당 데이터의 값을 가져왔다.

참고로 해당 값들을 알고 있는 경우 selenium에서는 xpath를 설정해 데이터를 가져올 수 있다.

 

 

타자 기록(좌)과 도루 기록(우)

데이터가 row별로 쌓이고 있는 것을 확인 할 수 있으며 칼럼의 경우 종류에 따라 수가 다른 것을 확인할 수 있다.

 

데이터 수집을 위해서 빈 DataFrame을 만들고 거기에 한 줄씩 채워가는 형식으로 데이터를 수집할려고 했는데,

종류에 따라 칼럼명이 다르기 때문에 다소 고민이 되었다.

각 카테고리 별로 칼럼명을 지정하느냐 아니면 테이블에 있는 칼럼명을 수집해서 진행하느냐...

 

카테고리별로 칼럼명 지정하는 건 귀찮으니까 테이블에 있는 칼럼명을 수집해서 하기로 했다.

 

근데 신기하게 순위, 이름, 소속의 경우에는 별도의 attribute가 없다.

col_tag = temp_table.find_all('td')
#### 모든 Row 수집
col_list = ['Rank', 'Name', 'Team']
#### 항상 기본으로 들어가 있는 칼럼
for col in col_tag:
	try:
    	temp_value = col.attrs['data-id']
        #### data-id 값 수집
        if temp_value not in col_list:
        	col_list.append(temp_value)
        else:
            break
        #### 칼럼명 리스트에 없는 경우에만 추가
    except:
        pass
        
temp_data = pd.DataFrame(columns = col_list)
#### 해당 칼럼명을 가진 빈 DataFrame 생성

앞에 3개(순위, 이름, 소속)의 경우 별도 attribute 값이 없기 때문에 고정 값으로 설정 후 진행했다.

이후 칼럼명 값을 수집하고 이를 바탕으로 빈 DataFrame을 생성했다.

 

 

테이블 값을 그대로 출력해보면 하나의 List에 데이터가 순서대로 쌓여 있는 것을 알 수 있다.

 

이럴 경우 나는 각 데이터의 index를 활용해서 데이터를 수집한다.

 

 

i = 0
#### DataFrame index  값
index = 0
#### 수집할 데이터의 index 값
col_len = len(col_list)
#### 칼럼 수
while True:
	try:
    	temp_data.loc[i] = [x.text for x in temp_table.find_all('td')[index:index + col_len]]
        i += 1
        #### DataFrame index의 경우 1씩 증가
        index += col_len
        #### 수집 데이터 index의 경우 각 칼럼 수만큼 증가 
    except:
        break
    

데이터 수집의 경우 각 칼럼 수만큼 인덱스 값을 증가시키고

데이터를 추가할 때는 한 row씩 넣기 때문에 1씩 증가 시킨다.

 

이렇게 하면 각 카테고리별 데이터 수집이 끝난다.

다만, 연도별 수집을 하기위해선 selector 조작을 해야하는데, 이 부분은 Selenium을 활용한 크롤러를 만들 때 해볼 예정이다.

 

<전체 코드>

import pandas as pd
import requests
from bs4 import BeautifulSoup as bs

url_base = 'https://www.koreabaseball.com/Record/Player/{category}'
category_list = ['HitterBasic/Basic1.aspx', 'PitcherBasic/Basic1.aspx', 
                 'Defense/Basic.aspx', 'Runner/Basic.aspx']

data_list = []

for category in category_list:
    url = url_base.format(category = category)
    req = requests.get(url)

    html = req.text
    soup = bs(html, 'html.parser')
    
    temp_table = soup.find('div', {'class':'record_result'})
    #### get table info
    
    col_tag = temp_table.find_all('td')
    col_list = ['Rank', 'Name', 'Team']
    for col in col_tag:
        try:
            temp_value = col.attrs['data-id']
            if temp_value not in col_list:
                col_list.append(temp_value)
            else:
                break
        except:
            pass
        
    temp_data = pd.DataFrame(columns = col_list)

        
    i = 0
    index = 0
    col_len = len(col_list)
    while True:
        try:
            temp_data.loc[i] = [x.text for x in temp_table.find_all('td')[index:index + col_len]]
            i += 1
            index += col_len
        except:
            break
    
    data_list.append(temp_data)
    
반응형

댓글