프로젝트/[DA] 데이터 분석 : 배구

프로젝트 설명 & 데이터 수집 (웹 크롤링), 저장 - 선수 데이터

sollllllll 2022. 3. 11. 23:48

새롭게 시작하는 프로젝트는 스포츠 데이터 분석 프로젝트로, 스포츠 중 여자배구의 데이터를 분석해보려고 한다.

프로젝트의 큰 목표는 선수들의 경기 기록 데이터를 통해 능력치를 구하고, 어떤 두 팀이 경기를 치른다고 가정할 때 포지션 별로 선수를 지정하면 어느 팀이 승리할지 예측하는 것이다.

 

프로젝트의 진행 과정을 담을 것이기 때문에 배구의 자세한 룰 설명은 생략하도록 하겠다.

선수의 경기 기록 데이터에는 크게 공격, 서브, 세트, 블로킹, 리시브, 디그 내용이 포함되어 있다.

각 포지션 별로 중요하게 여겨지는 항목이 조금씩은 다르지만 위 항목들을 토대로 선수의 역량을 측정한다.

 

 

 

데이터를 분석하기 위해서 가장 먼저 해야 할 일은 분석할 데이터를 마련하는 일이다.

데이터를 마련하려면 여러 방법이 있겠지만 이미 있는 데이터를 사용하는 것이 아니면 데이터를 직접 모아야 한다.

그리고 데이터를 모을 때 가장 많이 쓰이는 방법이 바로 크롤링이다.

 

자 그럼 크롤링을 하기 위해 코드를 작성해보자.

 

 

 

(1) 라이브러리 import

from selenium import webdriver	# 크롤링에 사용
import pandas as pd	# 데이터프레임, csv 생성에 사용
import time

 

 

 

(2) 크롤링

내가 크롤링 할 웹페이지는 다음과 같이 생겼다.

포지션과 포지션 별 페이지 수를 입력받아서 자동으로 크롤링하도록 할 것이다.

 

크롤링 할 웹페이지

 

 

크롬 브라우저에서 크롤링하기 위해서는 chromedriver 를 사용한다.

파일을 다운받은 후 해당 파일을 드라이버로 사용한다.

다음으로 url 을 자동으로 변경하며 크롤링할 때 필요한 변수를 입력받도록 한다.

browser = webdriver.Chrome('C:/Users/ADMIN/Desktop/chromedriver.exe')  # chromedriver 다운 필요

position = input("포지션: ")
page = int(input("페이지 수: "))

다음으로 url 을 입력하고 드라이버가 url 을 사용하도록 get 하고, 잠깐의 시간 텀을 둔다.

search_url = "url 입력"

browser.get(search_url)
browser.implicitly_wait(2)

 

 

자 이제 웹페이지의 html 구조를 보자.

우리가 추출해야 할 데이터들이 table 태그 안에 있는 것을 알 수 있다.

이 정보들을 토대로 크롤링 코드를 짜보도록 하겠다.

html 구조

table 안의 내용을 크롤링 하는 기본 구조는 아래와 같다.

테이블을 찾을 때 class_name 으로 찾았는데, 위의 html 구조와 아래 코드를 봤을 때 "table-div" 는 테이블 태그의 클래스명이 아니지만 이 클래스명을 사용해야 정상적으로 크롤링이 이뤄졌다.

이처럼 크롤링이 원하는대로 되지 않을 때는 상위와 하위를 오가며 적절한 태그를 찾아봐야 한다.

 

아무튼 처음에 테이블을 찾을 때는 class_name 으로 찾고,

그 다음부터는 tag_name 으로 tbody 와 tr, td 영역을 찾는다.

find_element 와 find_elements 의 차이는 한 개를 얻는지 여러 개를 얻는지에 대한 차이이다.

find_elements 메소드를 사용할 때는 인덱스를 사용할 수 있고, 슬라이싱을 통해 원하는 영역만큼 데이터를 추출한다.

 

나는 좀 더 세분화하느라 데이터를 변수에 저장하고, 미리 선언해 둔 리스트에 변수를 추가했지만

