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 <optional> 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 <optional> 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项目
做完看看,还有很多不成熟的地方,还需要再努力一下啊。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容