Node.js Request+Cheerio实现一个小爬虫-基础功能实现3:流程控制及并发控制

Node.js Request+Cheerio实现一个小爬虫-基础功能实现1:内容抓取
Node.js Request+Cheerio实现一个小爬虫-基础功能实现2:文件写入
Node.js Request+Cheerio实现一个小爬虫-基础功能实现3:流程控制及并发控制
Node.js Request+Cheerio实现一个小爬虫-番外篇:代理设置

前一篇文章最后遇到了流程控制的问题。虽然我们可以用emmit或者是用计数器的方法来进行控制,但是这种方法局限性比较强一些。因此在这里可以用到Node的流程控制Aysnc模块。

Aysnc的 GitHub主页
这里是一些example还有中文注释,写得十分详细。


我们的设想是在抓取完全部请求之后,对抓取的结果进行排序后写入文件。所以在这里可以使用map函数。上面例子中对于map的解释。(当时就只想着用map了,其实用each也是可以的)

map: 对集合中的每一个元素,执行某个异步操作,得到结果。所有的结果将汇总到最终的callback里。与each的区别是,each只关心操作不管最后的值,而map关心的最后产生的值。

官方文档中map的用法

map(coll, iteratee, callbackopt)

官方文档中给出的参数:

Name Type Description
coll Array / Iterable / Object A collection to iterate over.
iteratee function A function to apply to each item in coll. The iteratee is passed a callback(err, transformed) which must be called once it has completed with an error (which can be null) and a transformed item. Invoked with (item, callback).
callback function A callback which is called when all iteratee functions have finished, or an error occurs. Results is an Array of the transformed items from the coll. Invoked with (err, results).

上面这段话是什么意思呢?可以看看下面的代码。


const async = require('async');

// 创建一个url的请求列表,用于map函数中的 coll 
var urlLists = createUrlLists(BASE_URL, PAGE_MAX);

async.map(urlLists, function(url, callback) {
    // 这里的参数url便是urllists中的每一项
    var option = {
        url: url,
        headers: ... // 省略一下
    }

    // 请求用的函数与之前相同,其实写到外面会更好看
    request(option, function(error, response, body) {
    if (!error && response.statusCode == 200) {

        var $ = cheerio.load(body, {
            ignoreWhitespace: true,
            xmlMode: true
        });

        var shopInfo = {
            pageNo: option.url.match(/g\d+p(\d+)/)[1],
            pageURL: option.url,
            info: []
        };

        var shopList = $('div#shop-all-list').find('a[data-hippo-type = "shop"]');

        shopList.each(function(no, shop) {
            let info = {};
            info.no = no + 1;
            info.name = $(shop).attr('title');
            info.url = $(shop).attr('href');
            shopInfo.info.push(info);
        });

        shopLists.push(shopInfo);

        // 这里的callback是对函数自身的循环
        callback(null, url, option);
    }
});

}, function(err, result) {
    // 这里是要在前面的函数全部完成之后再调用
    // 因此shopLists在这个时候已经是了全部url数据的状态了
    // 这里的result其实就是我们传入的urlLists
    // 但是我们没有对urlLists进行操作,所以我们也可以使用each方法来实现同样的功能

    shopLists.sort(function(shop1, shop2) {
        return parseInt(shop1.pageNo) - parseInt(shop2.pageNo);
    });

    fs.exists(FILE_PATH + FILE_NAME, function(exits) {
        if (exits) {
            fs.appendFile(FILE_PATH + FILE_NAME, shopLists, 'utf-8', function(err) {
                if (err) {
                    console.error("文件生成时发生错误.");
                    throw err;
                }
            });
        } else {
            fs.writeFile(FILE_PATH + FILE_NAME, shopLists, 'utf-8', function(err) {
                if (err) {
                    console.error("文件生成时发生错误.");
                    throw err;
                }
                console.info('文件已经成功生成.');
            });
        }
    });
});

这样一来,我们就可以对获取到的内容排序后写入到文件中去了。但是,嗯,是的,问题又来了。如果我们只对3,4个url发出请求,那么问题还不大。但如果是30,40个甚至是上百个请求呢?现在的网站对于爬虫或者恶意的访问都会做防范,如果一次性请求太大或者太过于频繁的话,就很容易被403了。所以呢,就需要考虑到并发数的问题了。(做爬虫也要考虑到对方服务器,不要太过火)并发数的问题同样可以用Async模块来解决。

并发控制可以参照这篇文章
只要把map方法,换成mapLimit方法就可以了。

mapLimit(coll, limit, iteratee, callbackopt)

先看看官方文档的解释。其实比起map就多了一个limit参数,这个limit参数就是用来控制并发数的。

Name Type Description
coll Array / Iterable / Object A collection to iterate over.
limit number The maximum number of async operations at a time.
iteratee function A function to apply to each item in coll. The iteratee is passed a callback(err, transformed) which must be called once it has completed with an error (which can be null) and a transformed item. Invoked with (item, callback).
callback function A callback which is called when all iteratee functions have finished, or an error occurs. Results is an array of the transformed items from the coll.Invoked with (err, results).

于是稍作修改便可以了。

async.mapLimit(urlLists, 5, function(url, callback) {
    // 这里多了并发的最高上限
        // 现在一起发出的请求最多为5条
    var option = {
        url: url,
        headers: ...
    }

    // 请求用的函数与之前相同,其实写到外面会更好看
    request(option, function(error, response, body) {
        // 这里与之前相同
    });
});

到此为止,基本上这么一个爬虫就算是完成了。当然,要避免403的最有效手段应该还是用代理吧。这个大概可以作为一个番外写一下。

最后这一次是GitHub项目
做完看看,还有很多不成熟的地方,还需要再努力一下啊。

你可能感兴趣的:(Node.js Request+Cheerio实现一个小爬虫-基础功能实现3:流程控制及并发控制)