Python을 사용하여 농구 마스크 해제
이 블로그 시리즈에서는 강력한 Python 라이브러리를 활용하여 농구의 숨겨진 통계적 진실을 밝히는 데 도움을 줍니다. 데이터 기반 접근 방식의 첫 번째 단계는 필요한 데이터를 식별하고 수집하는 것입니다.
운 좋게도 Basketball-Reference.com 은 쉽게 스크랩할 수 있는 농구 데이터 페이지를 호스팅합니다. 이 연습의 프로세스는 여러 페이지에 쉽게 적용할 수 있지만 이 경우 여러 신인 클래스의 계절 통계를 스크랩할 계획입니다.
목표
- 데이터 소스 식별
- 페이지 다운로드
- 중요한 페이지 요소 식별
- 사전 세척 및 추출
- 보관소
- 요청 패키지
- 아름다운 수프 패키지
- 팬더 패키지
데이터 소스 식별
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"
- 적절한 패키지를 가져옵니다.
- 목록
years변수는 원하는 연도의 범위를 지정합니다(1990년에서 2017년까지). 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)
- 의 문자열 내에 있는 중괄호는 가 현재 반복되는 연도로 대체할
url수 있도록 합니다 . 예를 들어, 첫 번째 반복에서 값은 다음과 같습니다.formaturlhttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
— 두 번째 반복에서 다음 연도가 대신 참조됩니다.https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html - 데이터 변수는
requests.get()현재 반복되는url문자열 값 의 함수 및 참조에 대한 자리 표시자 역할을 합니다. - 그런 다음 메서드
requests는 새로 형식이 지정된 URL 문자열을 사용하여 해당 페이지를 검색합니다. - 후속 작업은 우리 ( ) 에서 페이지 데이터를
with open()읽고 쓰고 ( ) 새로 생성된 HTML 파일을 로컬에 저장합니다.w+requests.getdata.text
사이트 스크래핑의 일반적인 성장통을 피하기 위해 이러한 페이지를 로컬 HTML 파일로 저장합니다. 페이지 사이트를 방문할 때 해당 페이지를 호스팅하는 서버는 귀하의 요청을 존중하고 적절한 데이터를 귀하의 브라우저로 다시 보냅니다. 그러나 동일한 정보를 반복해서 요청하는 특정 클라이언트가 있으면 서버에 과도한 부담이 가해집니다. 서버 관리자는 이 서비스를 온라인에서 다른 사람에게 최적으로 제공할 수 있도록 이러한 지속적인 요청을 차단할 수 있는 권한이 있습니다.
로컬 컴퓨터에 이러한 HTML 파일을 다운로드하면 다음 두 가지를 방지할 수 있습니다.
- 동일한 데이터를 수집하기 위해 평소보다 더 오래 기다려야 함
- 페이지 방문이 차단되어 데이터 수집이 완전히 중단됨
HTML 형식에서 데이터를 스크랩하는 데는 몇 가지 이점이 있습니다. 가장 중요한 것 중 하나는 요소 속성을 고유하게 식별하는 방법입니다. 고유하게 식별할 수 있는 경우 Beautiful Soup을 사용하여 스크랩할 수 있습니다. 그러나 그렇게 하려면 이러한 HTML 요소 자체를 검사하는 방법을 알아야 합니다.
검사 방법
우리는 이 문서의 내부 작업에 대해 더 깊이 파고들 필요가 있지만, 나는 이것을 HTML 학습에 대한 연습으로 만들지 않을 것이라고 약속합니다.
HTML 개체를 검사하는 방법을 알고 있다면 자유롭게 건너뛰십시오. 그렇지 않으면 페이지 요소를 검사하는 방법을 따르십시오.
- 크롬 상단 메뉴바에 있는 세 개의 수직 점을 클릭합니다.
- "추가 도구"를 선택하십시오.
- 개발자 도구를 선택합니다.
- 웹 페이지를 마우스 오른쪽 버튼으로 클릭
- 개발자 도구 패널에 액세스하려면 "검사"를 선택하십시오.
페이지 검사
이러한 모든 페이지가 로컬에 저장되어 있으므로 파일 시스템으로 이동하여 원하는 브라우저에서 열거나 다음 코드 스니펫을 구현하여 코드를 계속 빌드하여 페이지를 보도록 선택할 수 있습니다.
with open("notebooks/Rookies/2000.html") as f:
page = f.read()
이 페이지의 원하는 모든 데이터는 해당 테이블 요소에 보관됩니다. 이 모든 데이터를 있는 그대로 성급하게 빨아들이기 전에 지금이 이 테이블의 모든 것이 수집할 가치가 있는지 고려할 가장 좋은 시기입니다.
사전 청소
사전 청소는 어휘에서 자주 사용되지 않는 단어일 수 있지만 정기적으로 데이터를 스크랩하는 자신을 보는 사용자에게는 그렇게 해야 합니다 . 데이터 수집 프로젝트 진행 시간 낭비로 인한 좌절을 피하려면 먼저 밀에서 왕겨를 분리하는 것이 가장 좋습니다.
예를 들어 빨간색으로 표시된 세 가지 요소를 기록해 두십시오.
한 행은 "기본" 테이블 헤더 역할을 합니다. 다른 두 행은 맨 위에 있는 동일한 아티팩트의 중복 인스턴스입니다. 이 패턴은 20행마다 반복됩니다.
추가 검사를 통해 이러한 모든 행에 동일한 tr(테이블 행) HTML 태그가 있음을 알 수 있습니다. 이러한 각 요소를 다른 요소와 구별하는 것은 클래스 이름입니다.
- 기본 헤더 행
— 클래스 =over_header - 헤더 행 반복
— 클래스 =over_header thead - 통계 범주 행
— 클래스 =thead - 배열
classes개체는 unwarted 테이블 행 요소의 클래스 이름을 모두 보유합니다.
#array to house list of dataframes
dfs = []
#unnecessary table rows to be removed
classes = ["over_header", "over_header thead", "thead"]
이 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()
- 이 메서드는 변수가 호출될 때 로컬에 저장된 HTML 파일을 읽을 수
with있는 구조를 코드에 제공합니다.page - HTML 파서 클래스는 Beautiful Soup 클래스를 인스턴스화하고
page문자열 객체와html.parser. - 두 번째 루프는 배열
for의 값을 반복합니다.classes - 세 번째
for루프는 Beautiful Soup의 방법을 활용하여 태그 와 클래스 이름이 일치하는find_all요소를 식별합니다.trclasses 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)
rookie_tableBeautiful Soup의find방법을 인스턴스화하여 ID로 HTML 요소를 식별합니다.rookies- Pandas가 HTML 테이블을 읽을 수 있음을 확인하면 루키 테이블이 메서드를 사용하여 Pandas에 로드되고 문자열로
read_html전달됩니다.rookie_table - 데이터 프레임 목록에서 단일 데이터 프레임으로
[0]전환을 끝내기 위해 고정rookies rookies"연도" 열은 데이터 프레임 의 끝입니다.dfs.append(rookies)데이터 프레임 목록으로 반복되는 순서대로 모든 신인 연도의 모든 테이블을 수용하는 역할을 합니다.- 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가지 간단한 단계가 있습니다.
- 페이지 URL 식별
- 페이지 다운로드
- 요소 식별
- 사전 세척 및 추출
- 보관소
이 시리즈의 다음 단계에서는 실제로 이 데이터를 사용하여 미래 플레이어 잠재력에 대한 통찰력을 얻을 것입니다. 따라서 향후 분할을 경계하십시오!

![연결된 목록이란 무엇입니까? [1 부]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































