javascript - 在 NodeJS 中使用嵌套回调时遇到问题

标签 javascript node.js callback cheerio

我正在编写一个程序,该程序可以抓取网站中的链接,然后抓取这些链接以获取信息。为了抓取网站,必须先登录。因此顺序是:登录 -> 抓取链接索引 -> 抓取信息链接

登录函数的回调打印一个空数组{ results: [], hasMore: true },所以我的代码有问题(抓取部分有效):

var request = require('request');
var request = request.defaults({jar: true}); // necessary for persistent login
var cheerio = require('cheerio');

var url1 = "https://example.org/torrents/browse/index/";
var loginUrl = "https://example.org/user/account/login/";

var credentials = {
    username: 'user1',
    password: 'passpass'
};

login(function (result) {
    console.log(result);
});

function login(callback) {
    request.post({
        uri: loginUrl,
        headers: { 'content-type': 'application/x-www-form-urlencoded' },
        body: require('querystring').stringify(credentials)
    }, function(err, res, body){
        if(err) {
            console.log("Login error");
            return;
        }
        scrapeTorrents(url1, function (result) {
            callback(result);
        });
    });
}

function scrapeTorrents(url, callback) {
    request(url, function(err, res, body) {
        if(err) {
            console.log("Main scrape error");
            return;
        }
        var links = []
        var $ = cheerio.load(body);
        $('span.title').each(function(i, element){
            var title = $(this);
            var a = $(this).children().eq(0);
            var detailsUrl = a.attr('href');
            //console.log(detailsUrl);
            links.push(detailsUrl);
        });
         scrapeTorrentDetails(links, function (result) {
             callback(result);
         });
    });
}

function scrapeTorrentDetails(links, callback) {
    var results = [];

    function getDetails(url) {
        request(url, function(err, res, body) {
                if(err) {
                    console.log("Detail scrape error");
                    return;
                }
                console.log("Scraping: " + url);
                var $ = cheerio.load(body);
                var tds = $('td');
                var title = $(tds).get(1).firstChild.data;
                var hash = $(tds).get(3).firstChild.data.trim();
                var size = $(tds).get(9).firstChild.data;
                //  console.log(tds.length);
                if (tds.length > 23) {
                    var rlsDate = $(tds).get(23).firstChild.data || '';;
                    var genres = $(tds).get(27).firstChild.data || '';;
                    var runtime = $(tds).get(31).firstChild.data || '';;
                    if ( $(tds).get(33).firstChild != null) {
                        var plot = $(tds).get(33).firstChild.data || '';;
                    }
                    var rating = $('#imdb_rating').parent().next().text() || '';; // of 10
                    var imdb_id = $('[name=imdbID]').get(0).attribs.value || '';;
                    var cover = $('#cover').children().eq(0).get(0).attribs.href || '';;
                    var thumb = $('[alt=Cover]').get(0).attribs.src || '';;
                    if (typeof cover == 'undefined') {
                        cover = thumb;
                    }
                } else {
                    var rlsDate = "notfound";
                    var genres = "notfound";
                    var runtime = "notfound";
                    var plot = "notfound";
                    var rating = "notfound"; // of 10
                    var imdb_id = "notfound";
                    var cover = "notfound";
                    var thumb = "notfound";
                }

                var movie = {
                    type: 'movie',
                    imdb_id: imdb_id,
                    title: title,
                    year: rlsDate,
                    genre: genres,
                    rating: rating,
                    runtime: runtime,
                    image: thumb,
                    cover: cover,
                    synopsis: plot,
                    torrents: {
                        magnet: 'magnet:?xt=urn:btih:' + hash + '&tr=http://tracker.example.org:2710/a/announce',
                        filesize: size
                    }
                };

                results.push(movie);
            });
    }

    for (var i=0; i<links.length; i++){
            getDetails("https://example.org" + links[i]);
    }

    callback( {
        results: results,
        hasMore: true
    });
}

也许 Q promise 会更好。我如何在上面的代码中实现它?

如果您想知道代码的用途,我计划修改 Popcorn-time 以使用另一个 torrent 跟踪器(没有 API)。

谢谢

最佳答案

这段代码的主要问题是:

for (var i=0; i<links.length; i++){
        getDetails("https://example.org" + links[i]);
}

callback( {
    results: results,
    hasMore: true
});

