Bật mí bóng rổ bằng Python
Trong chuỗi blog này, các thư viện Python mạnh mẽ được tận dụng để giúp khám phá một số sự thật thống kê ẩn giấu trong bóng rổ. Bước đầu tiên trong bất kỳ phương pháp tiếp cận dựa trên dữ liệu nào là xác định và thu thập dữ liệu cần thiết.
Thật may mắn cho chúng tôi, Basketball-Reference.com lưu trữ các trang dữ liệu bóng rổ có thể dễ dàng thu thập. Các quy trình của hướng dẫn này có thể dễ dàng áp dụng cho bất kỳ số lượng trang nào của họ, nhưng trong trường hợp này, chúng tôi dự định thu thập số liệu thống kê theo mùa của nhiều lớp tân binh.
Các mục tiêu
- Xác định nguồn dữ liệu
- Tải xuống trang
- Xác định các yếu tố trang quan trọng
- Làm sạch trước và giải nén
- Lưu trữ
- gói yêu cầu
- Gói súp đẹp mắt
- gói gấu trúc
Xác định nguồn dữ liệu
Basketball-Reference.com lưu trữ hàng trăm trang được tuyển chọn về số liệu thống kê bóng rổ, từ mức trung bình theo mùa của các danh mục điểm số điển hình như điểm, số lần bật lại và tỷ lệ sút bóng, cho đến diễn biến từng trận của từng trận đã chơi trong trận chung kết. 20 năm hoặc lâu hơn. Người ta có thể dễ dàng lạc lối trong cơn sóng thần thống kê này nếu không có mục tiêu rõ ràng về chính xác những gì cần tìm kiếm.
Mục tiêu ở đây trong bài đăng này rất đơn giản: thu thập dữ liệu về tân binh sẽ giúp đánh giá giá trị và tiềm năng thực sự của một cầu thủ trẻ.
Liên kết sau đây là một trang như vậy. Nó liệt kê tất cả các số liệu thống kê có liên quan của các tân binh trong một mùa giải cụ thể.
https://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
Để tích lũy đủ dữ liệu nhằm đưa ra những suy luận thống kê chắc chắn về người chơi, dữ liệu của một năm sẽ không đủ. Cần phải có dữ liệu có giá trị hàng chục năm được thu thập để giúp lọc thông tin nhiễu và đưa ra kết luận về tiềm năng trong tương lai của người chơi.
Nếu một hành động có thể được lặp lại thủ công, thì nó sẽ trở thành một ứng cử viên tuyệt vời cho tự động hóa. Trong trường hợp này, số trong URL ở trên tương ứng với năm tương ứng của hạng tân binh đó. Với kiến thức đó, hãy bắt đầu tập hợp những dòng mã đầu tiên của chúng ta lại với nhau.
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"
- Các gói thích hợp được nhập khẩu
- Biến
yearsdanh sách chỉ định phạm vi của các năm mong muốn - 1990 đến 2017 url_basephục vụ để lưu trữ biến chuỗi được định dạng trước của URL mục tiêu
Khi quét các trang web, bắt buộc phải loại bỏ càng nhiều chi phí càng tốt. Vì trang web lưu trữ tất cả thông tin của họ trên giao diện người dùng HTML, nên có thể dễ dàng tải xuống toàn bộ trang và lưu trữ cục bộ.
#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)
- Các dấu ngoặc nhọn được tìm thấy trong
urlchuỗi ' sẽ dùng để cho phépformatthay thế nó bằng năm lặp lại hiện tại
— Ví dụ: trong lần lặp đầu tiên, giáurltrị sẽ làhttps://www.basketball-reference.com/leagues/NBA_1990_rookies-season-stats.html
— Ở lần lặp lại thứ hai, năm tiếp theo sẽ được tham chiếu thay thế:https://www.basketball-reference.com/leagues/NBA_1991_rookies-season-stats.html - Biến dữ liệu hoạt động như một trình giữ chỗ cho
requests.get()hàm và các tham chiếu củaurlgiá trị chuỗi được lặp hiện tại - Sau đó, phương pháp này
requestssử dụng chuỗi URL mới được định dạng để truy xuất trang được đề cập - Phần tiếp theo
with open()đọc và ghi (w+) dữ liệu trang từrequests.get(data.text) của chúng tôi và lưu trữ cục bộ các tệp HTML mới được tạo
Để tránh một nỗi đau ngày càng tăng trong việc quét trang web, chúng tôi lưu trữ các trang này dưới dạng tệp HTML cục bộ. Hãy xem, khi thực hiện một lượt truy cập vào một trang web, máy chủ lưu trữ trang nói trên sẽ tôn trọng yêu cầu của bạn và gửi lại dữ liệu thích hợp cho trình duyệt của bạn. Nhưng việc một khách hàng cụ thể yêu cầu lặp đi lặp lại cùng một thông tin sẽ gây căng thẳng quá mức cho máy chủ. Quản trị viên máy chủ hoàn toàn có quyền chặn các yêu cầu liên tục này để có thể cung cấp dịch vụ này cho những người khác trực tuyến một cách tối ưu.
Bằng cách tải xuống các tệp HTML này trên máy cục bộ của bạn, bạn sẽ tránh được hai điều:
- Phải đợi lâu hơn bình thường để thu thập cùng một dữ liệu
- Bị chặn truy cập trang, tạm dừng hoàn toàn việc thu thập dữ liệu
Có một vài lợi thế trong việc cạo dữ liệu từ các định dạng HTML. Một trong những điều quan trọng nhất là làm thế nào các thuộc tính phần tử có thể được xác định duy nhất. Nếu chúng có thể được xác định duy nhất, chúng có thể được loại bỏ bằng Beautiful Soup. Nhưng để làm được điều đó, người ta phải biết cách tự kiểm tra các phần tử HTML này.
Cách kiểm tra
Chúng ta sẽ cần tìm hiểu sâu hơn về hoạt động bên trong của tài liệu này, nhưng tôi hứa rằng tôi sẽ không coi đây là một bài tập về học HTML.
Nếu bạn biết cách kiểm tra các đối tượng HTML, vui lòng tiếp tục. Nếu không, vui lòng làm theo cách kiểm tra các thành phần trang.
- Nhấp vào ba dấu chấm dọc trên thanh menu trên cùng của Chrome
- Chọn “Công cụ khác”
- Chọn Công cụ dành cho nhà phát triển.
- Nhấp chuột phải vào trang web
- Chọn “Kiểm tra” để truy cập bảng Công cụ dành cho nhà phát triển
Kiểm tra trang
Thấy rằng tất cả các trang này đều được lưu trữ cục bộ, chúng tôi có thể chọn xem chúng bằng cách vào hệ thống tệp để mở chúng trong trình duyệt mong muốn hoặc chúng tôi có thể tiếp tục xây dựng mã của mình bằng cách triển khai đoạn mã sau.
with open("notebooks/Rookies/2000.html") as f:
page = f.read()
Tất cả dữ liệu mong muốn của trang này được đặt trong phần tử bảng đó. Trước khi vội vàng thu thập tất cả dữ liệu này, bây giờ là thời điểm tốt nhất để xem xét liệu mọi thứ trên bảng này có đáng để thu thập hay không.
Làm sạch trước
Làm sạch trước có thể không phải là một từ thường xuyên trong vốn từ vựng của bạn, nhưng đối với những người bạn thấy mình thường xuyên cạo dữ liệu, thì nó nên như vậy. Nếu bạn muốn tránh sự thất vọng vì lãng phí hàng giờ tiến độ trong một dự án thu thập dữ liệu, thì tốt nhất trước tiên bạn nên tách vỏ trấu ra khỏi lúa mì.
Chẳng hạn, hãy lưu ý ba yếu tố được đánh dấu bằng màu đỏ.
Một hàng đóng vai trò là tiêu đề bảng "chính". Hai hàng còn lại là các phiên bản trùng lặp của cùng một tạo phẩm được tìm thấy ở trên cùng. Mẫu này lặp lại sau mỗi hàng thứ 20.
Kiểm tra thêm cho thấy rằng tất cả các hàng này đều có cùng một trthẻ HTML (hàng của bảng). Điều phân biệt từng phần tử này với bất kỳ phần tử nào khác là tên lớp của chúng.
- Hàng tiêu đề chính
— Lớp =over_header - Lặp lại hàng tiêu đề
— Class =over_header thead - Hàng Danh mục Thống kê
— Lớp =thead - Đối
classestượng mảng sẽ chứa tất cả các tên lớp của phần tử hàng trong bảng chưa được chỉnh sửa.
#array to house list of dataframes
dfs = []
#unnecessary table rows to be removed
classes = ["over_header", "over_header thead", "thead"]
Phương pháp này decomposedùng để loại bỏ các phần tử không mong muốn như vậy trong một trang. Theo tài liệu chính thức của Beautiful Soup ...
decompose()
Tag.decompose()loại bỏ một thẻ từ cây, sau đó phá hủy hoàn toàn nó và nội dung của nó.
Dưới đây là một đoạn mã nơi chúng tôi có thể tối ưu hóa decomposephương thức với sự trợ giúp của forcác vòng lặp.
#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()
- Phương
withthức cung cấp mã của chúng tôi cấu trúc chopagebiến để đọc các tệp HTML được lưu trữ cục bộ khi được gọi - Một lớp trình phân tích cú pháp HTML được khởi tạo bằng cách khởi tạo lớp Beautiful Soup và truyền vào cả
pageđối tượng chuỗi và đối tượnghtml.parser. - Vòng lặp thứ hai
forlặp qua các giá trị trongclassesmảng - Vòng lặp thứ ba
forsử dụng phương thức của Beautiful Soupfind_allđể xác định các phần tử có cảtrthẻ và tên lớp khớp với các phần tử trongclasses tr.decomposephục vụ để loại bỏ hoàn toàn từng thành phần hàng của bảng được xác định khỏi trang
Trích xuất dữ liệu
Cuối cùng chúng ta có thể bắt đầu làm việc với phần mã thực sự trích xuất dữ liệu từ bảng. Hãy nhớ rằng bảng chứa tất cả dữ liệu liên quan có ID duy nhất HTML rookies. Các bổ sung sau đây vào mã của chúng tôi sẽ phục vụ để phân tích dữ liệu của bảng này.
#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_tablekhởi tạo phương thức của Beautiful Soupfindđể xác định phần tử HTML bằng IDrookies- Thấy rằng Pandas có thể đọc các bảng HTML, bảng tân binh được tải vào Pandas bằng
read_htmlphương thức, chuyển vào nórookie_tabledưới dạng một chuỗi - Xử lý đến cuối
[0]biếnrookiestừ một danh sách các khung dữ liệu thành một khung dữ liệu duy nhất - Cột “Năm” là phần cuối của
rookieskhung dữ liệu dfs.append(rookies)phục vụ để chứa tất cả các bảng của mỗi năm tân binh theo thứ tự chúng được lặp lại thành một danh sách các khung dữ liệu- Phương pháp Pandas
concatđược sử dụng để kết hợp danh sách các khung dữ liệu thành một khung dữ liệu duy nhất:all_rookies
Bước cuối cùng của chúng tôi liên quan đến việc lấy tất cả thông tin sạch, hữu ích này và lưu trữ thông tin đó ở định dạng CSV mà con người và máy có thể đọc được dễ dàng. Việc thêm dòng này vào cuối mã của chúng tôi sẽ hữu ích khi quyết định quay lại và tham khảo dữ liệu đã thu thập.
#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")
Một lần nữa, quy trình tiếp theo trong hướng dẫn này chắc chắn sẽ áp dụng cho hầu hết mọi trang khác trên Basketball-Reference.com . Có năm bước đơn giản đáng thực hiện trong từng trường hợp.
- Xác định URL trang
- Tải xuống trang
- Xác định các yếu tố
- Làm sạch trước và giải nén
- Lưu trữ
Phần tiếp theo trong loạt bài này sẽ thực sự sử dụng dữ liệu này để hiểu rõ hơn về tiềm năng của người chơi trong tương lai. Vì vậy, hãy cảnh giác với các phần trong tương lai!

![Dù sao thì một danh sách được liên kết là gì? [Phần 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































