1

我正在使用jsdom(Node.js 的网络抓取库)发出 1 到 10 个 Web 请求。它是这样的:

app.get('/results', function(req, res) {

jsdom.env(
  "http://website1.com",
  ["http://code.jquery.com/jquery.js"],
  function (errors, window) {
    // scrape website #1
  }
);

jsdom.env(
  "http://website2.com",
  ["http://code.jquery.com/jquery.js"],
  function (errors, window) {
    // scrape website #2
  }
);

jsdom.env(
  "http://website3.com",
  ["http://code.jquery.com/jquery.js"],
  function (errors, window) {
    // scrape website #3
  }
);
}

res.render('results', { items: items });
}

仅在完成所有jsdom请求并收集了所需的所有信息之后,如何运行res.render() ?在同步世界中,这显然不是问题,但由于 javascript 是异步的,因此 res.render() 将在任何jsdom回调完成之前运行。

4

1 回答 1

5

天真的解决方案

对于少量刮擦,您可以采用的“幼稚”解决方案是嵌套所有内容(在最后一次刮擦的回调中开始每个刮擦,最后一次回调包含渲染方法。)

scrape
  cb: scrape
     cb: scrape
        cb: render all results

当然,这变得乏味且难以辨认。(而且一切都是串联运行的,而不是并行运行的,这不会很快。)

更好的解决方案

更好的解决方案是编写一个函数来计算render所有返回结果和调用的数量。这是一种实现:

function parallel_cb(total, finalCallback) {
    var done = 0;
    var results = [];
    return function(result) {
        done += 1;
        results.push(result);
        if (total == done) finalCallback(results);
    }
}

要在您的示例中使用它:

app.get('/results', function(req, res) {
    var myCallback = parallel_cb(
        sitesToScrape.count, // or 3 in this case
        function(items) {res.render('results', { items: items })});

    jsdom.env(
      "http://nodejs.org/dist/",
      ["http://code.jquery.com/jquery.js"],
      function (errors, window) {
        // do some scraping
        myCallback(result_from_scrape);
      }
    );

    jsdom.env(
      "http://nodejs.org/dist/",
      ["http://code.jquery.com/jquery.js"],
      function (errors, window) {
        // more scraping
        myCallback(result_from_scrape);
      }
    );

    jsdom.env(
      "http://nodejs.org/dist/",
      ["http://code.jquery.com/jquery.js"],
      function (errors, window) {
        // even more scraping
        myCallback(result_from_scrape);
      }
    );
});

最佳解决方案

您应该真正学习使用现有的并行/异步库,而不是自己编写,正如@almypal 在对您的问题的评论中所建议的那样。

async如文档中所述,您可以做一些更整洁的事情:https ://github.com/caolan/async#parallel

或者,如果您的所有抓取实际上都在结果页面中寻找相同的元素,您甚至可以对要抓取的 URL 数组进行并行映射:https ://github.com/caolan/async#maparr-iterator-callback

您的每个抓取都可以使用异步并行方法提供的回调函数来返回其抓取的结果。最后的 [可选] 回调将包含您对render所有项目的调用。

编辑:你要求的例子

这是您的代码,直接翻译到async库中:

var async = require("async");

app.get('/results', function(req, res) {
    async.parallel( // the first argument is an array of functions
      [
        // this cb (callback) is what you use to let the async
        // function know that you're done, and give it your result
        function (cb) { 
          jsdom.env(
            "http://nodejs.org/dist/",
            ["http://code.jquery.com/jquery.js"],
            function (errors, window) {
              // do some scraping      

              // async's callback expects an error for the first
              // param and the result as the second param
              cb(null, result_from_scrape); //No error
            }
          );
        },
        function (cb) { 
          jsdom.env(
            "http://nodejs.org/dist/",
            ["http://code.jquery.com/jquery.js"],
            function (errors, window) {
              // more scraping
              cb(null, result_from_scrape);
            }
          );
        },
        function (cb) { 
          jsdom.env(
            "http://nodejs.org/dist/",
            ["http://code.jquery.com/jquery.js"],
            function (errors, window) {
              // even more scraping
              cb(null, result_from_scrape);
            }
          );
        }
      ],
      // This is the "optional callback". We need it to render.
      function (err, results) {
        // If any of the parallel calls returned an error instead
        // of null, it's now in the err variable.
        if (err) res.render('error_template', {error: err});
        else res.render('results', { items: results });
      });
});
于 2013-02-01T10:42:24.067 回答