본문 바로가기
Minding's Programming/Crawling

[Python/Selenium] (업데이트)Selenium으로 KBO 경기 일정 크롤링하기

by Minding 2024. 7. 9.
728x90
반응형

2024.07.01 - [Minding's Programming/Knowledge] - [Python/Selenium] Selenium으로 KBO 경기 일정 크롤링하기

 

[Python/Selenium] Selenium으로 KBO 경기 일정 크롤링하기

야구장 소개 홈페이지를 만드는 데 경기 일정도 한 페이지에 보여주는 곳이 있으면 좋겠다고 생각이 들었다. KBO 홈페이지에 있는 경기 일정을 주기적으로 크롤링해 이 홈페이지에 노출시키고자

minding-deep-learning.tistory.com

 

이 글은 위 링크의 크롤링 코드를 보완한 코드와 함께 어떻게 보완했는지에 대한 설명을 위한 글이다.

 

전체 코드

 

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select
import pandas as pd

class GameCalCrawler:

    url = "https://www.koreabaseball.com/Schedule/Schedule.aspx"

    def crawling(self, month):
        # selenium 업데이트로 인해 이제 크롬드라이버가 필요없다!
        options = Options()

        options.add_argument("--headless")
        options.add_argument('--no-sandbox')

        driver = webdriver.Chrome(options=options)
        driver.get(self.url)

        select = Select(driver.find_element(By.ID, "ddlMonth"))
        select.select_by_value(month)
        table = driver.find_element(By.CLASS_NAME, "tbl-type06")
        thead = table.find_element(By.TAG_NAME, "thead") # 테이블 칼럼 부분
        header = thead.text.split() # df에 칼럼으로 쓸 부분 미리 빼놓음
        tbody = table.find_element(By.TAG_NAME, "tbody") # 경기 일정이 있는 부분
        rows = tbody.find_elements(By.TAG_NAME, "tr") # 각 라인 별 데이터 추출
        if len(rows) == 1:
            return '0'

        lines = []
        for value in rows: # 라인 별로 반복문 돌며 데이터 추출
            body = value.find_elements(By.TAG_NAME, "td")
            schedule_list = []
            for b in body:
                schedule_list.append(b.text) # 칼럼 별 데이터들 라인 별로 리스트에 저장
            lines.append(schedule_list) # 각 라인 별 데이터를 한번 더 리스트에 저장

        # 날짜 데이터가 없는 라인을 위해 데이터 핸들링
        data = []
        game_day = None

        for line in lines:
            if line[0].endswith(')'): # 날짜 칼럼 데이터 위치에 ')'로 끝날 경우 날짜 데이터가 있다고 판단
                game_day = line[0] # 해당 날짜 변수에 저장 후 data list에 바로 추가
                data.append(line)
            else:
                line.insert(0, game_day) # 날짜 데이터가 없을 경우 위에서 저장한 날짜 데이터 첫 번째 순서에 추가
                data.append(line)

        df = pd.DataFrame(data, columns=header) # json으로 저장해주기 위해 데이터프레임 생성
        df = df.replace('','-')
        df.to_json('./app/game_schedule/{0}m_calender.json'.format(month), force_ascii = False, orient='records', indent=4)

        driver.quit()

        return '1'

if __name__ == "__main__":
    crawler = GameCalCrawler()
    df = crawler.crawling('07')
    # print(df)

 

이전 코드와 달라진 점을 기반으로 본다고 하면,

# 기존코드
schedule = table.text

lines = schedule.strip().split('\n')
if lines[1] == '데이터가 없습니다.': # 해당 월 경기 데이터가 없을 경우
return '0'
header = lines[0].split()
rows = []

for line in lines[1:]:
if line.split()[0].endswith(')'): # '날짜' 칼럼 데이터 위치에 ')'로 끝날경우 날짜 데이터로 판단
date = line.split(' ')[0] # 해당 날짜 저장(아래 데이터에 추가 위함)
rows.append(line.split(' '))
else:
temp = line.split() # 임시 리스트에 저장한 뒤
temp.insert(0, date) # 위에서 저장한 날짜 데이터 맨 앞에 추가
rows.append(temp)

rows.insert(0, header) # 칼럼으로 지정해줄 데이터를 rows 리스트의 맨 앞에 넣어줌
# 보완코드
thead = table.find_element(By.TAG_NAME, "thead") # 테이블 칼럼 부분
header = thead.text.split() # df에 칼럼으로 쓸 부분 미리 빼놓음
tbody = table.find_element(By.TAG_NAME, "tbody") # 경기 일정이 있는 부분
rows = tbody.find_elements(By.TAG_NAME, "tr") # 각 라인 별 데이터 추출
if len(rows) == 1:
return '0'

lines = []
for value in rows: # 라인 별로 반복문 돌며 데이터 추출
body = value.find_elements(By.TAG_NAME, "td")
schedule_list = []
for b in body:
schedule_list.append(b.text) # 칼럼 별 데이터들 라인 별로 리스트에 저장
lines.append(schedule_list) # 각 라인 별 데이터를 한번 더 리스트에 저장

이전 코드에서는 table에 있는 text 전체를 크롤링해 각 행으로 나눠준 뒤 list에 저장해 주는 방식이었다. 하지만 이 경우 공백을 기준으로 데이터를 split하기 때문에, 데이터가 비워져 있는 경우(공백인 경우)의 데이터가 누락되어 추후 데이터프레임을 만들 때 핸들링을 한번 더 해줘야 했었다.

 

보완한 코드에서는 각 table의 칼럼, 데이터가 있는 부분을 thead와 tbody로 지정해주고, tbody의 경우 tr 태그 안에 있는 td 태그안에 있는 값까지 크롤링해 공백이 있는 부분까지 문제없도록 크롤링할 수 있었다. (공백 또한 공백 그 자체의 데이터로 크롤링 가능)

 

# 기존 코드
df = pd.DataFrame(rows)
df = df.rename(columns=df.iloc[0]) # 첫 번째 행을 칼럼명으로 지정
df = df.drop(df.index[0])
if df['하이라이트'][1] != '하이라이트':
df['라디오'] = df['TV']
df['TV'] = df['하이라이트']
df = df.fillna('-')
df = df.drop(['게임센터', '하이라이트', '구장',], axis=1)
df.rename(columns = {"라디오":"구장"}, inplace=True)
# 날짜 데이터가 없는 라인을 위해 데이터 핸들링
data = []
game_day = None

for line in lines:
if line[0].endswith(')'): # 날짜 칼럼 데이터 위치에 ')'로 끝날 경우 날짜 데이터가 있다고 판단
game_day = line[0] # 해당 날짜 변수에 저장 후 data list에 바로 추가
data.append(line)
else:
line.insert(0, game_day) # 날짜 데이터가 없을 경우 위에서 저장한 날짜 데이터 첫 번째 순서에 추가
data.append(line)

df = pd.DataFrame(data, columns=header) # json으로 저장해주기 위해 데이터프레임 생성
df = df.replace('','-')
df.to_json('./app/game_schedule/{0}m_calender.json'.format(month), force_ascii = False, orient='records', indent=4)

따라서, 기존 코드에서는 df를 만들어준 뒤 한번 더 핸들링을 거쳤어야 했지만, 보완코드의 경우 날짜 데이터 핸들링만 해준 뒤 그대로 df를 만들어주면 된다. 또한 시인성을 위해 공백문자(' ')는 '-'로 변환해줬다.

 

 

728x90
반응형

댓글