iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 23
1

定義目標

在製作電商相關的系統時,金流常常是很重要的一環,而台灣消費者的習慣很常會選用超商貨到付款,所以在串接金流的時候,若消費者選擇 7-11 貨到付款,則都會需要讓使用者選擇門市代碼,而門市代碼其實目前好像並沒有比較好的 service (有的話請提供讓我知道一下),所以我們今天的主題就來做一個 7-11 全門市代碼的爬取吧。


實際探訪

在網路上 reserach 的結果,若要爬取 7-11 的所有門市資料,那麼來源會有兩個,一個是 http://emap.pcsc.com.tw/ 另一個是 http://www.ibon.com.tw/retail_inquiry.aspx#gsc.tab=0 ,這兩個我們都來趟訪一下,然後再來判斷我們選用哪一種比較方便。

首先先看 http://emap.pcsc.com.tw/ ,進去網站之後,會先選擇縣市,然後進到縣市頁面後,要再查詢區域,接著才會出現這個區域所有的門市地點。

再來看一下整個步驟的 request,從整個流程來看,我們至少要送出兩個 request 才能拿到一個區域的門市地點,而算起來大概會有 20 幾個縣市,假設每個縣市平均有 10 個區域,那也就是說我們至少要 request 200 次才能拿到所有門市清單。另外還有一點,這兩個 request 的 post 參數都不算少,雖然不見得都有用到,但是還是得一個個嘗試。

接著我們來看看 http://www.ibon.com.tw/retail_inquiry.aspx#gsc.tab=0 ,進到頁面後一樣可以選擇城市,然後預設是所有區域,切換城市後一樣預設顯示所有區域的門市。

再來看一下整個步驟的 request,從整個流程來看,我們僅需要一個 request 就能拿到一個縣市的所有門市地點,若算起來有大約 20 幾個縣市,那也就是說我們僅需要 20 幾個 request 就能完成任務。而且,從探訪來看,這個 request post data 單純很多,只需要兩個參數即可。

兩相比較完,毫無疑問的我們選用 http://www.ibon.com.tw/retail_inquiry.aspx#gsc.tab=0 來抓取。


分解研究

要取得所有門市,那麼會拆解成以下兩個步驟:

  1. 取得所有縣市
  2. 取得一個縣市的所有門市

取得所有縣市

這部分看起來挺簡單,就單純的 select 而已,測試一下確定抓取沒問題。

取得一個縣市的所有門市

這部分也是單純 select 而已,測試一下確定抓取沒問題,那麼我們就可以進入實作了。


實作程式

getCities function

首先我們先來實作取得所有縣市的 function,基本上一個 get request 就搞定了,然後將取得的縣市丟給 callback。

function getCities(callback) {
  request('http://www.ibon.com.tw/retail_inquiry.aspx#gsc.tab=0', (err, res, body) => {
    var $ = cheerio.load(body)
    var cities = $('#Class1 option').map((index, obj) => {
      return $(obj).text()
    }).get()
    callback(cities)
  })
}

getStories function

接下來我們來實作取得門市的 function,這邊會收一個縣市做參數,然後將取得的門市再回傳給 callback 就可以了。

function getStories(city, callback) {
  var options = {
    url: 'http://www.ibon.com.tw/retail_inquiry_ajax.aspx',
    method: 'POST',
    form: {
      strTargetField: 'COUNTY',
      strKeyWords: city,
    }
  }
  request(options, (err, res, body) => {
    var $ = cheerio.load(body)
    var stores = $('tr').map((index, obj) => {
      return {
        id: $(obj).find('td').eq(0).text().trim(),
        store: $(obj).find('td').eq(1).text().trim(),
        address: $(obj).find('td').eq(2).text().trim(),
      }
    }).get()
    stores.shift()
    callback(stores)
  })
}

積木組合

最後我們先取得所有城市,再用 async.map 來取得所有城市的門市,這樣就完成了。

getCities((cities) => {
  async.map(cities, (city, callback) => {
    getStories(city, (stores) => {
      callback(null, stores);
    })
  }, (err, results) => {
    console.log([].concat.apply([], results));
  })
})

完整程式碼

const request = require('request');
const cheerio = require('cheerio');
const async = require('async');

getCities((cities) => {
  async.map(cities, (city, callback) => {
    getStories(city, (stores) => {
      callback(null, stores);
    })
  }, (err, results) => {
    console.log([].concat.apply([], results));
  })
})

function getCities(callback) {
  request('http://www.ibon.com.tw/retail_inquiry.aspx#gsc.tab=0', (err, res, body) => {
    var $ = cheerio.load(body)
    var cities = $('#Class1 option').map((index, obj) => {
      return $(obj).text()
    }).get()
    callback(cities)
  })
}

function getStories(city, callback) {
  var options = {
    url: 'http://www.ibon.com.tw/retail_inquiry_ajax.aspx',
    method: 'POST',
    form: {
      strTargetField: 'COUNTY',
      strKeyWords: city,
    }
  }
  request(options, (err, res, body) => {
    var $ = cheerio.load(body)
    var stores = $('tr').map((index, obj) => {
      return {
        id: $(obj).find('td').eq(0).text().trim(),
        store: $(obj).find('td').eq(1).text().trim(),
        address: $(obj).find('td').eq(2).text().trim(),
      }
    }).get()
    stores.shift()
    callback(stores)
  })
}


衍伸應用

若我們選擇另一個資料來源,則可能會在研究上耗費更多的時間,除此之外,我們也會浪費更多的時間資源在請求 request 上,我自己也實作了另外一個資料來遠的程式碼,另一個需要抓取的時間大概是 30 秒,而我們選用的這個資料來源,只需要 3 秒,從這個 case 我們應該能夠學習到選擇正確的資料來源遠比技術重要。


上一篇
Facebook 個人相簿
下一篇
iThelp oAuth 登入
系列文
爬蟲始終來自於墮性34

尚未有邦友留言

立即登入留言