ズッキーニのプログラミング実験場

プログラミング + アカデミック + 何か面白いこと。 記載されているものは基本的に私が所属する団体とは関係がありません。

   Sep 08

[node.js]jQuery Deferred を使ってみる

by zuqqhi2 at 2013年9月8日
Pocket

やりたいこと

jQuery Deferredを使ってコールバックを順番に処理する。

プログラム

食べログAPIの内容をmongoDBに入れるプログラムを作成した。
画像APIにもアクセスするため、通常のコールバックだと階層がやたら深くなる。
ところが、jQuery Deferredのおかげで大分見やすくなっている。

#!/usr/bin/env node

 
//===============================================
// Load Libraries

var cheerio = require("cheerio");
var request = require("request");
var yaml = require("yaml");
var fs = require("fs");
var command = require("commander");
var mongoose = require("mongoose");
var log4js = require('log4js');
var jquery = require('jquery');

//===============================================
// Logging Setting

log4js.configure({
        appenders: [{
                "type": "dateFile",
                "filename": "./logs/insert_tabelog_datas.log",
                "pattern": "-yyyy-MM-dd"
        }]
});
var logger = log4js.getLogger("dateFile");

//===============================================
// Load config file

var configData = fs.readFileSync("./config/config.yml","utf8");
var config = yaml.eval(configData); 

//===============================================
// Make request URL

var requestBaseUrl = config.tabelog_api.base_url;
var apiKey = config.tabelog_api.api_key;

// paramter
// parse command options and create help command automatically
command
    .version('1.0.0')
    .usage('[option]')
    .option('-p, --prefecture <String>', 'prefecture name (default japan)')
    .option('-n, --pagenum <n>', 'page number (max 60)', parseInt)
    .parse(process.argv);

var pageNumber = 1;
var prefecture = "japan";
if (command.prefecture) prefecture = command.prefecture;
if (command.pagenum)            pageNumber = command.pagenum;

var queryParams = new Array();
queryParams.push("Prefecture=" + prefecture);
queryParams.push("PageNum=" + pageNumber);
queryParams.push("ResultSet=large");
queryParams.push("Key=" + apiKey);

// join
var requestUrl = requestBaseUrl + "?" + queryParams.join("&amp;");

//===============================================
// DB setting
var db = mongoose.connect('mongodb://' + config.event_db.host + '/' + config.event_db.event_data,
        function (err) {
                if (err) {
                        logger.error("Connection Fail. mongodb://" + config.event_db.host + "/" + config.event_db.event_data);
                } else {
                        logger.info("Connection Success!");
                }
        }
);
var InsertTabelogDataConfigSchema = new mongoose.Schema({
        cur_prefecture : { type: Number, default: 0 },
        cur_pagenum    : Number,
        max_pagenum    : Number,
        created_time   : { type: Date, default: Date.now },
        update_time    : { type: Date, default: Date.now }
});

var EventsSchema = new mongoose.Schema({
        event_id     : { type: Number, default: 0 },
        genre_id     : Number,
        title        : String,
        image        : String,
        description  : String,
        url          : String,
        station      : String,
        address      : String,
        business_hour: String,
        holiday      : String,
        latitude     : Number,
        longitude    : Number,
        created_time : { type: Date, default: Date.now },
        update_time  : { type: Date, default: Date.now }
});

EventsSchema.pre('save', function(next) {
    if(!this.isNew) return next();
 
    var model = this;
 
    model.db.db.executeDbCommand({
        findAndModify: 'current_event_id', // 'コマンド名': '対象のコレクション名'
        query: { name: model.collection.name }, // 検索オプション
        update: { $set: { name: model.collection.name }, $inc: { sequence: 1 } },
        new: true, // 更新したデータを受け取るかどうか
        upsert: true // 見つからなかったら挿入するかどうか
    }, function(err, data) {
        if(!err &amp;&amp; data.documents[0].ok) {
            // model.id に取得した値をセット
            model.event_id = data.documents[0].value.sequence;
            next();
        } else {
            next(err || new Error(data.documents[0].errmsg));
        }
    });
});

var Events = db.model("events_001s", EventsSchema);


//===============================================
// Get xml data about restaurant from tabelogAPI

function fetch_tabelogAPI_response(api_request_url) {
        var deferred = jquery.Deferred();
        
        request({url: api_request_url}, function(error, response, body)
        {
                if (!error &amp;&amp; response.statusCode == 200) {
                        logger.info("response statusCode : " + response.statusCode);

                        $ = cheerio.load(body, {ignoreWhitespace: true, xmlMode: true});

                        var url = response.request.href;
                        var latest_id = 0;
                        
                        return deferred.resolve($);
                } else {
                        deferred.reject(new Error(error));
                }
        });
        return deferred.promise();
}

//===============================================
// Parse response data from tabelogAPI
 
