曾經一兩次有接觸過博弈類型的需求,都是要製作即時比分和消息販賣(不過都沒有接下就是),他們需要各種比賽的比分顯示,包含籃球、足球、網球、高球、羽球等等。
今天我們就拿 NBA 來當案例挑戰抓取看看。
我不是一個非常懂 NBA 的球迷,所以我就直接去 NBA 官網 http://www.nba.com/ 抓資料,進去網站後上方有個 Scores,點擊後就會出現今天賽程的所有比賽清單和分數。那麼我們只需要把這個頁面解析出來,應該就能完成任務。
在進到頁面的時候,很明顯感覺他是前端 js render 的畫面,那麼直覺肯定會是有 api 呼叫的 request,打開 dev tools 觀察,果然有許多的 json request。
根據檔案名稱來看,很直覺的分數就肯定會在 scoreboard.json,再來觀察一下他的 request,路徑的部分是由日期所組成的,接著觀察看看 response,這天有 10 場比賽,所以 game 裡面就有 10 筆 record。再點進去看,有看到 hTeam 和 vTeam,這兩個肯定就肯定是主場和客場了。
在 hTeam 和 vTeam 裡面,包含了這支隊伍目前的勝負場數和這場比賽的比分,不過倒是沒有完整的隊伍名稱,只有隊伍代號,那麼肯定會有 data 去對應隊伍名稱。在一連串的 json request 裡面,就有著 teams.json,而我們就可以透過裡面的資料來 mapping 隊伍名稱,如此一來就能輕鬆的完成這次任務了。
要完成抓取 NBA 比賽分數,那麼我們會需要完成兩個步驟:
這部分是一個 get request,用 postman 測試,看起來沒有什麼問題。
這部分也是一個 get request,用 postman 測試也沒什麼問題,那就可以進入實作了。
首先先來取得 teams 的 mapping array,用來對應 scoreboard.json 的隊伍代號 tricode
。
function getTeamMappingArray(){
return new Promise(done=>{
request('https://data.nba.net/prod/v1/2017/teams.json', (err, res, body)=>{
var teams = JSON.parse(body)
var mapping = []
teams.league.standard.forEach((obj)=>{
mapping[obj.tricode] = obj
})
done(mapping)
})
})
}
再來取得 scoreboard,然後在完成後呼叫 promise 的 resolve。
function getScoreboard(){
return new Promise(done=>{
request('https://data.nba.net/prod/v2/20171227/scoreboard.json', (err, res, body)=>{
var scoreboard = JSON.parse(body)
done(scoreboard.games)
})
})
}
接下來我們來使用 promise all 來處理這兩個 async function,然後 mapping 一下隊伍名稱,這樣就可以了。
Promise.all([
getTeamMappingArray(),
getScoreboard()
]).then((results)=>{
var teams = results[0]
var games = results[1]
console.log(games.map((game )=>{
return {
hTeam: {
name: teams[game.hTeam.triCode].nickname,
score: game.hTeam.score,
},
vTeam: {
name: teams[game.vTeam.triCode].nickname,
score: game.vTeam.score,
},
}
}));
})
const request = require('request');
Promise.all([
getTeamMappingArray(),
getScoreboard()
]).then((results)=>{
var teams = results[0]
var games = results[1]
console.log(games.map((game )=>{
return {
hTeam: {
name: teams[game.hTeam.triCode].nickname,
score: game.hTeam.score,
},
vTeam: {
name: teams[game.vTeam.triCode].nickname,
score: game.vTeam.score,
},
}
}));
})
function getTeamMappingArray(){
return new Promise(done=>{
request('https://data.nba.net/prod/v1/2017/teams.json', (err, res, body)=>{
var teams = JSON.parse(body)
var mapping = []
teams.league.standard.forEach((obj)=>{
mapping[obj.tricode] = obj
})
done(mapping)
})
})
}
function getScoreboard(){
return new Promise(done=>{
request('https://data.nba.net/prod/v2/20171227/scoreboard.json', (err, res, body)=>{
var scoreboard = JSON.parse(body)
done(scoreboard.games)
})
})
}
其實這類型的服務重點不會在於如何抓取資料,重點會是在於資料即時性和穩定性,若有既有的 service,那其實用他們的服務會比爬蟲來的保險,畢竟這種一有問題就可能是幾千萬上下的狀況。
而另外一個課題,若有許多不同的比賽,那勢必不會只有籃球,每種都抓,可能全部比賽一次爬蟲的時間就會造成很大的 delay,如何讓資料即時,也會是很重要的一個環節。