學習任何新事物,都需要先搭建起學習框架。
通常我們會從「基礎知識」、「案例實操」兩個板塊切入,構建起自己的學習閉環。
一、關於「基礎知識」
對於想快速瞭解知識關鍵要點的新手,以下是比較推薦的學習素材:
MOOC課程:Python語言程式設計、Python網路爬蟲與資訊提取
參考書籍:《利用Python進行資料分析》、《Python程式設計:從入門到實踐》
二、關於「案例實操」
驗證自己是否真正理解知識並能應用的最佳方式,就是通過案例完成實踐操作。
分享案例
作為爬蟲新手,如何利用Python爬取NBA球員資料。
1、搭建工作環境
傳統方式:對於Win使用者,需要自己在本地搭建執行環境。
準備工作:
- 用Anaconda
安裝Package
- Requests庫
- BeautifulSoup4(bs4)庫
- Numpy和Pandas庫(用於資料儲存和清洗)
說明:
以上工具,除Python環境外,皆為Python的第三方庫。Windows環境下通過cmd的pip install命令安裝。對於Mac使用者,雖沒那麼複雜,但同樣會需要花費時間與精力。
大家會發現,按以上步驟搭建工作環境,是個特別費時費力的事兒,P2、P3的相容性問題也很突出。
那麼有沒有更好的方法呢?答案是,有的。
像科賽網,針對資料科學家人群,重點打造了K-Lab線上資料分析協作平臺。它涵蓋了Python、R等多種主流語言,完成90%以上資料分析&挖掘相關庫的部署,避免了搭建本地環境配置時遇到的各種問題,幫助資料人才真正實現「線上」做資料分析,提高效率,專注價值創造。
2、搭建工作流
1)選取目標官網:NBA中文資料網站
2)匯入庫
#引入主要的爬取工具:
import requests
from bs4 import BeautifulSoup
#以下是資料的清洗和儲存所需的輔助工具
import re
import numpy as np
import pandas as pd
複製程式碼
完成匯入後,獲取每個球員各個賽季單場比賽的基礎資料(CSV文件格式):
3)找到頁面,批量爬取
樣例:LeBron James的詳情頁,該網站資料來源相對規整,URL設定易找規律,方便抓取。
說明:為了實現自動批量抓取,爬取時一般需要讓爬蟲先爬取列表頁,再根據列表頁的索引連結爬取相應的詳情頁。但在該專案並不需要如此,從連結1、連結2可以看出其中規律。
將需要關注的引數用粗體表示:
- page:表示資料的頁碼,每頁顯示20條資料。
- GameType:season表示常規賽,playoff表示季後賽。
- Player id:網站自定義的球員編號。
爬取時,我們只需要改動這些資料就能實現全站資源的爬取,非常適合新手。
4)解析頁面
推薦在Chrome瀏覽器的開發者模式下解析頁面,它能快速定位到所需內容。
圖中藍標顯示的Table標籤便是需要抓取的表格。
關於table標籤:
table標籤有兩個子標籤thead、tbody。
- thead子標籤:儲存表頭資訊,包含一個tr子標籤(表頭只有一行)。tr又包含多個th子標籤,每個th儲存一格表頭資訊。
- tbody子標籤:儲存表格主題,包含多個tr子標籤。tr又包含多個td子標籤,每個td儲存一格表格資訊。
5)將表格內容搬運到本地
首先,通過fillframe函式,將網頁上的表格轉換成一個pandas的DataFrame表格物件。
def fillframe(tbody,index):
#這裡只使用tbody標籤,表頭通過index傳入,index是一個list
frame=pd.DataFrame()
if tbody:
all_tr=tbody.find_all('tr')
for tr in all_tr:
dic={}
all_td=tr.find_all('td')
i=-1
for td in all_td:
if i==-1:
#可以發現,網頁表格中每行的第一格都是空的,所以我們需要將其跳過。
i+=1
continue
else:
dic[index[i]]=td.string
i+=1
frame=pd.concat([frame,pd.DataFrame(dic,index=[0])],ignore_index=True)
return frame
複製程式碼
其次,用fillindex函式將fillframe函式生成index。
def fillindex(thead):
index=[]
if thead:
all_th=thead.tr.find_all('th')
i=-1
for th in all_th:
if i==-1:
i+=1
continue
else:
index.append(th.string)
i+=1
return index
複製程式碼
經過以上操作,不難發現,網頁上表格的表頭可能有歧義。比如它將投籃的命中數和三分的命中數兩項資料的索引都設定為了命中。為了保險起見,建議手動設定index。
index=['球員','賽季','結果','比分','首發','時間','投籃','命中','出手','三分', '三分命中','三分出手','罰球','罰球命中','罰球出手','籃板','前場','後場','助攻','搶斷','蓋帽','失誤','犯規','得分']
複製程式碼
6)開始爬取頁面,並提取table
提取過程中,由於每個球員的資料條目數不同,不能確定球員資料的頁數。
可以考慮給spider函式設定為“在頁面無法正常讀取或讀取的頁面中找不到table標籤時返回False值”。
def spider(page,player_id,gametype,index):
url='http://www.stat-nba.com/query.php?page='+str(page)+'&QueryType=game&GameType='+str(gametype)+'&Player_id='+str(player_id)+'&crtcol=season&order=1'
r=requests.get(url,timeout=30)
if r.status_code==200:
demo=r.text
soup=BeautifulSoup(demo,"html.parser")
data=soup.find('div',{'class':'stat_box'})
if not data: #找不到資料表格時退出
return False
table=data.find('table')
if table:
tbody=table.find('tbody')
return fillframe(tbody,index)
else: #資料表格為空時退出
return False
else: #頁面讀取失敗時退出
return False
複製程式碼
其次,用update函式儲存更新已爬取好的DataFrame。
def update(frame,path,filename):
try: #嘗試讀取檔案filename
frame0=pd.read_csv(path+'\\'+filename)
except: #如果檔案已經存在,則更新它
frame.to_csv(path+'\\'+filename,index=False,encoding='utf-8')
else: #否則建立名為filename的檔案
frame0=pd.concat([frame0,frame],ignore_index=True)
frame0.to_csv(path+'\\'+filename,index=False,encoding='utf-8')
複製程式碼
最後,著手設計主函式,來控制迴圈以及儲存資料。
frame_player=pd.DataFrame()
gametype='season'
for player_id in range(1862,1863):
#這裡僅爬取一位球員(James)測試,需要正式爬取請改為range(1,4450)
page=0
flag=True
while flag:
result=spider(page,player_id,gametype,index)
if type(result)==bool and not result: #返回False時
flag=False
break
else: #爬取成功時讀取表格
frame_player=pd.concat([frame_player,result],ignore_index=True)
page+=1
複製程式碼
7)測試階段:爬取一位球員資料,結果如下:
frame_player.head()
複製程式碼
接下來,就可以使用之前定義的update函式將frame_player儲存到本地,也可以在每次while迴圈結束時執行一次update函式以起到實時更新的效果。
注意:實時更新完以後需執行frame_player=DataFrame()語句,將frame_player清空以避免資料重複。
以上便是完整的爬取資料操作過程。
8)常見問題解答
由於網站自身的編碼原因,爬取時可能會遇到所有中文字元都成為亂碼,如下:
frame_player.head()
複製程式碼
對該資料集,可以手動輸入Index來解決表頭亂碼。
爬取球員英文姓名解決球員姓名亂碼,具體的函式如下:
def getname(player_id):
r2=requests.get('http://www.stat-nba.com/player/'+str(player_id)+'.html',timeout=30)
if r2.status_code==200:
demo2=r2.text
soup2=BeautifulSoup(demo2,"html.parser")
name_source=soup2.find('div',{'class':"playerinfo"}).find('div',{'class':'name'})
if re.findall('[A-z]* [A-z]*',name_source.contents[0]):
name=re.findall('[A-z]* [A-z]*',name_source.contents[0])[0]
else:
name=np.nan
else:
name=np.nan
return name
複製程式碼
PS:爬蟲還有很多可以優化的空間,比如控制每次傳送請求的時間間隔防止IP被封,爬取球隊名稱做對映來解決比分中的亂碼。
我們已在科賽網上建立該爬蟲專案,可以看到完整的實現程式碼。
建議新手萌登陸官網線上實現執行,不僅僅只停留在閱讀層面。
操作步驟
登入「kesci.com」- 點選「K-Lab」- 點選「專案」- 在「關鍵詞」框內輸入「爬取」即可檢視該專案的爬蟲完整程式碼,一鍵Fork、線上執行。
操作過程中有任何疑問,可新增科賽網工作人員微訊號(小科:kesci_jack),備註:掘金