1

jsdom (Node.js の Web スクレイピング ライブラリ)を使用して、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 は非同期であるため、jsdomコールバックが完了する前に res.render() が実行されます。

4

1 に答える 1

5

素朴な解決策

少数のスクレイプに対して採用できる「素朴な」解決策は、すべてをネストすることです (最後のスクレイプのコールバックで各スクレイプを開始し、最後のコールバックには render メソッドが含まれます)。

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

もちろん、それは退屈で判読不能になります。(そして、すべてが並列ではなく直列で実行されるため、それほど高速ではありません。)

より良い解決策

renderより良い解決策は、すべてが返されたときに返された結果と呼び出しの数をカウントする関数を作成することです。1 つの実装を次に示します。

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 に答える