데이터를 바로 리스트에 추가해도 문제는 없다. (바로 추가하는 것이 효율은 더 좋을 것..)

table = browser.find_element_by_class_name("table-div")	# 똑같이 테이블이어도 필요한 클래스명이 다를 수 있음
tbody = ptable.find_element_by_tag_name("tbody")
trows = ptbody.find_elements_by_tag_name("tr")

for index, value in enumerate(trows):
    data = value.find_elements_by_tag_name("td")[1:]
    name = data[0].text
    team = data[1].text
    ...
    namelist.append(name)
    teamlist.append(team)
    ...

 

 

데이터 추출이 끝났다면 드라이버로 사용했던 브라우저를 닫아준다.

browser.close()

 

 

 

(3) 데이터프레임 생성, csv 파일로 저장

리스트에 각 내용이 모두 담겼다면 데이터프레임에서 사용할 column 과 값을 딕셔너리의 { 키:값 } 형태로 저장한다.

그 다음 딕셔너리를 데이터프레임 형식으로 변환하고 확인해본다.

# DataFrame - Libero
data = {"name":namelist,"team":teamlist,"pos":"Li","score":scorelist,"error":errlist,...}

libero = pd.DataFrame(data)
libero.index += 1
libero.head(5)

데이터프레임이 잘 생성된 것을 볼 수 있다!

데이터프레임을 생성할 때 따로 지정하지 않으면 기본으로 인덱스를 지정해주는데 인덱스가 1부터 시작하도록 코드에 추가해주었다.

리베로 데이터

 

 

같은 방법으로 레프트, 라이트, 센터, 세터 포지션의 데이터도 추가로 얻는다.

이제 각 포지션 별 csv 파일과 모두 합친 파일을 만들어보자.

 

DataFrame.to_csv() 메소드를 이용해 데이터프레임을 csv 파일로 변환한다.

메소드 속성에 파일 경로와 파일 이름, 인코딩 방법을 지정하는데 인코딩 방법은 utf-8-sig 를 사용해야 한글이 깨지지 않는다.

libero.to_csv('C:\\...\\libero.csv',encoding='utf-8-sig')
left.to_csv('C:\\...\\left.csv',encoding='utf-8-sig')
right.to_csv('C:\\...\\right.csv',encoding='utf-8-sig')
center.to_csv('C:\\...\\center.csv',encoding='utf-8-sig')
setter.to_csv('C:\\...\\setter.csv',encoding='utf-8-sig')

 

중간에 리베로 데이터를 얻은 후 다른 포지션 데이터를 얻을 때 포지션을 모두 Li 로 설정하는 실수가 있었다.

하지만 데이터프레임 특성으로 간단하게 해결했다.

한 칼럼의 모든 값을 바꿀 때는 아래와 같이 DataFrame[column] = value 형식을 사용할 수 있다.

left["pos"] = "L"

 

아무튼 이제 각 포지션 별 데이터를 만들었으니 이 포지션들을 모두 합쳐서 선수 데이터를 만들어보려고 한다.

data 라는 리스트 안에 데이터프레임들을 저장하고 판다스의 concat() 메소드를 사용해서 행 방향으로 합쳤다.

데이터프레임 별로 인덱스가 있기 때문에 ignore_index 속성을 True 로 설정해주어서 각 인덱스를 무시하도록 했다.

이 때 각 데이터프레임의 컬럼이 다르면 컬럼 부분에 다른 컬럼이 추가되고 값이 없는 컬럼은 빈 값으로 채워진다.

data = [libero,left,right,center,setter]
player = pd.concat(data,ignore_index=True)
player.index += 1

player.to_csv('C:\\...\\player.csv',encoding='utf-8-sig')

 

 

선수 데이터인 player.csv 파일을 보면 다음처럼 잘 합쳐진 것을 확인할 수 있다.

 

 

 

이번에는 이렇게 프로젝트에 쓸 데이터를 만드는 과정을 다뤄보았다.

다음 포스트에서는 데이터 전처리를 다뤄 볼 예정이다.