Python을 사용하여 농구 마스크 해제

Jan 24 2023
1부: 아름다운 수프로 긁어내기
이 블로그 시리즈에서는 강력한 Python 라이브러리를 활용하여 농구의 숨겨진 통계적 진실을 밝히는 데 도움을 줍니다. 데이터 기반 접근 방식의 첫 번째 단계는 필요한 데이터를 식별하고 수집하는 것입니다.

이 블로그 시리즈에서는 강력한 Python 라이브러리를 활용하여 농구의 숨겨진 통계적 진실을 밝히는 데 도움을 줍니다. 데이터 기반 접근 방식의 첫 번째 단계는 필요한 데이터를 식별하고 수집하는 것입니다.

운 좋게도 Basketball-Reference.com 은 쉽게 스크랩할 수 있는 농구 데이터 페이지를 호스팅합니다. 이 연습의 프로세스는 여러 페이지에 쉽게 적용할 수 있지만 이 경우 여러 신인 클래스의 계절 통계를 스크랩할 계획입니다.

목표

  1. 데이터 소스 식별
  2. 페이지 다운로드
  3. 중요한 페이지 요소 식별
  4. 사전 세척 및 추출
  5. 보관소
  • 요청 패키지
  • 아름다운 수프 패키지
  • 팬더 패키지

데이터 소스 식별

Basketball-Reference.com은 포인트, 리바운드, 슈팅 비율과 같은 일반적인 박스 스코어 범주의 시즌 평균에서 마지막으로 진행된 각 게임의 플레이 바이 플레이 액션에 이르기까지 다양한 농구 통계에 대한 수백 개의 선별된 페이지를 호스팅합니다. 20년 남짓. 정확히 무엇을 찾아야 하는지에 대한 명확한 목표가 설정되지 않은 경우 이 통계적 쓰나미에서 쉽게 길을 잃을 수 있습니다.

이 게시물의 목표는 간단합니다. 어린 선수의 진정한 가치와 잠재력을 평가하는 데 도움이 되는 신인 데이터를 얻는 것입니다.

다음 링크는 그러한 페이지 중 하나입니다. 특정 시즌의 신인 관련 통계를 모두 나열합니다.

https://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html

선수에 대한 확실한 통계적 추론을 할 수 있는 충분한 데이터를 축적하기 위해서는 1년의 데이터가 부족합니다. 소음을 걸러내고 플레이어의 미래 잠재력에 대한 결론에 도달하는 데 도움이 되도록 수집된 수십 년 분량의 데이터가 필요합니다.

작업을 수동으로 반복할 수 있는 경우 자동화를 위한 훌륭한 후보가 됩니다. 이 경우 위 URL의 숫자는 해당 신인 클래스의 해당 연도에 해당합니다. 그 지식을 바탕으로 첫 번째 코드 라인을 구성해 보겠습니다.

import requests
from bs4 import BeautifulSoup
import pandas as pd

years = list(range(1990, 2017))
url_base = "https://www.basketball-reference.com/leagues/NBA_{}_rookies-season-stats.html"

  1. 적절한 패키지를 가져옵니다.
  2. 목록 years변수는 원하는 연도의 범위를 지정합니다(1990년에서 2017년까지).
  3. url_base대상 URL의 미리 형식이 지정된 문자열 변수를 저장하는 역할을 합니다.

웹 페이지 스크래핑에서 가능한 한 많은 오버헤드를 제거하는 것이 필수적입니다. 사이트가 HTML 프런트 엔드에 모든 정보를 저장하므로 페이지 전체를 쉽게 다운로드하고 로컬에 저장할 수 있습니다.

#loop iterates through years
for year in years:
    url = url_base.format(year)
    data = requests.get(url)
    #each page year is locally stored as an HTML file
    with open("notebooks/Rookies/{}.html".format(year), "w+") as f:
        f.write(data. Text)

  1. 의 문자열 내에 있는 중괄호는 가 현재 반복되는 연도로 대체할 url수 있도록 합니다 . 예를 들어, 첫 번째 반복에서 값은 다음과 같습니다.format
    urlhttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
    — 두 번째 반복에서 다음 연도가 대신 참조됩니다.https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html
  2. 데이터 변수는 requests.get()현재 반복되는 url문자열 값 의 함수 및 참조에 대한 자리 표시자 역할을 합니다.
  3. 그런 다음 메서드 requests는 새로 형식이 지정된 URL 문자열을 사용하여 해당 페이지를 검색합니다.
  4. 후속 작업은 우리 ( ) 에서 페이지 데이터를 with open()읽고 쓰고 ( ) 새로 생성된 HTML 파일을 로컬에 저장합니다.w+requests.getdata.text