getDetails() 是异步的,但您只需调用它 links.length 次并继续 - 就像它们都已完成一样。因此,在调用回调并尝试传递结果之前,getDetails() 中的任何请求都不会完成。但是,尚未填写任何结果,因此它们将为空。

您的代码中到处都有所有这些其他嵌套回调(根据需要),但您却在这个地方失败了。在使用结果调用最终回调之前,您需要知道所有 getDetails() 调用何时完成。

此外,您还必须决定是否可以并行调用所有 getDetails() 调用(所有调用同时进行),或者您真正想要做的是调用一个,等待它完成,然后调用下一个,依此类推...现在您将它们全部放在一起进行中,如果目标服务器不立即拒绝那么多请求,那么这可以工作。

<小时/>

有几种潜在的策略可以解决这个问题。

  1. getDetails() 添加回调,然后记录从 getDetails() 获得 links.length 回调的时间 并且仅当整个计数完成后才调用最终回调。

  2. 更改 getDetails() 以返回 promise 。然后,您可以使用诸如 links.map(getDetails) 之类的东西来创建一个 Promise 数组,然后您可以使用 Promise.all() 来了解它们何时全部完成.

就我个人而言,我会更改您的所有代码以使用 Promise,并且我会使用 Bluebird Promise 库,因为它的额外功能(例如 Promise.map())使这一切变得更加简单。

这里有一个修复程序,它向 getDetails() 添加回调,然后计算已完成的数量:

function scrapeTorrentDetails(links, callback) {
    var results = [];

    function getDetails(url, done) {
        request(url, function(err, res, body) {
                if(err) {
                    console.log("Detail scrape error");
                    done(err);
                    return;
                }
                console.log("Scraping: " + url);
                var $ = cheerio.load(body);
                var tds = $('td');
                var title = $(tds).get(1).firstChild.data;
                var hash = $(tds).get(3).firstChild.data.trim();
                var size = $(tds).get(9).firstChild.data;
                //  console.log(tds.length);
                if (tds.length > 23) {
                    var rlsDate = $(tds).get(23).firstChild.data || '';;
                    var genres = $(tds).get(27).firstChild.data || '';;
                    var runtime = $(tds).get(31).firstChild.data || '';;
                    if ( $(tds).get(33).firstChild != null) {
                        var plot = $(tds).get(33).firstChild.data || '';;
                    }
                    var rating = $('#imdb_rating').parent().next().text() || '';; // of 10
                    var imdb_id = $('[name=imdbID]').get(0).attribs.value || '';;
                    var cover = $('#cover').children().eq(0).get(0).attribs.href || '';;
                    var thumb = $('[alt=Cover]').get(0).attribs.src || '';;
                    if (typeof cover == 'undefined') {
                        cover = thumb;
                    }
                } else {
                    var rlsDate = "notfound";
                    var genres = "notfound";
                    var runtime = "notfound";
                    var plot = "notfound";
                    var rating = "notfound"; // of 10
                    var imdb_id = "notfound";
                    var cover = "notfound";
                    var thumb = "notfound";
                }

                var movie = {
                    type: 'movie',
                    imdb_id: imdb_id,
                    title: title,
                    year: rlsDate,
                    genre: genres,
                    rating: rating,
                    runtime: runtime,
                    image: thumb,
                    cover: cover,
                    synopsis: plot,
                    torrents: {
                        magnet: 'magnet:?xt=urn:btih:' + hash + '&tr=http://tracker.example.org:2710/a/announce',
                        filesize: size
                    }
                };

                results.push(movie);
                done();
            });
    }

    var doneCnt = 0;
    for (var i=0; i<links.length; i++){
        getDetails("https://example.org" + links[i], function() {
            ++doneCnt;
            if (doneCnt === links.length) {
                callback( {
                    results: results,
                    hasMore: true
                });
            }
        });
    }

}

关于javascript - 在 NodeJS 中使用嵌套回调时遇到问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33425739/

相关文章:

javascript - 在 Javascript/NodeJs 中无需链接代码的多个数据库调用

python - 谷歌云视觉响应nodejs的非最大抑制

node.js - PATH变量中的 Electron 为空?

javascript - 路由器应用程序的回调未调用 Node.Js

python - 过程在导入时的执行方式与在其 native 模块中运行时的执行方式不同

javascript - 如何在 jQuery 中切换部分 href?

javascript - 如何创建两个变量的变量

javascript - $location.path 在表单提交后不会改变路线

javascript - 使用 Node js 动态构建 JSON

javascript - 许多异步函数应该等待第一个函数完成