function parse_tabelogAPI_response(response) {
        var restaurant_data = new Array();

        response("Item").each(function(i, xmlItem) {
                var rcd = $(xmlItem).children()[0]["children"][0]["data"];
                        
                var data = {};
                data["genre_id"]      = 1;
                data["title"]         = $(xmlItem).children()[1]["children"][0]["data"];
                data["image"]         = "";
                data["description"]   = "";
                data["url"]           = $(xmlItem).children()[2]["children"][0]["data"];
                data["station"]       = $(xmlItem).children()[12]["children"][0]["data"];
                data["address"]       = $(xmlItem).children()[13]["children"][0]["data"];
                data["business_hour"] = $(xmlItem).children()[15]["children"][0]["data"];
                data["holiday"]       = $(xmlItem).children()[16]["children"][0]["data"];
                data["latitude"]      = parseFloat($(xmlItem).children()[17]["children"][0]["data"]);
                data["longitude"]     = parseFloat($(xmlItem).children()[18]["children"][0]["data"]);                   
                data["rcd"]           = rcd;
                restaurant_data.push(data);

                logger.info("Retrieve data: " + i);
                logger.info(data);
                logger.info("Rcd : " + rcd);
        
        });
        return restaurant_data;
}

//===============================================
// Get image URL from tabelogAPI

var image_api_base_url = "http://api.tabelog.com/Ver1/ReviewImageSearch/?Key=" + apiKey + "&amp;Rcd=";
function fetch_restaurant_image_url(data) {
        var deferred = jquery.Deferred();
        
        var arg = new Array(2);
        arg[0] = 0;
        arg[1] = data;
        var result = parse_tabelog_imageAPI_response(arg);
        for (var idx = 1; idx < data.length; idx++) {
                result = result.then(parse_tabelog_imageAPI_response);
        }
        result.then(function (arg) {
                return deferred.resolve(arg[1]);
        });
        return deferred.promise();
}

function parse_tabelog_imageAPI_response(data) {
        var deferred = jquery.Deferred();
        var idx = data[0];
        var info = data[1];

        var image_api_url = image_api_base_url + info[idx]["rcd"];
        request({url: image_api_url}, function (error, response, body)
        {
                if (!error &amp;&amp; response.statusCode == 200) {
                        logger.info("response statusCode : " + response.statusCode);

                        $ = cheerio.load(body, {ignoreWhitespace: true, xmlMode: true});
                        info[idx]["image"] = $("Item").children()[2]["children"][0]["data"];
                        
                        logger.info("image url of rcd " + info[idx]["rcd"] + " : " + info[idx]["image"]);

                        var result = new Array(2);
                        result[0] = idx + 1;
                        result[1] = info;
                        return deferred.resolve(result);
                } else {
                        return deferred.reject(new Error(error));
                }
        });
        return deferred.promise();
}

//===============================================
// Get restaurant description:
function fetch_restaurant_description(data) {
        var deferred = jquery.Deferred();
        
        var arg = new Array(2);
        arg[0] = 0;
        arg[1] = data;
        var result = parse_tabelog_description_response(arg);
        for (var idx = 1; idx < data.length; idx++) {
                result = result.then(parse_tabelog_description_response);
        }
        result.then(function (arg) {
                return deferred.resolve(arg[1]);
        });
        return deferred.promise();
}

function parse_tabelog_description_response(data) {
        var deferred = jquery.Deferred();
        var idx = data[0];
        var info = data[1];

        request({url: info[idx]["url"]}, function (error, response, body)
        {
                if (!error &amp;&amp; response.statusCode == 200) {
                        logger.info("response statusCode : " + response.statusCode);

                        $ = cheerio.load(body, {ignoreWhitespace: true, xmlMode: true});
                        info[idx]["description"] = $("p.comment").first().text();
                        
                        var result = new Array(2);
                        result[0] = idx + 1;
                        result[1] = info;
                        return deferred.resolve(result);
                } else {
                        return deferred.reject(new Error(error));
                }
        });
        return deferred.promise();
}

//===============================================
// Delete unuseful field
function delete_unuseful_field(data) {
        var deferred = jquery.Deferred();

        for (var idx = 0; idx < data.length; idx++) {
                delete data[idx]["rcd"];
        }

        return deferred.resolve(data);
}

//===============================================
// Insert data for mongo
function insert_restaurant_data(data) {
        var deferred = jquery.Deferred();

        for (var idx = 0; idx < data.length; idx++) {
                var newPost = new Events(data[idx]);
                newPost.save(function(err) {
                        if (err) {
                                logger.error("insert error :" + data[idx]);
                        } else {
                                logger.info("insert is successful with " + data[idx]);
                        }
                });
        }

        return deferred.resolve(data);
}

//===============================================
// [Main] Analyze response
logger.info("request url: " + requestUrl);

// Fetch restaurant data
fetch_tabelogAPI_response(requestUrl)
        // Parse the data
        .then(parse_tabelogAPI_response)
        // Get image URL from tabeloAPI
        .then(fetch_restaurant_image_url)
        // Get description from restaurant page
        .then(fetch_restaurant_description)
        // Delete unuseful field of data
        .then(delete_unuseful_field)
        // Insert retrieval data to mongo
        .then(insert_restaurant_data)
        // output
        .then(function(data) {
                console.log(data);
        });

Related Posts

Pocket

You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.