사이트 스크래핑의 일반적인 성장통을 피하기 위해 이러한 페이지를 로컬 HTML 파일로 저장합니다. 페이지 사이트를 방문할 때 해당 페이지를 호스팅하는 서버는 귀하의 요청을 존중하고 적절한 데이터를 귀하의 브라우저로 다시 보냅니다. 그러나 동일한 정보를 반복해서 요청하는 특정 클라이언트가 있으면 서버에 과도한 부담이 가해집니다. 서버 관리자는 이 서비스를 온라인에서 다른 사람에게 최적으로 제공할 수 있도록 이러한 지속적인 요청을 차단할 수 있는 권한이 있습니다.

로컬 컴퓨터에 이러한 HTML 파일을 다운로드하면 다음 두 가지를 방지할 수 있습니다.

  1. 동일한 데이터를 수집하기 위해 평소보다 더 오래 기다려야 함
  2. 페이지 방문이 차단되어 데이터 수집이 완전히 중단됨

HTML 형식에서 데이터를 스크랩하는 데는 몇 가지 이점이 있습니다. 가장 중요한 것 중 하나는 요소 속성을 고유하게 식별하는 방법입니다. 고유하게 식별할 수 있는 경우 Beautiful Soup을 사용하여 스크랩할 수 있습니다. 그러나 그렇게 하려면 이러한 HTML 요소 자체를 검사하는 방법을 알아야 합니다.

검사 방법

우리는 이 문서의 내부 작업에 대해 더 깊이 파고들 필요가 있지만, 나는 이것을 HTML 학습에 대한 연습으로 만들지 않을 것이라고 약속합니다.

HTML 개체를 검사하는 방법을 알고 있다면 자유롭게 건너뛰십시오. 그렇지 않으면 페이지 요소를 검사하는 방법을 따르십시오.

  1. 크롬 상단 메뉴바에 있는 세 개의 수직 점을 클릭합니다.
  2. "추가 도구"를 선택하십시오.
  3. 개발자 도구를 선택합니다.
  1. 웹 페이지를 마우스 오른쪽 버튼으로 클릭
  2. 개발자 도구 패널에 액세스하려면 "검사"를 선택하십시오.

페이지 검사

이러한 모든 페이지가 로컬에 저장되어 있으므로 파일 시스템으로 이동하여 원하는 브라우저에서 열거나 다음 코드 스니펫을 구현하여 코드를 계속 빌드하여 페이지를 보도록 선택할 수 있습니다.

with open("notebooks/Rookies/2000.html") as f:
    page = f.read()

이 페이지의 원하는 모든 데이터는 해당 테이블 요소에 보관됩니다. 이 모든 데이터를 있는 그대로 성급하게 빨아들이기 전에 지금이 이 테이블의 모든 것이 수집할 가치가 있는지 고려할 가장 좋은 시기입니다.

사전 청소

사전 청소는 어휘에서 자주 사용되지 않는 단어일 수 있지만 정기적으로 데이터를 스크랩하는 자신을 보는 사용자에게는 그렇게 해야 합니다 . 데이터 수집 프로젝트 진행 시간 낭비로 인한 좌절을 피하려면 먼저 밀에서 왕겨를 분리하는 것이 가장 좋습니다.

예를 들어 빨간색으로 표시된 세 가지 요소를 기록해 두십시오.

한 행은 "기본" 테이블 헤더 역할을 합니다. 다른 두 행은 맨 위에 있는 동일한 아티팩트의 중복 인스턴스입니다. 이 패턴은 20행마다 반복됩니다.

추가 검사를 통해 이러한 모든 행에 동일한 tr(테이블 행) HTML 태그가 있음을 알 수 있습니다. 이러한 각 요소를 다른 요소와 구별하는 것은 클래스 이름입니다.

  1. 기본 헤더 행
    — 클래스 =over_header
  2. 헤더 행 반복
    — 클래스 =over_header thead
  3. 통계 범주 행
    — 클래스 =thead
  4. #array to house list of dataframes
    dfs = []
    #unnecessary table rows to be removed
    classes = ["over_header", "over_header thead", "thead"]
    

  5. 배열 classes개체는 unwarted 테이블 행 요소의 클래스 이름을 모두 보유합니다.

이 decompose메서드는 페이지에서 이러한 원치 않는 요소를 제거하는 역할을 합니다. Beautiful Soup 공식 문서에 따르면 ...

decompose()

Tag.decompose()트리에서 태그를 제거한 다음 해당 태그와 그 내용을 완전히 파괴합니다.

decompose아래는 루프 의 도움으로 메서드를 최적화할 수 있었던 코드 스니펫입니다 for.

#for loop to iterate through the years 
for year in years:
    with open("notebooks/Rookies/{}.html".format(year)) as f:
        page = f.read()
    soup = BeautifulSoup(page, "html.parser")
    #for loop cleans up unnecessary table headers from reappearing in rows
    for i in classes:
        for tr in soup.find_all("tr", {"class":i}):
            tr.decompose()

  1. 이 메서드는 변수가 호출될 때 로컬에 저장된 HTML 파일을 읽을 수 with있는 구조를 코드에 제공합니다.page
  2. HTML 파서 클래스는 Beautiful Soup 클래스를 인스턴스화하고 page문자열 객체와 html.parser.
  3. 두 번째 루프는 배열 for의 값을 반복합니다.classes
  4. 세 번째 for루프는 Beautiful Soup의 방법을 활용하여 태그 와 클래스 이름이 일치하는 find_all요소를 식별합니다. trclasses
  5. tr.decompose페이지 전체에서 식별된 각 테이블 행 요소를 생략하는 역할을 합니다.

