Node.js中的大量数据是否会超过堆栈大小?

Node.js中的大量数据是否会超过堆栈大小?,node.js,callstack,Node.js,Callstack,我不太熟悉Node.js的内部工作原理,但据我所知,当您进行太多函数调用时,会出现“超过最大调用堆栈大小”错误 我正在制作一个蜘蛛,它会跟随链接,我开始在随机数目的爬网URL后出现这些错误。发生这种情况时,Node不会给您堆栈跟踪,但我非常确定我没有任何递归错误 我用它来获取URL,我用它来解析获取的HTML并检测新链接。堆栈溢出总是发生在cheerio内部。当我用干杯换错的时候,错误消失了。Htmlparser2要轻得多,因为它只在每个打开的标记上发出事件,而不是解析整个文档并构建树 我的理论

我不太熟悉Node.js的内部工作原理,但据我所知,当您进行太多函数调用时,会出现“超过最大调用堆栈大小”错误

我正在制作一个蜘蛛,它会跟随链接,我开始在随机数目的爬网URL后出现这些错误。发生这种情况时,Node不会给您堆栈跟踪,但我非常确定我没有任何递归错误

我用它来获取URL,我用它来解析获取的HTML并检测新链接。堆栈溢出总是发生在cheerio内部。当我用干杯换错的时候,错误消失了。Htmlparser2要轻得多,因为它只在每个打开的标记上发出事件,而不是解析整个文档并构建树

我的理论是cheerio耗尽了堆栈中的所有内存,但我不确定这是否可能

下面是我的代码的简化版本(仅供阅读,不会运行):

var\=require('下划线');
var fs=需要('fs');
var urllib=require('url');
var请求=要求(“请求”);
var cheerio=需要('cheerio');
var mongo=“这是到mongodb的全局连接。”;
var maxConc=7;
变量爬虫={
同期:0,,
队列:[],
获取:{},
获取:函数(url){
var self=这个;
self.concurrent+=1;
self.fetched[url]=0;
get(url,{timeout:10000,pool:{maxSockets:maxConc}},函数(err,response,body){
self.concurrent-=1;
self.fetched[url]=1;
self.extract(url,body);
});
},
摘录:函数(引用者、数据){
var self=这个;
var url=[];
insert({u id:referer,html:data,time:+(新日期)});
/**
*错误发生在这里,在随机数目的抓取页面之后
**/
加载(数据)('a')。每个(函数(){
var href=resolve(this.attribs.href,referer);//解析相对URL,不重要
//仅当href尚未提取、它不在队列中且不在此页面上时,才保存href
如果(href&&!uu.has(self.fetched,href)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
url.push(href);
});
//检查数据库,看看我们是否已经访问了一些URL。
find({u id:{$in:url},{u id:1}).toArray(函数(err,results){
如果(错误)结果=[];
else results=u.pull(结果,id');
url=url.filter(函数(url){return!uz.contains(results,url);});
self.push(url);
});
},
推送:函数(URL){
Array.prototype.push.apply(this.queue,URL);
var url,self=this;
while((url=self.queue.shift())和&this.concurrent
看起来您在那里得到了一些递归。递归函数调用最终将超过堆栈,因为这是存储函数指针的地方

下面是它是如何发生的:

  • 在request.get回调中提取提取调用
  • 提取调用推入mongo.pages.find回调
  • push调用在while循环中获取
  • 这个循环似乎会一直重复,直到堆栈用完为止

    在您的情况下,当您调用
    cheerio.load
    时,堆栈的运行速度非常低,这就是为什么它会立即运行

    尽管您很可能想检查这是否是一个bug或您想要的东西,但为了在nodejs中获得相同的效果而不使用直接递归,需要使用:

    process.nextTick(函数调用)

    它将离开封闭的函数,该函数将指针从堆栈中弹出,但在下一次勾选时调用
    functionToCall

    您可以在noderepl中尝试:

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

    将立即打印“hello”

    它与设置超时(functionToCall,0)相似,但优于它

    与您的代码相关,您可以将
    self.fetch(url)
    替换为
    process.nextTick(函数(){self.fetch(url);})
    ,并且不应再耗尽堆栈


    也就是说,如上所述,您的代码中更有可能存在错误,因此请首先查看该错误。

    您正在使用
    self.concurrent-=1递减
    太早了,在完成所有异步操作后,应该在
    extract
    函数中减小它。这是一个突出的问题。不确定它是否能解决这个问题。

    我不认为像你解释的那样有任何递归。reqest.get将实际请求包装在process.nextTick()中。此外,extract()和push()是从回调中调用的,回调也发生在进程的下一个滴答声中。例如,当我调用fetch时,它只调用request.get(),而request.get()不会执行任何进一步的递归。好的一点,我怀疑是递归,因为这是堆栈耗尽的最可能原因。但是,如果这些回调是真正异步的(即,不要立即回调),那么您是对的,递归不会成为堆栈耗尽的原因。我在cheerio中遇到了相同的错误。。你找到原因了吗?不幸的是没有。对于这个项目来说,只使用htmlparser2就足够了——并且错误不会发生在它身上。好的。。最后,我不得不手动操作正在解析的html文本,然后将其传递给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/' );