처음 버전은
https://chaos-apic.tistory.com/7
개발 환경
os: Ubuntu-20.04
language: python3.10.14
cpu: i9-13900kf
홈페이지 구성 확인
먼저 홈페이지에 들어가 보았다.
타자, 투수, 수비, 주루 4가지로 나뉘어져 있고
연도, 시즌, 팀, 포지션, 상황 별로 나뉘어져 있다.
또한 페이지가 동적이기 때문에 페이지내에서 변경사항이 있더라도 url 변동이 없다.
그렇기 때문에 셀레니움을 이용해볼 생각이다.
테이블 추출
일단 테이블을 추출하고 이를 데이터프레임으로 만들어보자.
# 페이지에서 테이블 추출
def create_table(driver):
# 페이지 소스 가져오기
kbo_page = driver.page_source
# html로 파싱
soup = BeautifulSoup(kbo_page, 'html.parser')
# 테이블 추출
table = soup.select_one('#cphContents_cphContents_cphContents_udpContent > div.record_result > table')
# 데이터프레임으로 변환
table = pd.read_html(str(table), flavor='html5lib')[0]
return table
그럼 이렇게 잘 추출 된 것을 확인 할 수 있다.
그럼 이것을 팀 별로 추출해보자.
팀 별 데이터 추출
먼저 kbo는 지금까지 여러 팀이 생기고 사라졌기 때문에 팀 이름이 일정하지 않다.
그래서 매년 팀 이름을 확인하고 선택하는 방식으로 하려고 한다.
팀 이름 출력
# 콥보 박스 선택
combobox = driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ddlTeam_ddlTeam')
# 콤보박스 안의 옵션들 선택
options = combobox.find_elements(By.TAG_NAME, 'option')
# 팀 이름 출력
teams = [option.text for option in options]
teams
그럼 이렇게 나오는데 여기서 "팀 선택"은 전체이기 때문에 필요가 없으므로 제외한다.
셀레니움에는 콤보박스에서 옵션 선택 방식이 두 가지가 있다.
- value 선택
- 텍스트로 선택
나는 여기서 텍스트로 선택을 해볼 생각이다.
# 콤보 박스 선택
select = Select(combobox)
# 옵션 선택
select.select_by_visible_text(teams[0])
이렇게 하니 첫 번째 값인 기아의 선수 데이터가 선택되었음을 알 수 있다.
이것을 for문으로 돌리면 모든 팀을 선택할 수 있게 된다.
연도 선택
연도같은 경우에는 KBO 출범인 1982년 부터 현재 2024년까지 있음으로 위와 같은 방식으로 하면 된다.
# 시즌 콥보 박스 선택
season_combo = driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ddlSeason_ddlSeason')
# 옵션들 선택
season_options = season_combo.find_elements(By.TAG_NAME, 'option')
# 옵션들 출력
seasons = [option.text for option in season_options]
print(seasons)
그럼 이렇게 시즌 연도도 잘 출력되었다.
아니면 어차피 시즌은 정해져있기 때문에
seasons = [str(i) for i in range(1982,2025)]
print(seasons)
이렇게 해도 같은 결과를 나타낸다.
그리고 선택을 해보면 잘 나오는 것을 알 수 있다.
# 시즌 리스트
seasons = [str(i) for i in range(1982,2025)]
# 시즌 콤보 박스 선택
season_combo = driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ddlSeason_ddlSeason')
# 시즌 콤보 박스 선택
season_combo = Select(season_combo)
# 값 선택
season_combo.select_by_value(seasons[0])
연도별 팀별 데이터 추출
그럼 이제 연도별, 팀별 데이터를 추출해보자.
kbo 데이터는 2002년을 기점으로 데이터 형식이 바뀌어있다.
그래서 그냥 데이터를 추출해서 합치면 몇 몇 데이터에 구명이 생긴다.
일단 보기 편하게 하기 위해 1982-2001, 2002-2024년으로 데이터를 나눌 생각이다.
먼저 앞의 년도부터 해보자.
그리고 뒤로 가면 갈 수록 선수 명단이 늘어가 2페이지가 될 때가 있다.
그 때를 대비해여 페이지 수가 2개라면 2페이지로 넘어가는 코드를 추가해 준다.
그리고 2페이지로 넘기고 그대로 두면 다음 페이지로 넘어갔을 때 그 페이지에 2페이지가 없다면 흰 화면이 나오기 때문에 다시 1페이지로 되돌리고 다음 연도, 팀으로 넘어가야 한다.
# 페이지 개수 세기 2
page_count = len(driver.find_elements(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_udpContent > div.record_result > div > a'))
if page_count > 1:
# 2 페이지 클릭
driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ucPager_btnNo2').click()
time.sleep(.2)
# 1 페이지로 되돌아가기
driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ucPager_btnNo1').click()
이제 위 코드들을 이용하여 약간의 수정을 거치고 완성했다.
전체 코드
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import pandas as pd
from selenium.webdriver.support.ui import Select
from selenium import webdriver
from bs4 import BeautifulSoup
# 크롬드라이버 실행
driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
# wait until someid is clickable
wait = WebDriverWait(driver, 10)
driver.get('https://www.koreabaseball.com/Record/Player/HitterBasic/Basic1.aspx?sort=HRA_RT')
# 페이지에서 테이블 추출
def create_table(driver):
# 페이지 소스 가져오기
kbo_page = driver.page_source
# html로 파싱
soup = BeautifulSoup(kbo_page, 'html.parser')
# 테이블 추출
table = soup.select_one('#cphContents_cphContents_cphContents_udpContent > div.record_result > table')
# 데이터프레임으로 변환
table = pd.read_html(str(table), flavor='html5lib')[0]
return table
def team_list(driver):
time.sleep(.5)
# 콤보 박스 선택
combobox = driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ddlTeam_ddlTeam')
time.sleep(1)
# 콤보박스 안의 옵션들 선택
options = combobox.find_elements(By.TAG_NAME, 'option')[1:]
# 팀 이름 출력
teams = [option.text for option in options]
return teams
def page_click(driver):
df1 = create_table(driver)
# 페이지 개수 세기 2
page_count = len(driver.find_elements(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_udpContent > div.record_result > div > a'))
if page_count > 1:
# 2 페이지 클릭
driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ucPager_btnNo2').click()
df2 = create_table(driver)
time.sleep(.2)
# 1 페이지로 되돌아가기
driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ucPager_btnNo1').click()
df = pd.concat([df1, df2])
else:
return df1
return df
# 데이터프레임 리스트
dfs = []
# 시즌 리스트
seasons = [str(i) for i in range(1982,2002)]
for season in seasons:
time.sleep(.5)
# 시즌 콤보 박스 선택
season_combo = driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ddlSeason_ddlSeason')
# 시즌 콤보 박스 선택
season_combo = Select(season_combo)
# 값 선택
season_combo.select_by_value(season)
# 팀 목록 불러오기
teams = team_list(driver)
for team in teams:
time.sleep(.5)
combobox = driver.find_element(By.CSS_SELECTOR, '#cphContents_cphContents_cphContents_ddlTeam_ddlTeam')
team_combo = Select(combobox)
team_combo.select_by_visible_text(team)
time.sleep(.5)
df = page_click(driver)
df['year'] = season
dfs.append(df)
기타 오류 사항
여기서 하다보니 계속 element가 없다고 오류가 난다.
이유는 여러가지가 있는데 대충 몇가지를 뽑자면
- 페이지가 로드 되기 전에 코드를 수행했다.
- 페이지가 변해서 다시 코드를 수행해야한다.
이 두가지가 가장 많은거 같다.
그래서 이 해결책은 그냥 일정 시간동안 기다리는 것이다.
중간에 있는 time.sleep()코드가 그 해답이다.
'아무거나 만들어 봄 > kBO 크롤링' 카테고리의 다른 글
KBO 크롤링하기-2 (0) | 2024.05.17 |
---|