2012-09-24 25 views
4

我不是很熟悉Node.js的內部工作,但據我所知,當你進行過多的函數調用時,會得到'Maximum call stack size exceeded'錯誤。Node.js中的大量數據能否超過堆棧大小?

我正在製作一個蜘蛛,它會跟隨鏈接,並且我在隨機數量的抓取的URL後開始獲取這些錯誤。發生這種情況時,節點不會給你一個堆棧跟蹤,但我很確定我沒有任何遞歸錯誤。

我使用request來獲取URL,我使用cheerio來解析獲取的HTML並檢測新的鏈接。 cheerio中總是發生堆棧溢出。當我爲htmlparser2換上啦啦隊時,錯誤消失了。 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

我得到與cheerio相同的錯誤..你找出原因? – Lloyd

+0

不幸的是沒有。對於這個項目,僅僅使用htmlparser2就足夠了 - 並且錯誤不會發生。 – disc0dancer

+0

ok ..最後,我不得不手動操縱html文本,在將它傳遞給cheerio之前,我解析了它,剝離了我並不關心的所有標記。 – Lloyd

回答

0

看起來你得到了一些遞歸對那裏發生的。遞歸函數調用最終會超過堆棧,因爲這是存儲函數指針的地方。

所以在這裏是怎麼回事了:

  1. 取電話在回調request.GET中
  2. 提取調用push在mongo.pages.find回調提取
  3. 推通話while循環中取

這個循環似乎重複,直到你用完堆棧。

在你的情況下,當你撥打cheerio.load時,堆棧運行的很低,這就是爲什麼它在那裏和那裏耗盡。

雖然你很可能要檢查,如果這是一個錯誤或東西,你打算,爲了獲得在的NodeJS相同的效果,而無需使用直遞歸是使用:

process.nextTick(functionToCall)

它將離開封閉的函數,該函數將其指針從堆棧彈出,但在下一個打勾處調用functionToCall

,您可以嘗試在noderepl:

process.nextTick(function() { console.log('hello'); })

將打印 '你好' 馬上。

它很喜歡setTimeout(functionToCall, 0),但是比它更受歡迎。

有關您的代碼,您可以用process.nextTick(function() { self.fetch(url); })代替self.fetch(url),並且不應再用完堆棧。

這就是說,如上所述,代碼中存在一個錯誤的可能性更大,因此請首先仔細研究一下。

+0

我不認爲任何遞歸正在進行解釋。 reqest.get將實際請求包裝在process.nextTick()中。另外,extract()和push()是從回調中調用的,這也發生在進程的下一個tick上。例如,當我調用fetch時,它只會調用request.get(),它不會進行任何進一步的遞歸。 – disc0dancer

+0

好點,我懷疑遞歸,因爲這是堆棧用完的最可能的原因。如果這些回調是真正的異步(即不立即回調),那麼你是正確的,遞歸不會是你用完堆棧的原因。 –

0

你在遞減self.concurrent -= 1;爲時尚早,在完成所有的異步工作後,你應該在extract函數中遞減它。這是一個棘手的問題。不知道它是否會解決它。

相關問題