Express.js

Node.js 雲端服務 Amazon (5)

基本介紹

教學目標

透過 Node.js 搭配 Amazon Elastic MapReduce 建立簡易的電影推薦 API 之應用。

前置作業

  1. 申請 AWS 雲端服務帳號。
  2. 建立 Amazon Elastic MapReduce 的叢集服務。

使用教學

推薦分析

在 Amazon Elastic MapReduce 中的 Cluster Details 就能找到 SSH 的登入方式。

登入 Amazon Elastic MapReduce 的主要伺服器

1
$ ssh hadoop@ec2-54-169-107-189.ap-southeast-1.compute.amazonaws.com -i ~/leoyehme.pem

下載範例檔案,並且解壓縮該檔案。

1
2
$ wget http://files.grouplens.org/datasets/movielens/ml-1m.zip
$ unzip ml-1m.zip

進行範例檔案內容的處理,以進行接下來的推薦分析。

1
$ cat ml-1m/ratings.dat | sed 's/::/,/g' | cut -f1-3 -d, > ratings.csv

將本機檔案放置 Hadoop 檔案系統之中。

1
$ hadoop fs -put ratings.csv /ratings.csv

開始透過 Apache Mahout 和 Amazon Elastic MapReduce 進行推薦分析

1
$ mahout recommenditembased --input /ratings.csv --output recommendations --numRecommendations 10 --outputPathForSimilarityMatrix similarity-matrix --similarityClassname SIMILARITY_COSINE

當我們透過三台 m1.medium 等級的 EC2 進行 Hadoop + Mahout 資料解析需要花費二十分鐘左右。

檢查推薦分析之後的結果。

1
2
$ hadoop fs -ls recommendations
$ hadoop fs -cat recommendations/part-r-00000 | head

安裝 Node.js

開始進行安裝。

1
2
3
4
5
6
$ sudo yum update
$ sudo yum install gcc-c++ make
$ sudo yum install openssl-devel
$ sudo yum install git
$ git clone git://github.com/joyent/node.git
$ git checkout v0.8.1 ./configure make sudo make install

修改執行檔的路徑。

1
2
3
$ sudo vi /etc/sudoers

Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

繼續安裝 NPM。

1
2
3
$ git clone https://github.com/isaacs/npm.git
$ cd npm
$ sudo make install

升級 Node.js 為穩定版本

1
2
3
$ sudo npm install n -g
$ sudo n stable
$ node -v

安裝相關套件

1
$ npm install express --save

實作推薦 API

透過 vi 工具撰寫推薦 API 程式碼

1
$ vi server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var express = require('express');
var app = express();
var router = express.Router();
app.use('/api', router);
router.get('/movie/:id', function(req, res){
var exec = require('child_process').exec;
exec("hadoop fs -cat recommendations/part*", function(error, stdout, stderr){
var result = stdout.split("\n");
result.forEach(function(element, index, arr) {
var data = element.split("\t");
var key = data[0];
var value = data[1];
if (req.params.id == key) {
res.json({data:value});
}
});
});
});
var port = process.env.PORT || 8080;
app.listen(port);

修改 EC2 的 Security Groups 中的 ElasticMapReduce-master 的 Inbound

1
2
3
4
5
Custom TCP Rule
TCP
8080
Anywhere
0.0.0.0/0

啟動推薦 API 伺服器

1
$ node server.js

在瀏覽器網址列輸入 http://ec2-54-169-107-189.ap-southeast-1.compute.amazonaws.com:8080/api/movie/37

1
{"data":"[1231:5.0,237:5.0,2133:5.0,3844:5.0,2478:5.0,1688:5.0,832:5.0,3108:5.0,1946:5.0,224:5.0]"}

推薦 API 優化

將每次存取 Hadoop 檔案系統中的檔案匯出至本機,接著再被推薦 API 進行存取。

1
hadoop fs -cat recommendations/part* > result.csv

