เปิดโปงบาสเก็ตบอลโดยใช้ Python
ในซีรีส์บล็อกนี้ ไลบรารี Python อันทรงพลังถูกนำไปใช้เพื่อช่วยเปิดเผยความจริงทางสถิติที่ซ่อนอยู่ในบาสเก็ตบอล ขั้นตอนแรกในแนวทางที่ขับเคลื่อนด้วยข้อมูลคือการระบุและรวบรวมข้อมูลที่จำเป็น
โชคดีสำหรับเราBasketball-Reference.comโฮสต์หน้าข้อมูลบาสเก็ตบอลที่สามารถคัดลอกได้ง่าย กระบวนการของคำแนะนำนี้สามารถนำไปใช้กับหน้าเว็บจำนวนเท่าใดก็ได้ แต่สำหรับกรณีนี้ เราวางแผนที่จะรวบรวมสถิติตามฤดูกาลของคลาสมือใหม่หลายๆ คลาส
วัตถุประสงค์
- ระบุแหล่งข้อมูล
- ดาวน์โหลดหน้า
- ระบุองค์ประกอบของหน้าที่สำคัญ
- ทำความสะอาดล่วงหน้าและดึงออก
- คลังเก็บเอกสารสำคัญ
- ขอแพ็คเกจ
- แพ็คเกจซุปที่สวยงาม
- แพ็คเก็จหมีแพนด้า
การระบุแหล่งข้อมูล
Basketball-Reference.com โฮสต์หน้าเว็บหลายร้อยหน้าเกี่ยวกับสถิติบาสเก็ตบอลซึ่งมีตั้งแต่ค่าเฉลี่ยตามฤดูกาลของหมวดหมู่คะแนนกล่องทั่วไป เช่น คะแนน การรีบาวด์ และเปอร์เซ็นต์การยิง ไปจนถึงการเล่นต่อการเล่นของแต่ละเกมที่เล่นในช่วงหลังสุด 20 ปีหรือมากกว่านั้น เราสามารถหลงทางได้อย่างง่ายดายในสึนามิทางสถิตินี้หากไม่มีเป้าหมายที่ชัดเจนว่าจะมองหาอะไรกันแน่
เป้าหมายในโพสต์นี้เรียบง่าย: รับข้อมูลมือใหม่ที่จะช่วยในการประเมินคุณค่าและศักยภาพที่แท้จริงของผู้เล่นอายุน้อย
ลิงค์ต่อไปนี้เป็นหน้าดังกล่าว มันแสดงสถิติที่เกี่ยวข้องทั้งหมดของมือใหม่ในฤดูกาลใดฤดูกาลหนึ่ง
https://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
เพื่อที่จะสะสมข้อมูลให้เพียงพอสำหรับการอนุมานทางสถิติที่ชัดเจนเกี่ยวกับผู้เล่น ข้อมูลหนึ่งปีจะไม่ถูกตัดทิ้ง ต้องมีการรวบรวมข้อมูลมูลค่าหลายสิบปีเพื่อช่วยกรองเสียงรบกวนและหาข้อสรุปเกี่ยวกับศักยภาพในอนาคตของผู้เล่น
หากสามารถดำเนินการซ้ำได้ด้วยตนเอง การดำเนินการนั้นเป็นตัวเลือกที่ยอดเยี่ยมสำหรับระบบอัตโนมัติ ในกรณีนี้ ตัวเลขใน 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สตริงของ ' จะทำหน้าที่อนุญาตให้ theformatแทนที่ด้วยปีที่ทำซ้ำในปัจจุบัน
— ตัวอย่างเช่น ในการวนซ้ำครั้งแรกurlค่าจะเป็นhttps://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()การอ่านและเขียนที่ตามมา (w+) ข้อมูลเพจจากrequests.get(data.text) ของเรา และจัดเก็บไฟล์ HTML ที่สร้างขึ้นใหม่ไว้ในเครื่อง
เพื่อหลีกเลี่ยงความเจ็บปวดที่เพิ่มมากขึ้นในการขูดไซต์ เราจัดเก็บหน้าเหล่านี้เป็นไฟล์ HTML ในเครื่อง ดูสิ เมื่อเยี่ยมชมไซต์ของเพจ เซิร์ฟเวอร์ที่โฮสต์เพจดังกล่าวจะตอบรับคำขอของคุณและส่งข้อมูลที่เหมาะสมกลับไปยังเบราว์เซอร์ของคุณ แต่การมีไคลเอนต์เฉพาะรายเดียวที่ขอข้อมูลเดียวกันซ้ำแล้วซ้ำอีกจะทำให้เซิร์ฟเวอร์ทำงานหนักเกินไป ผู้ดูแลเซิร์ฟเวอร์มีสิทธิ์ในการบล็อกคำขอถาวรเหล่านี้เพื่อให้สามารถให้บริการนี้แก่ผู้อื่นทางออนไลน์ได้อย่างเหมาะสม
การดาวน์โหลดไฟล์ HTML เหล่านี้ในเครื่องของคุณ จะเป็นการหลีกเลี่ยงสองสิ่ง:
- ต้องรอนานกว่าปกติเพื่อรวบรวมข้อมูลเดียวกัน
- โดนบล็อคไม่ให้เข้าเพจ หยุดเก็บข้อมูลไปเลย
มีข้อดีบางประการในการคัดลอกข้อมูลจากรูปแบบ HTML สิ่งที่สำคัญที่สุดประการหนึ่งคือการระบุคุณสมบัติขององค์ประกอบโดยไม่ซ้ำกันได้อย่างไร หากสามารถระบุได้อย่างเฉพาะเจาะจง สามารถขูดโดยใช้ Beautiful Soup แต่การจะทำเช่นนั้นได้ เราต้องรู้วิธีตรวจสอบองค์ประกอบ HTML เหล่านี้ด้วยตัวมันเอง
วิธีการตรวจสอบ
เราจะต้องเจาะลึกลงไปในการทำงานภายในของเอกสารนี้ แต่ฉันสัญญาว่าจะไม่ทำแบบฝึกหัดในการเรียนรู้ HTML
หากคุณรู้วิธีตรวจสอบวัตถุ HTML ก็ข้ามไปได้เลย มิฉะนั้น โปรดติดตามวิธีการตรวจสอบองค์ประกอบของหน้า
- คลิกที่จุดแนวตั้งสามจุดบนแถบเมนูด้านบนของ Chrome
- เลือก “เครื่องมือเพิ่มเติม”
- เลือกเครื่องมือสำหรับนักพัฒนา
- คลิกขวาที่หน้าเว็บ
- เลือก "ตรวจสอบ" เพื่อเข้าถึงแผงเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์
ตรวจสอบเพจ
เมื่อเห็นว่าเพจเหล่านี้ทั้งหมดถูกจัดเก็บไว้ในเครื่อง เราสามารถเลือกดูได้โดยการเข้าไปในระบบไฟล์เพื่อเปิดในเบราว์เซอร์ที่เราต้องการ หรือเราสามารถสร้างโค้ดต่อไปได้โดยใช้ส่วนย่อยของโค้ดต่อไปนี้
with open("notebooks/Rookies/2000.html") as f:
page = f.read()
ข้อมูลที่ต้องการทั้งหมดของหน้านี้อยู่ในองค์ประกอบตารางนั้น ก่อนที่จะรีบดูดข้อมูลทั้งหมดนี้อย่างที่เป็นอยู่ ตอนนี้เป็นเวลาที่ดีที่สุดที่จะพิจารณาว่าทุกอย่างในตารางนี้มีค่าควรแก่การรวบรวมหรือไม่
ทำความสะอาดล่วงหน้า
การทำความสะอาดล่วงหน้าอาจไม่ใช่คำที่ใช้บ่อยในคำศัพท์ของคุณ แต่สำหรับคนที่คุณเห็นว่าตัวเองกำลังขูดข้อมูลอยู่เป็นประจำ ก็ควรเป็นเช่นนั้น หากคุณต้องการหลีกเลี่ยงความหงุดหงิดจากการเสียเวลาหลายชั่วโมงในการดำเนินโครงการรวบรวมข้อมูล วิธีที่ดีที่สุดคือแยกแกลบออกจากข้าวสาลีก่อน
ตัวอย่างเช่น สังเกตองค์ประกอบสามอย่างในกล่องสีแดง
แถวหนึ่งทำหน้าที่เป็นส่วนหัวของตาราง "หลัก" อีกสองแถวคืออินสแตนซ์ที่ซ้ำกันของอาร์ติแฟกต์เดียวกันซึ่งอยู่ด้านบนสุด รูปแบบนี้ซ้ำทุกแถวที่ 20
การตรวจสอบเพิ่มเติมพบว่าแถวเหล่านี้ทั้งหมดมีtrแท็ก HTML (แถวตาราง) เหมือนกัน สิ่งที่ทำให้แต่ละองค์ประกอบเหล่านี้แตกต่างจากองค์ประกอบอื่นๆ คือชื่อคลาส
- แถวส่วนหัวหลัก
— คลาส =over_header - ทำซ้ำแถวส่วนหัว
— คลาส =over_header thead - สถิติ หมวดหมู่ แถว
— ชั้น =thead - วัตถุ
classesอาร์เรย์จะเก็บชื่อคลาสขององค์ประกอบแถวตารางทั้งหมดที่ไม่ได้รับการเปิดเผย
#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()
- เมธอด นี้
withให้โค้ดของเราเป็นโครงสร้างสำหรับpageตัวแปรเพื่ออ่านไฟล์ HTML ที่จัดเก็บไว้ในเครื่องเมื่อเรียกใช้ - คลาสตัวแยกวิเคราะห์ HTML เริ่มต้นได้โดยการยกตัวอย่างคลาส Beautiful Soup และส่งผ่านทั้ง
pageอ็อบเจกต์สตริง และhtml.parser. - ลูป ที่สองวน
forซ้ำผ่านค่าในclassesอาร์เรย์ - ลูป ที่สาม
forใช้วิธีการของ Beautiful Soupfind_allเพื่อระบุองค์ประกอบที่มีทั้งtrแท็กและชื่อคลาสที่ตรงกับองค์ประกอบเหล่านั้นclasses tr.decomposeทำหน้าที่ละองค์ประกอบแถวตารางที่ระบุออกจากหน้าทั้งหมด
แยกข้อมูล
ในที่สุดเราก็สามารถเริ่มทำงานในส่วนของโค้ดที่ดึงข้อมูลจากตารางได้จริง โปรดจำไว้ว่าตารางที่มีข้อมูลที่เกี่ยวข้องทั้งหมดมี ID เฉพาะของrookiesHTML การเพิ่มโค้ดของเราต่อไปนี้จะทำหน้าที่แยกวิเคราะห์ข้อมูลของตารางนี้
#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_tableยกตัวอย่างวิธีการของ Beautiful Soupfindเพื่อระบุองค์ประกอบ HTML ด้วย IDrookies- เมื่อเห็นว่า Pandas สามารถอ่านตาราง HTML ได้ ตารางมือใหม่จะถูกโหลดลงใน Pandas โดยใช้
read_htmlวิธีส่งผ่านrookie_tableเป็นสตริง - การตรึงเพื่อสิ้นสุด
[0]การเปลี่ยนrookiesจากรายการของ data frames เป็น data frame เดียว - คอลัมน์ "ปี" คือจุดสิ้นสุดของ
rookiesกรอบข้อมูล dfs.append(rookies)ทำหน้าที่จัดเก็บตารางทั้งหมดของทุก ๆ ปีใหม่ตามลำดับที่พวกเขาวนซ้ำในรายการของเฟรมข้อมูล- วิธี Pandas
concatใช้เพื่อรวมรายการของ data frames ให้เป็น data frame เดียว: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 อย่างไม่ต้องสงสัย มีห้าขั้นตอนง่ายๆ ที่ควรทำในแต่ละกรณี
- ระบุ URL ของหน้า
- ดาวน์โหลดหน้า
- ระบุองค์ประกอบ
- ทำความสะอาดล่วงหน้าและดึงออก
- คลังเก็บเอกสารสำคัญ
ถัดไปในซีรีส์นี้จะใช้ข้อมูลนี้จริง ๆ เพื่อทำความเข้าใจเกี่ยวกับศักยภาพของผู้เล่นในอนาคต ดังนั้นโปรดระวังงวดหน้า!





































![รายการที่เชื่อมโยงคืออะไร? [ส่วนที่ 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)