關於crawler-以全國運動場館為例

Justin Lee
10 min readMay 21, 2017

--

比起上篇(關於crawler-以台北捷運為例中透過cheerio來抓取資料,這次主要著墨在資料處理上較多,其原因後續會說明。

開發環境

作業系統:ubuntu

後端程式語言:node js

框架:express

目標

這次的目標是全國運動場館資訊網,而抓取的資料則是運動場館搜尋API (無分頁)這支,此次的需求為:

  • 前端給後端經緯度,並顯示出離該經緯度5公里內的所有運動場的名稱及地址。

也就是說這次的處理除了給予前端資訊外,我們還要計算出起始點經緯度及目的地經緯度之間的距離。

資料結構

由於這次的資料屬於變動頻率不高的資料,所以筆者並沒有透過cheerio來抓取資料。若變動頻率高的資料,我們可以透過系統排程的方式來幫我們處理,系統排程部份之前在關於資料庫備份那篇就有提到。而crawler這部份的處理之後會在寫篇分享提及。

回歸正題,這部份我們就直接使用它在API說明所使用的範例來說明:

[{"GymID":7670,"Name":"陽明簡易棒球場","OperationTel":"07-7229449#677","Address":"高雄市三民區澄和路195號 ","Rate":0.0,"RateCount":0,"Distance":4.09,"GymFuncList":"陽明簡易棒球場","Photo1":"https://az804957.vo.msecnd.net/photogym/20140617155612_IMG_0011.JPG","LatLng":"22.643504187077,120.339056253433"},...]

很明顯的它的資料是JSON array的形式,可以看成它是由多個object所組成的array。所以我們先建立一個新的JSON file並將資料全部複製貼上到該檔案底下(data/sport_place.json)。

資料結構就會大概變成是:

+ get_sport_data
+ data
+ sport_place.json
+ model
+ sport_place_model.js
+ controllers
+ data_controller.js
+ routes
+ sport_place_.js

建立express開發環境

// 建立名為get_sport_data的express應用程式$ express --view=ejs get_sport_data
// 安裝相依項目
$ cd get_sport_data
$ npm install

資料剖析

那我們先在sport_place_model.js中,試著剖析資料:

var jsonData = require('../data/sport_place');var sportJson = jsonData;console.log(sportJson);

基本上就會跳出在sport_place.json中的所有資料:

[ { GymID: 7670,
Name: '陽明簡易棒球場',
OperationTel: '07-7229449#677',
Address: '高雄市三民區澄和路195號 ',
Rate: 0,
RateCount: 0,
Distance: 4.09,
GymFuncList: '陽明簡易棒球場',
Photo1: 'https://az804957.vo.msecnd.net/photogym/20140617155612_IMG_0011.JPG',....
]

接著我們先取出全部的名稱、地址及經緯度資料:

for (var i = 0; i < sportJson.length; i++) {
var name = sportJson[i].Name;
var address = sportJson[i].Address;
var latLng = sportJson[i].LatLng;
console.log("name: " + name);
console.log("address: " + address);
console.log("latLng: " + latLng);
}
}
//結果:
name: 陽明簡易棒球場
address: 高雄市三民區澄和路195號
latLng: 22.643504187077,120.339056253433
name: 福東國小跑步操場
address: 高雄市苓雅區福德三路96號
latLng: 22.624310609714,120.328617095947
...

可以發現到經緯度黏在一起了,我們可以透過javascript中的substringindexOf的做法來分開。

for (var i = 0; i < sportJson.length; i++) {
var name = sportJson[i].Name;
var address = sportJson[i].Address;
var latLng = sportJson[i].LatLng;
var lat1 = (sportJson[i].LatLng).substring(0, sportJson[i].LatLng.indexOf(","));
var lon1 = (sportJson[i].LatLng).substring(sportJson[i].LatLng.indexOf(",") + 1, 99);
console.log("name: " + name);
console.log("address: " + address);
console.log("lat: " + latLng);
console.log("lat1: " + lat1);
console.log("lon1: " + lon1);
}
name: 陽明簡易棒球場
address: 高雄市三民區澄和路195號
lat: 22.643504187077,120.339056253433
lat1: 22.643504187077
lon1: 120.339056253433
name: 福東國小跑步操場
address: 高雄市苓雅區福德三路96號
lat: 22.624310609714,120.328617095947
lat1: 22.624310609714
lon1: 120.328617095947
...

計算距離

接著我們來開始計算兩點經緯度之間的距離,由於筆者對這部分一開始並沒有什麼太大的概念,所以採取的作法是…去別人寫好的網頁…看它html語法中的script語言,把它改成可以用的語法 XD

這邊筆者參考的網頁是:

並擷取它的script語法

with (Math) {
/* form.dab.value = form.xa.value */
form.dab.value = form.r.value*acos(sin(form.xa.value*3.1416/180.) * sin(form.xb.value*3.1416/180.) + cos(form.xa.value*3.1416/180.)*cos(form.xb.value*3.1416/180.) * cos(form.ya.value*3.1416/180.
- form.yb.value*3.1416/180.))
/* r*acos(sin(xa)*sin(xb)+cos(xa)*cos(xb)*cos(ya-yb)) */
/* 地球赤道半徑 r=6378.39km, 地球兩極半徑=6356.91km */
}

之後改成:

function distance(lat1, lon1, lat2, lon2) {
var R = 6378.39; // km (change this constant to get miles)
var result = R * Math.acos(Math.sin(lat1 * 3.1416 / 180.) *
Math.sin(lat2 * 3.1416 / 180.) +
Math.cos(lat1 * 3.1416 / 180.) * Math.cos(lat2 * 3.1416 / 180.) *
Math.cos(lon1 * 3.1416 / 180. - lon2 * 3.1416 / 180.));
return result;
}

這樣帶入起點(lat1, lon1)及終點(lat2, lon2)就可以取得兩點之間的距離,其單位是公里(km)。

但還是得提,這公式的概念是由大圓距離(Great-circle distance)所產生。

接著,帶入5公里內的運動場距離判定:

var name = [];
var address =[];
for (var i = 0; i < sportJson.length; i++) {
var resultObj = {};
var lat2 = (sportJson[i].LatLng).substring(0, sportJson[i].LatLng.indexOf(","));
var lon2 =(sportJson[i].LatLng).substring(sportJson[i].LatLng.indexOf(",") + 1, 99);
if (distance(lat, lon, lat2, lon2) <= 5) {
name.push(sportJson[i].Name);
address.push(sportJson[i].Address);
}
}

這邊的處理方式就是我們先將名稱及地址用成array,要是達成5公里內的條件,就push到array裡面。之後,我們再將這兩個array合併成JSON array:

var resultArray = [];
for (var i = 0; i < name.length; i++) {
var resultObj = {};
resultObj.name = name[i];
resultObj.address = address[i];
resultArray.push(resultObj);
}

處理後的resultArray就會是我們要的結果。

測試

這時我們可以透過postman來進行測試,並寫些關於錯誤情況的判斷:

  • 沒有輸入lat值
  • 沒有輸入lon值
  • lat值超過範疇
  • lon值超過範疇
  • 5公里內沒有運動場所

API說明大概就會是:

搜尋5公里運動場所-API
http://localhost:3000/sportPlace
HTTP method: POSTrequest(w-xxx-form-urlencoded)
lat
lon
example
lat: 22.643504187077
lon: 120.396412611008
ok response{
"result": [
{
"name": "金潭國小棒球場",
"address": "高雄市林園區中厝里金潭路40號"
},
...
]
}
err responseerr 情況1. 沒有輸入lat值
2. 沒有輸入lon值
3. lat值超過範疇
4. lon值超過範疇
5. 5公里內沒有運動場所

關於錯誤判斷及完整的code可以參考:

crawler相關文章

--

--