ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스포츠 데이터 파이썬 크롤링 -Selenium, By.CSS_SELECTOR, pickle
    데이터 분석 공부/파이썬 데이터 분석 2024. 4. 6. 21:10
    728x90

    셀레니움(Selenium)과 뷰티풀수프(BeautifulSoup)를 활용해서 스포츠 선수들의 정보를 크롤링 해 보겠습니다. 

     

    우선 몇 가지 함께 알아두면 재밌는 정보가 있어서 함께 정리해 둡니다. 


    #crontab -e

    리눅스와 유닉스 기반 시스템에서 주기적으로 작업을 실행하기 위해 사용되는 명령어입니다. 'cron'은 리눅스 시스템에서 스케줄링 작업을 관리하는 시스템이며, crontab 명령어를 사용하여 사용자의 cron 작업을 관리할 수 있습니다.

     

    crontab -e 명령어는 현재 사용자의 cron 작업을 편집하기 위해 텍스트 에디터를 열어줍니다. 사용자가 이 명령어를 실행하면 텍스트 에디터가 열리고, 해당 사용자의 cron 작업을 정의하는 텍스트 파일이 표시됩니다. 사용자는 이 파일에 주기적으로 실행하길 원하는 명령어를 추가하고, 저장한 후 에디터를 닫으면 해당 cron 작업이 스케줄에 추가됩니다.

     

    cron 작업은 시간, 날짜 및 실행 주기를 지정하여 실행되며, 예를 들어 매일, 매주, 매월, 또는 특정 시간에 실행되도록 할 수 있습니다. 이를 통해 자동으로 시스템 유지보수, 백업, 데이터 처리 등을 수행할 수 있습니다.

     

    # kafka

    자료구조 중 que의 일종입니다. 
    지난 포스트에서 종목정보 가져오기 위해 naver와 KRX에 명령어를 보냈고 정상적으로 정보가 가져와졌는데요.

    그걸 요청한 주문서를 kafka
    리스트에 이름 쓰는 거 = buffer 버퍼 

     

    # IP

    - private ip : 192로 시작하는 ip 주소. 내부 네트워크에서 쓰는 주소. 
    공유기나 내부 네트워크를 사용해서 인터넷에 접속할 경우 사설 IP(Private IP)라고 하는 특정 주소 범위(192.168.0.1 ~ 192.168.255.254)가 내부적으로 사용되며, 이걸 알려줘도 외부에서 내 컴퓨터 찾을 수 없음. public ip를 줘야 알 수 있음. 

     

    - public ip : 외부에서 접속할 때 필요한 고유한 주소.  
    공유기의 포트 포워딩을 통해 public ip 포워딩해주면 원격으로 사용 가능

    ip a 
    : ip 목록 보여주는 명령어 

     

    ping 8.8.8.8 
    ping -c 3 8.8.8.8 (3개만 찍어줘) 
    : 네트워크 작업 시, 상대방 컴퓨터 네트워크가 살아있는지 확인하는 명령어
    : ms(밀리세컨드) 숫자가 작을 수록 좋음 
    : 8.8.8.8 은 구글의 DNS 서버 

     

    # 네트워크 환경 체크

    # 네트워크 프로그램 설치

    (sudo) apt install net-tools

     

    # 현재 사용중인 포트 확인 가능. 안 쓰는 포트는 닫아두어야 해킹으로부터 안전.
    netstat -ntlp 

     

    # 현재 주피터 노트북이 켜져 있는지 확인하는 코드. 내가 알고 싶은 포트(8888)이 있는지 검색(grep) 해서 | (파이프) 로 명령어 전달

    netstat -ntlp | grep 8888[포트넘버] 

     

     

     

    자 이제 본격적으로 셀레니움을 활용하기 위한 환경 설정부터 해 보겠습니다. 

    # wget : 인터넷에 있는 파일을 다운로드 받는 명령어 

    향후 스파크, 하둡 등을 다운로드 받을 때 이 명령어를 쓰면 됩니다.

    셀레니움을 실행했을 때 크롬을 열 수 있어야 하기 때문에, 

    크롬 파일을 환경에 다운로드 받는 아래 명령어를 실행할게요. 

    # 크롬 설치 파일 다운로드
    wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
    
    # 크롬 설치 
    (sudo) apt install ./google-chrome-stable_current_amd64.deb -y

     

    크롬 다운로드를 완료했다면 이제 주피터 노트북 환경에서 셀레니움을 실행해볼게요.

    driver_version 다음에는 실행하시는 당시의 환경의 크롬 버전을 확인한 다음 해당 버전을 넣어주셔야 합니다.

    # 셀레니움 실행하기 
    google-chrome --version  # 현재 설치된 크롬 버전 확인
    
    from selenium import webdriver
    from webdriver_manager.chrome import ChromeDriverManager
    from selenium.webdriver.chrome.service import Service
    driver = webdriver.Chrome(service=Service(ChromeDriverManager(driver_version="123.0.6312.105").install()))

     

     

    # 원하는 영역의 css 정보 복사하기
    F12 (개발자도구) > 선택 원하는 영역 찾아서 우클릭 > copy > copy selector 하면 #lnb > li:nth-child(3) > a 복사됨
    이런 정보를 클릭할 건지 (click) , 텍스트를 넣을 건지 (send) 등 코딩하여 자동화가 가능합니다.

     

    기존에 우분투 서버에서 설치한 주피터 노트북에서 오류가 발생해 오류 원인 확인, 차선책으로 미니콘다 설치를 했습니다.

    더보기

    오류 원인을 확인해 보았는데 


    ## 오류 발생, 크롬 설치 불가
    # 실행되고 있는 실행파일의 위치 확인 : which 
    which google-chrome # 터미널에 입력하여 구글 크롬 설치 파일 위치 확인
    cd /opt/google/ # 들어가서 실행파일 제대로 있는지 확인 

    ## 미니콘다 설치 및 콘다로 가상환경 만들기
    conda create --name[이름] --python[버전]
    ex) conda create --name encore_py39 python=3.9
    conda activate encore_py39 # conda 가상환경 실행하기

    conda install jupyter  # 콘다 명령어로 주피터 설치해보기 
    pip install selenium webdriver_manager 

     

    이제 진짜로 크롤링 코드를 시작합니다...! 

    우선 선수 고유번호를 가져오기 위해 KBO 홈페이지 내 팀명 선택, 페이지 클릭해 선수 정보를 수집하는 코드를 작성합니다.

    # URL 열기
    driver.get("https://www.koreabaseball.com/")
    
    from selenium.webdriver.common.by import By
    driver.find_element(By.CSS_SELECTOR, "#lnb > li:nth-child(3) > a").click()
    driver.page_source
    driver.find_element(By.CSS_SELECTOR, "#cphContents_cphContents_cphContents_ddlTeam > option:nth-child(3)").click()

     

     

    위 코드에서는 By.CSS_SELECTOR 를 사용했지만, x_path 등 다른 방식으로도 element를 찾을 수 있습니다. 

     

    import time
    import re
    pattern = re.compile("playerId=([0-9]+)")
    
    select_page = "#cphContents_cphContents_cphContents_ucPager_btnNo{}"
    select_team = "#cphContents_cphContents_cphContents_ddlTeam > option:nth-child({})"
    playid = []
    
    for x in range(2,12):
        for_1 = select_team.format(x)
        driver.find_element(By.CSS_SELECTOR, for_1).click()
        time.sleep(2)
        #playid.extend(pattern.findall(driver.page_source))
        for y in range(1,6):
            f2 = select_page.format(y)
            try:
                driver.find_element(By.CSS_SELECTOR, f2).click()
                time.sleep(1)
                playid.extend(pattern.findall(driver.page_source))
            except Exception as e:
                print ("page 없음 ")
            time.sleep(2)

     

    KBO의 팀 정보를 확인했을 때, 각 CSS 코드 내 번호가 3번부터 11번까지여서 range는 (2,12)로 설정했습니다.

    select_page, select_team 에 대한 css 코드를 미리 작성해두고 for문을 돌며 정보를 가져오게 합니다.

    끝 페이지까지 모두 수집되면 프린트문과 함께 코드 실행이 종료되도록 했습니다. 

     

    import pickle 
    # binary save , load 
    with open("./kbo.pkl", "wb") as f:
        pickle.dump(playid, f)
        
    import pickle
    with open("./kbo.pkl", "rb") as f:
        abc = pickle.load(f)

     

    피클 형식으로 저장한 이유는 csv, txt 파일 등에 비해 파이썬 환경에서 매우 효율적이고 빠르게 파일을 저장, 불러올 수 있는 형식이기 때문입니다.

     

    자세한 피클 형식의 이점은 아래에 기재해 둘게요.

     

    더보기
    1. 유연성: pickle은 Python 객체의 직렬화에 특화되어 있습니다. 이는 거의 모든 종류의 Python 객체를 저장하고 복원할 수 있음을 의미합니다. 이는 리스트, 딕셔너리, 클래스 인스턴스 등과 같은 복잡한 데이터 구조를 손쉽게 저장하고 다시 로드할 수 있음을 의미합니다.

    2. 속도: pickle은 데이터를 이진 형식으로 직렬화하기 때문에 일반적으로 텍스트 파일 형식보다 빠르고 효율적입니다. 이는 특히 큰 데이터 세트의 경우에 더욱 두드러집니다.

    3. 파이썬 전용: pickle은 Python에 특화된 형식이기 때문에 Python 프로그램 간에 데이터를 공유할 때 유용합니다. 다른 프로그래밍 언어와 호환되는 CSV나 JSON과 달리, pickle은 Python에서만 사용할 수 있습니다.

    4. 구조 보존: pickle은 객체의 구조를 완벽하게 보존합니다. 이는 객체의 계층 구조, 참조, 메소드 등을 유지하며 객체를 저장하고 로드할 수 있다는 것을 의미합니다. 이는 데이터를 다시 로드할 때 데이터의 일관성과 유효성을 보장합니다.

     

    from bs4 import BeautifulSoup as BS
    import requests
    import os
    import re 
    
    # 선수 고유번호를 넣어 각 URL에 접속해 텍스트 정보 수집
    play_url = "https://www.koreabaseball.com/Record/Player/PitcherDetail/Basic.aspx?playerId={}"
    for x in playid:
        print(play_url.format(x))
        kbo_r = requests.get(play_url.format(x))

     

    선수 고유번호를 playid에 모두 수집했으니, 이를 활용해 각 선수들의 프로필 URL에 접속해 정보를 크롤링 할게요. 

     

    # 위에서 수집한 텍스트들 중 원하는 부분을 찾기 (개인 프로필)
    # for문 돌며 마지막 선수에 대한 정보를 테스트로 출력
    bs = BS(kbo_r.text)
    for x in bs.find("div", class_= "player_basic").findAll("li"):
        y=  x.text.split(":")
        y[1] = x.text.split(":")[1].strip()
        print(y)
        
    # dict comprehension 
    # 구단 정보도 별도로 추가하여 df의 신규 컬럼으로 생성
    data = {x.text.split(":")[0] : x.text.split(":")[1] for x in bs.find("div", class_= "player_basic").findAll("li")}
    data['team'] = bs.find("h4", id="h4Team").text
    data

     

    개발자 도구를 활용해, 원하는 정보가 들어있는 곳의 객체와 class 등을 확인해요. 

    <div class="player_basic"> 이 제가 원하는 정보들이 들어있는 영역이고,

    텍스트 데이터들은 <li class> 로 구성되어 있는 걸 확인하여 findAll 메서드로 x 라는 변수에 넣어줍니다.

    이 x를 살펴보면 선수명: 강민호 이런 식으로 : 을 통해 데이터를 구분지을 수 있어요. 

    : 를 기준으로 텍스트를 split 해 주고 y 값에 넣어줄게요. 

     

    아래는 사용자 정의 함수로 똑같이 구현해 본 코드입니다. 

    # 사용자 정의 함수로도 똑같이 구현
    
    def get_kbo(id_):
        kbo_r = requests.get(play_url.format(id_))
        bs = BS(kbo_r.text)
        data = {x.text.split(":")[0] : x.text.split(":")[1] for x in bs.find("div", class_= "player_basic").findAll("li")}
        data['team'] = bs.find("h4", id="h4Team").text
        return data

     

     

     

    댓글

Designed by Tistory.