我对Node.js的内部工作方式不是很熟悉,但据我所知,当你进行过多的函数调用时,会出现“超出最大调用堆栈大小”错误。
我正在制作一个跟随链接的蜘蛛,我开始在随机数量的抓取网址之后获取这些错误。发生这种情况时,Node不会给你一个堆栈跟踪,但我很确定我没有任何递归错误。
我正在使用request来获取网址,而 使用cheerio来解析所提取的HTML并检测新链接。堆栈溢出总是发生在cheerio内部。当我为htmlparser2交换cheerio时,错误消失了。 Htmlparser2要轻得多,因为它只是在每个打开的标签上发出事件,而不是解析整个文档和构建树。
我的理论是,cheerio吃掉了堆栈中的所有内存,但我不确定这是否可能?
这是我的代码的简化版本(仅供阅读,不会运行):
var _ = require('underscore');
var fs = require('fs');
var urllib = require('url');
var request = require('request');
var cheerio = require('cheerio');
var mongo = "This is a global connection to mongodb.";
var maxConc = 7;
var crawler = {
concurrent: 0,
queue: [],
fetched: {},
fetch: function(url) {
var self = this;
self.concurrent += 1;
self.fetched[url] = 0;
request.get(url, { timeout: 10000, pool: { maxSockets: maxConc } }, function(err, response, body){
self.concurrent -= 1;
self.fetched[url] = 1;
self.extract(url, body);
});
},
extract: function(referrer, data) {
var self = this;
var urls = [];
mongo.pages.insert({ _id: referrer, html: data, time: +(new Date) });
/**
* THE ERROR HAPPENS HERE, AFTER A RANDOM NUMBER OF FETCHED PAGES
**/
cheerio.load(data)('a').each(function(){
var href = resolve(this.attribs.href, referer); // resolves relative urls, not important
// Save the href only if it hasn't been fetched, it's not already in the queue and it's not already on this page
if(href && !_.has(self.fetched, href) && !_.contains(self.queue, href) && !_.contains(urls, href))
urls.push(href);
});
// Check the database to see if we already visited some urls.
mongo.pages.find({ _id: { $in: urls } }, { _id: 1 }).toArray(function(err, results){
if(err) results = [];
else results = _.pluck(results, '_id');
urls = urls.filter(function(url){ return !_.contains(results, url); });
self.push(urls);
});
},
push: function(urls) {
Array.prototype.push.apply( this.queue, urls );
var url, self = this;
while((url = self.queue.shift()) && this.concurrent < maxConc) {
self.fetch( url );
}
}
};
crawler.fetch( 'http://some.test.url.com/' );
答案 0 :(得分:0)
看起来你在那里进行了一些递归。递归函数调用最终会超出堆栈,因为这是存储函数指针的地方。
以下是它的发生方式:
这个循环似乎重复,直到你用完了。
在您的情况下,当您致电cheerio.load
时,堆栈的运行速度非常低,这就是为什么它在那时就用完了。
尽管您很可能想要检查这是否是您想要的错误,但为了在不使用直接递归的情况下在nodejs中获得相同的效果,请使用:
process.nextTick(functionToCall)
。
它将保留封闭的函数,该函数将其指针弹出堆栈,但在下一个滴答时调用functionToCall
。
您可以在noderepl中尝试:
process.nextTick(function () { console.log('hello'); })
会立即打印'你好'。
它与setTimeout(functionToCall, 0)
相似,但优先于它。
关于您的代码,您可以将self.fetch(url)
替换为process.nextTick(function () { self.fetch(url); })
,并且不应再耗尽堆栈。
话虽如此,如上所述,您的代码中更有可能存在错误,因此请先查看。
答案 1 :(得分:0)
您过早地使用self.concurrent -= 1;
递减,在所有异步内容完成后,您应该在extract
函数内递减它。这是一个突出的问题。不确定它是否会解决它。