我正在制作一个社交网络图,我想建立一个"六度分离"基于我从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);
})
};
})
}
答案 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服务器:)”
对于上面的例子,你可以
max : 400
,并将您的刮刀保留15分钟setTimeout(function(){pool.release(scraper);}, 15*60*1000);
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);
});
});
});
});
});