데이터 추출

마침내 테이블에서 실제로 데이터를 추출하는 코드 부분에서 작업을 시작할 수 있습니다. 모든 관련 데이터가 포함된 테이블에는 HTML 고유 ID가 있음을 기억하십시오 rookies. 코드에 대한 다음 추가 사항은 이 테이블의 데이터를 구문 분석하는 데 사용됩니다.

#the years we wish to parse for
years = list(range(1990, 2017))
#array to house list of dataframes
dfs = []
#unnecessary table headers to be removed
classes = ["over_header","over_header thead", "thead"]
 
for year in years:
    #url = url_base.format(year)
    #data = requests.get(url)
    ##page is saved as an html and placed in Rookies folder
    #with open("notebooks/Rookies/{}.html".format(year), "w+") as f:
        #f.write(data.text)
     #with open("notebooks/Rookies/2000.html") as f:
    with open("notebooks/Rookies/{}.html".format(year)) as f:
        page = f.read()
      soup = BeautifulSoup(page, "html.parser")   #for loop cleans up unnecessary table headers from reappearing in rows
    for i in classes:
        for tr in soup.find_all("tr", {"class":i}):
        tr.decompose()
        
### Scraping Block ###
    #identifies, scrapes, and loads rookie tables into one dataframe
    rookie_table = soup.find(id="rookies")
    rookies = pd.read_html(str(rookie_table))[0]
    rookies["Year"] = year
    dfs.append(rookies)

#new variable turns list of dataframes into single dataframe
all_rookies = pd.concat(dfs)

  1. rookie_tableBeautiful Soup의 find방법을 인스턴스화하여 ID로 HTML 요소를 식별합니다.rookies
  2. Pandas가 HTML 테이블을 읽을 수 있음을 확인하면 루키 테이블이 메서드를 사용하여 Pandas에 로드되고 문자열로 read_html전달됩니다.rookie_table
  3. 데이터 프레임 목록에서 단일 데이터 프레임으로 [0]전환을 끝내기 위해 고정rookies
  4. rookies"연도" 열은 데이터 프레임 의 끝입니다.
  5. dfs.append(rookies)데이터 프레임 목록으로 반복되는 순서대로 모든 신인 연도의 모든 테이블을 수용하는 역할을 합니다.
  6. Pandas 메서드는 concat데이터 프레임 목록을 하나의 단일 데이터 프레임으로 결합하는 데 사용됩니다.all_rookies

우리의 마지막 단계는 이 모든 유용하고 깨끗한 정보를 가져와 사람과 기계가 쉽게 읽을 수 있는 CSV 형식으로 보관하는 것입니다. 이 줄을 코드 끝에 추가하면 다시 돌아와서 수집된 데이터를 참조하기로 결정할 때 유용할 것입니다.

#a single dataframe archived as a local CSV using Pandas
all_rookies.to_csv("archive/NBA_Rookies_1990-2016.csv")

import requests
import pandas as pd
from bs4 import BeautifulSoup

years = list(range(1990, 2017))
dfs = []
classes = ["over_header","over_header thead", "thead"] #unnecessary table headers to be removed

for year in years:
    with open("C:/Users/cisco/OneDrive/python/Projects/Basketball/Rookies/{}.html".format(year), encoding = "utf-8") as f:
        page = f.read()
    soup = BeautifulSoup(page, "html.parser")
    #second for loop cleans up unnecessary table headers from reappearing in rows
    for i in classes:
        for tr in soup.find_all("tr", {"class":i}):
            tr.decompose()
    #identifies, scrapes, and loads rookie tables into one dataframe
    table_rookies = soup.find(id="rookies")
    rookies = pd.read_html(str(table_rookies))[0]
    rookies["Year"] = year
    dfs.append(rookies)

#new variable turns list of dataframes into single dataframe
all_rookies = pd.concat(dfs)

#dataframe archived as local CSV
all_rookies.to_csv("archive/NBA_Rookies_1990-2016.csv")

다시 말하지만, 이 연습에서 따르는 프로세스는 의심할 여지없이 Basketball-Reference.com 의 다른 모든 페이지에 적용됩니다 . 각 경우에 수행할 가치가 있는 5가지 간단한 단계가 있습니다.

  1. 페이지 URL 식별
  2. 페이지 다운로드
  3. 요소 식별
  4. 사전 세척 및 추출
  5. 보관소

이 시리즈의 다음 단계에서는 실제로 이 데이터를 사용하여 미래 플레이어 잠재력에 대한 통찰력을 얻을 것입니다. 따라서 향후 분할을 경계하십시오!