4

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

我正在制作一个会跟踪链接的蜘蛛,并且在随机数量的爬网 URL 之后我开始收到这些错误。发生这种情况时,Node 不会为您提供堆栈跟踪,但我很确定我没有任何递归错误。

我正在使用请求来获取 URL,我正在使用cheerio来解析获取的 HTML 并检测新链接。堆栈溢出总是发生在cheerio 内部。当我将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/' );
4

2 回答 2

0

看起来你在那里进行了一些递归。递归函数调用最终会超出堆栈,因为这是存储函数指针的地方。

所以这是如何发生的:

  1. 在 request.get 回调中提取调用
  2. 在 mongo.pages.find 回调中提取调用推送
  3. 在 while 循环内推送调用 fetch

这个循环似乎会重复,直到你用完堆栈。

在您的情况下,堆栈在您调用时运行得非常低,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); })并且不应再用完堆栈。

话虽如此,如上所述,您的代码中更有可能存在错误,因此请先查看。

于 2012-09-25T01:32:38.937 回答
0

你减self.concurrent -= 1;得太早了,你应该extract在所有异步内容完成后在函数内减量。这是一个突出的问题。不知道它是否会解决它。

于 2014-08-01T15:44:46.423 回答