使用nodejs / async批处理和延迟API调用

时间:2015-01-03 00:17:29

标签: javascript node.js mean-stack

我正在制作一个社交网络图,我想建立一个"六度分离"基于我从API获得的邻接列表的树。

对于每个人,API将返回[id1,id2,id3 ...]形式的朋友数组,这正是我想要的。但问题是,有很多人,API只允许400个电话/ 15分钟。我可以将数据保存在本地数据库中,但我不想在请求时充斥API。

我正在做的伪代码是这样的:

requestCharacter = function(id) {
    is this person in my db already? if true, return;
    else make api call(error, function(){loopFriends(character)}) {
       save character in database
    }
}

loopFriends(character){
   foreach(friend in character.friends) requestCharacter(friend);
}

我已经或多或少地对它进行了编码,并且它工作正常,但由于它不断穿越树木,并且由于人们在彼此的朋友列表中重复出现,因此效率非常低,并且保持不变破坏API限制

所以我想要做的是排队请求,在添加之前检查队列中是否有东西,并且一次批量运行400个或更少的请求。 (因此如果队列中有1200个,它将运行400,等待15分钟,运行400,等待15分钟,运行400 ......)

我尝试将async.js与其队列一起使用,并且我能够将一个吨加载到队列中,但我认为它实际上并没有运行。对于这种情况,最好的方法是什么?

我的实际非排队代码如下:

var lookupAndInsertCharacter = function(id){
  Character.findOne({ 'id': id }, function (err, person) {
    if (err) console.log(err);
    else {
      if(person!=null) {console.log('%s already exists in database, not saved', person.name); getCharacterFriends(id);}
      else insertCharacter(id, function(){getCharacterFriends(id)});
    };
  })
}

var insertCharacter = function(id, callback){
    var url = getCharacterURL(id);
    request(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      var result = JSON.parse(body);
      if(result.status_code != 1 ) {console.log("ERROR status_code: %s. Please wait 15 minutes", result.status_code); return;}
      else {
        var me = new Character(processCharacter(result));
        me.save(function(err){
          if (err) return handleError(err);
        });
        console.log("Saved character "+me.name);
      }  
    } 
    else {
      console.log(error);
    }
  }); 
}

var getCharacterFriends = function(id) {
  Character.findOne({ 'id': id }, function (err, person) {
    if (err) console.log(err);
    else {
      console.log("Getting friends for %s",person.name);
      _.each(person.character_friends, function(d){
        lookupAndInsertCharacter(d);
      });
      console.log("Getting enemies for %s",person.name);
      _.each(person.character_enemies, function(d){
        lookupAndInsertCharacter(d);
      })
    };
  })
}

2 个答案:

答案 0 :(得分:1)

最终为我工作的是对API调用进行速率限制。我用了

https://github.com/wankdanker/node-function-rate-limit

然后我制作了一个有限版本的insertCharacter:

var rateLimit = require('function-rate-limit');

var insertLimited = rateLimit(400, 900000, function (id) {
  insertCharacter(id);
});

答案 1 :(得分:0)

在下面的示例中,我将在FaceBook上发布所有论坛,其中的帖子以及作者的公开个人资料。

为了减慢这个过程,我创建了一个有限的'刮刀'池并保留每个刮刀一段时间,所以我“不能超载FaceBook服务器:)”

对于上面的例子,你可以

  • 将您的游泳池尺寸限制为400 max : 400,并将您的刮刀保留15分钟setTimeout(function(){pool.release(scraper);}, 15*60*1000);
  • 或将您的游泳池尺寸限制为1 max : 1并保留刮刀3.75秒setTimeout(function(){pool.release(scraper);}, 3750);

代码

function saveData (anyJson) {
    // put your Db communication here.
    // console.log(anyJson);
}

function now() {
    instant = new Date();
    return instant.getHours() +':'+ instant.getMinutes() +':'+ instant.getSeconds() +'.'+ instant.getMilliseconds();
}
var graph = require('fbgraph');
console.log(process.argv[2]);
graph.setAccessToken(process.argv[2]);

var poolModule = require('generic-pool');
var pool = poolModule.Pool({
    name     : 'scraper',
    create   : function(callback) {
        console.log(now() +' created scraper');
        // parameter order: err, resource
        callback(null, {created:now()});
    },
    destroy  : function(scraper) { 
        console.log(now() +' released scraper created '+ scraper.created); 
    },
    max      : 10,
    min      : 1, 
    idleTimeoutMillis : 60*60*1000,
    log : false
});

function pooledGraphGet(path,analyse) {
    pool.acquire(function(err,scraper) {
        if (err) {
            console.log(now() +' Could not get a scraper for '+ path);
            throw err;
        }
        graph.get(path,function(err,res) {
            if (err) {
                console.log(now() +' Could not get '+ path +' using scraper created '+ scraper.created);
                throw err;
            } else {
                console.log(now() +' Got '+ path +' using scraper created '+ scraper.created);
                setTimeout(function(){pool.release(scraper);}, 60*1000);
                analyse(res);
            }
        });
    });
}

pooledGraphGet('me?fields=friends,groups', function(res) {
    res.groups.data.forEach(function(group) {
        saveData (group);
        pooledGraphGet(group.id +'?fields=id,name,members,feed', function(res) {
            if (res.feed) res.feed.data.forEach(function(feed){
                saveData (feed);
                pooledGraphGet(feed.from.id +'?fields=id,name', function(res) {
                    saveData (res);
                });
            });
        });
    });
});