將每次存取本機檔案的結果儲存至陣列變數中,接著再下次推薦 API 被存取時就能先取陣列的內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var express = require('express');
var app = express();
var router = express.Router();
app.use('/api', router);
var cache = new Array();
router.get('/movie/:id', function(req, res){
if (cache[req.params.id]) {
res.json({data:cache[req.params.id]});
} else {
var fs = require('fs');
var path = require('path');
var filePath = path.join(__dirname, 'result.csv');
fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){
if (!err) {
var result = data.split("\n");
result.forEach(function(element, index, arr) {
var data = element.split("\t");
var key = data[0];
var value = data[1];
cache[key] = value;
if (req.params.id == key) {
res.json({data:value});
}
});
} else {
console.log(err);
}
});
}
});
var port = process.env.PORT || 8080;
app.listen(port);

啟動推薦 API 伺服器

1
$ node server.js

在瀏覽器網址列輸入 http://ec2-54-169-107-189.ap-southeast-1.compute.amazonaws.com:8080/api/movie/37。

1
{"data":"[1231:5.0,237:5.0,2133:5.0,3844:5.0,2478:5.0,1688:5.0,832:5.0,3108:5.0,1946:5.0,224:5.0]"}

重複存取推薦 API 多次,將會發現回應時間比未優化之前快非常多。

相關資源

2014Q1 工作心得 (1)

2014 年 Mary Meeker 的網際網路趨勢報告中說明了巨量資料趨勢,其中針對 Data-Generating Consumers 會需要提供最好的使用者界面幫助他們更容易的使用資料,在面對非結構化的資料,我們會透過寫程式的方式將其轉換成結構化的資料,這樣的過程也就是「爬蟲」,不論是影片和音樂的資料,皆有一個共通性那就是原始影音檔的連結網址散佈在網際網路各大影音網站平台之中,例如 Youtube 和 Dailymotion ,此時我們會需要透過程式將網站平台中的相關資料擷取至資料庫之中,技術方面主要是透過 Node.js 搭配 cheerio 套件解釋 HTML 網頁內容中的資料轉換成實用的資訊,接著儲存至 Amazon 架設的後台資料庫之中,最後再以 Node.js 搭配 expressjs 套件透過 API 提供結構化的 JSON 資料給 iPhone 或 Android 等行動裝置直接應用,呈現最好的使用者界面給 Data-Generating Consumers。

可是在此過程中並非想像中的簡單,一開始進行爬蟲時會遭遇編碼的問題,基本上若是英文資料,此時只要需要透過 ASCII 編碼就能夠進行解析,可是若是中文資料,此時就會需要透過 UNICODE 編碼方式進行解析,除此之外還是存在許多編碼的方式需要進行解決,像是簡體中文的網站可能就會需要 GB2312 或 GBK 編碼進行解析。等待編碼問題處理完成,接著需要花些時間觀察所需的資料要如何從網頁結構中取出,此時若發現所需的資料無法按照網頁結構取出,就必需以常用的正規表示式 (Regular Expression) 的方式解析最原始的網頁資料。

當上述問題解決之後接著就需要找出清單列表,根據清單列表中的資訊撰寫排程程式進行資料的擷取,此時若請求網頁內容的次數太頻繁,某些網站就會開始進行阻擋,以致於無法很順利的按照排程時間的規劃進行,就必需針對不同網站進行客製化的調整。當我們將大量資料擷取下來之後,常常會發現部份擷取下來的資料還是需要透過人工編輯的方式進行資料校正,此時就必提供管理平台讓編輯者能更方便的進行資料校正與確保品質。

最後還需要考慮自動更新的問題,就必需針對爬蟲程式進行排程時間的規劃,看似已經能上線正常運作之後,此時會發現某些網頁內容改變真頻繁,以致於剛剛完成的爬蟲程式立即又要根據網頁內容進行改版,總而言之,爬蟲看似簡單的工作,卻是巨量資料分析重要的第一步,且往往會有許多細節要注意。