iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 28
0

定義目標

曾經一兩次有接觸過博弈類型的需求,都是要製作即時比分和消息販賣(不過都沒有接下就是),他們需要各種比賽的比分顯示,包含籃球、足球、網球、高球、羽球等等。

今天我們就拿 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 比賽分數,那麼我們會需要完成兩個步驟:

  1. 取得今日比賽分數
  2. 取得隊伍對應表

取得今日比賽分數

這部分是一個 get request,用 postman 測試,看起來沒有什麼問題。

取得隊伍對應表

這部分也是一個 get request,用 postman 測試也沒什麼問題,那就可以進入實作了。


實作程式碼

getTeamMappingArray function

首先先來取得 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)
    })
  })
}

getScoreboard function

再來取得 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,如何讓資料即時,也會是很重要的一個環節。


上一篇
使用模擬器做台鐵訂票爬蟲
下一篇
NBA 新聞爬取
系列文
爬蟲始終來自於墮性34

尚未有邦友留言

立即登入留言