从一个nodejs爬虫来看nodejs异步回调嵌套时数据的安全性[原创,未经授权禁止转载]

代码如下:

/** * Created by Totooria Hyperion on 2016/4/20. */

‘use strict‘;

const fs = require(‘fs‘);const path = require(‘path‘);const cheerio = require(‘cheerio‘);const request = require(‘request‘);

function getCategoryByUrl(url,callback){    request(url,‘utf8‘, function (err,res,body) {        if(!err && res.statusCode == 200){            let $ = cheerio.load(body);            //console.log($(‘.menuList.blog_classList li‘)[0]);            //let len = $(‘.menuList.blog_classList li‘).length;            //for(let i=)            $(‘.menuList.blog_classList ul‘).eq(0).find(‘li‘).slice(1).each(function (index,item) {                let $this = $(item);                let _category = $this.find(‘.SG_dot a‘).text();                let _url = $this.find(‘.SG_dot a‘).attr(‘href‘);                callback(null,_url,_category);            });        } else {            return callback(err,null,null);        }    });}

function getArticleList(url,callback){    request(url,‘utf8‘,function(err,res,body){        if(!err && res.statusCode == 200){            let articleList = [];            let $ = cheerio.load(body);

            $(‘.articleList .articleCell‘).each(function (index,item) {                let $this = $(item);                let _title = $this.find(‘.atc_title a‘);                let _time = $this.find(‘.atc_tm‘)                articleList.push({                    title:_title.text(),                    url:_title.attr(‘href‘),                    time:_time.text()                });            });

            let nextUrl = $(‘.SG_pgnext a‘).attr(‘href‘);            if (nextUrl && !body.includes(‘您要访问的页面不存在或被删除‘)) {                getArticleList(nextUrl, function (err,list) {                    if(err){                        return callback(err,null);                    }                    callback(null,articleList.concat(list));                });            } else {                callback(null,articleList);            }        } else {            return callback(err,null);        }    });}

function getDetailByUrl(url,callback){    console.log(url);    request(url,‘utf8‘, function (err,res,body) {        if(!err && res.statusCode == 200){            let $ = cheerio.load(body,{                decodeEntities:false            });

            let content;

            try{                content = $(‘.articalContent‘).html().trim();            } catch(e){

            }            callback(null,content);        } else {            return callback(err,null);        }    });}

const templateStr = fs.readFileSync(path.join(__dirname,‘template.html‘),‘utf8‘);

getCategoryByUrl(‘http://blog.sina.com.cn/s/articlelist_5858394150_0_1.html‘, function (err,url,category) {    if(err){        throw err;    }

    getArticleList(url, function (err,articleList) {        if(err){            throw err;        }        try {            fs.mkdirSync(path.join(__dirname,category));        } catch (e) {

        }

        console.log(category + ‘:‘ + articleList.length);        articleList.forEach(function (item,index) {

            getDetailByUrl(item.url, function (err,content) {                if(err){                    throw err;                }

                let html = templateStr.replace(‘<%content%>‘,content);                //console.log(html);                console.log(path.join(__dirname,category, item.title + ‘.html‘));                fs.writeFile(path.join(__dirname,category, item.title + ‘.html‘),html, function () {                });            });        });    });});

看到所构造的每一个回调函数都有如下特点:

与C++里的lambda函数无法访问局部变量不同,nodejs中的回调函数可以向按照代码块从内向外逐级查找

所以,如上述代码所示,回调函数中的category可以访问到最外层函数的category参数。

而根据函数的声明可知,回调函数的执行是放在request的回调函数里面的,这意味着回调函数执行的时候,其外层函数已经执行完毕了,按道理其参数也应该被释放了,并且由于回调函数并不以category作为参数,这种情况在C++当中就不会建立一个category副本,因此C++并不支持lambda函数访问局部变量。

但是为什么JS可以访问呢?

莫非JS会为所有引用到的数据建立副本?

以下代码否认了这一点:

{    let d = [1,2,3];

    let tt = function (t){        return function(){            console.log(t[0]);        }    };

    let nn = tt(d);    d[0] = 5;    nn();}

nn的输出结果为5,这意味着,nn得到的这一lambda函数并没有为t[0]建立副本,此处t[0]的值是根据指向外面的d的指针来获得的。

并且数组d被代码块包裹起来,意味着它不是全局变量,在大括号外部输出d会显示d is not defined。

从上面的测试可以得到两个结论:

1.JS只会为函数的参数建立副本。

2.JS对任何数据的解析都是通过指针来进行的,只不过复杂数据类型的赋值为浅拷贝,而简单数据类型赋值的赋值为深拷贝。(函数体的赋值为深拷贝,我测试过,至于为何,我暂时也不清楚,毕竟没看过JS解释器源码。但是在实际应用中经验告诉我这是个好事。)

根据上述两个例子可以得出结论,nodejs中的异步回调函数之所以可以访问已经结束的函数当中的局部变量,是因为JS解释器在运行代码之前就将这些变量的地址传给了回调函数,从而使得这些变量的引用计数并没有变成0。因此回调函数才可以继续访问它们。

这便带来了一定风险,类似C++当中指向互斥锁临界区中的变量的指针如果被暴露在外就会导致互斥锁并不能确保数据读写不会产生冲突。C++使用RAII机制来解决这一问题。

对于JS,由于JS自带全局锁,所以并不会产生读写冲突,但却会导致数据混乱。

特别是在团队开发的情况下,每个人都需要尽可能地避免使用全局变量,为的就是防止变量名相同带来的数据混乱。

所以,虽然在nodejs当中可以像代码1一样访问,我个人还是倾向于将其改写为如下形式:

function f(arg1,arg2,arg3,function(err,arg3,arg4){})

其中err和arg4在函数f当中通过运算得到,而arg3是函数f外部的局部变量,虽然不在f的参数和其回调函数的参数中添加arg3也可以访问,但是添加了arg3就可以确保在回调函数内存有arg3的副本,当外部arg3被其它线程改变的时候,回调函数可以安全使用自己的副本而不用担心使用到错误的数据。

如下面代码所示:

function f(a,callback){
    callback(a,a+1);
}

{
    let b = 100;
    let a = 5;
    let c = 20;

    f(a, function (a,b) {
        setTimeout(function () {
            console.log(a);
            console.log(b);
            console.log(c);
        },1000)
    })
    a = 50;
}

输出结果为

5

6

20

如果不作为参数:

function f(a,callback){
    callback();
}

{
    let b = 100;
    let a = 5;
    let c = 20;

    f(a, function () {
        setTimeout(function () {
            console.log(a);
            console.log(b);
            console.log(c);
        },1000)
    })
    a = 50;
}

输出结果为:

50

100

20

可见,构造setTimeout的时候内部lambda函数所得到的a和b都不是一个简单的数字,而是指针。因此延迟后由于a指向的数据从5变成50,因此输出50。

时间: 2024-12-29 01:36:32

从一个nodejs爬虫来看nodejs异步回调嵌套时数据的安全性[原创,未经授权禁止转载]的相关文章

【nodeJS爬虫】前端爬虫系列

写这篇 blog 其实一开始我是拒绝的,因为爬虫爬的就是cnblog博客园.搞不好编辑看到了就把我的账号给封了:). 言归正传,前端同学可能向来对爬虫不是很感冒,觉得爬虫需要用偏后端的语言,诸如 php , python 等.当然这是在 nodejs 前了,nodejs 的出现,使得 Javascript 也可以用来写爬虫了.由于 nodejs 强大的异步特性,让我们可以轻松以异步高并发去爬取网站,当然这里的轻松指的是 cpu 的开销. 要读懂本文,其实只需要有 能看懂 Javascript 及

NodeJS爬虫系统初探

NodeJS爬虫系统 NodeJS爬虫系统 0. 概论 爬虫是一种自动获取网页内容的程序.是搜索引擎的重要组成部分,因此搜索引擎优化很大程度上是针对爬虫而做出的优化. robots.txt是一个文本文件,robots.txt是一个协议,不是一个命令.robots.txt是爬虫要查看的第一个文件.robots.txt文件告诉爬虫在服务器上什么文件是可以被查看的,搜索机器人就会按照该文件中的内容来确定访问的范围. 一般网站的robots.txt查找方法: 例如www.qq.com http://ww

简单实现nodejs爬虫工具

约30行代码实现一个简单nodejs爬虫工具,定时抓取网页数据. 使用npm模块 request---简单http请求客户端.(轻量级) fs---nodejs文件模块. index.js var request = require('request'); var fs = require("fs"); var JJurl = "https://recommender-api-ms.juejin.im/v1/get_recommended_entry?suid=6bYFY7I

AFHTTPClient的异步回调模式

以前第一个版本,ios的http都用的同步模式,在很多地方会导致线程阻塞,自己开发了一个简易的AFHTTPClient的异步回调模式. 回调的protocol: @protocol MyAFNetworkingResponse <NSObject> @required -(void) MyHttpResponse:(NSString*)ret Type:(NSString*)strType returnData:(NSObject*)retData; @end AFHTTPClient的异步通

Nodejs爬虫进阶教程之异步并发控制

Nodejs爬虫进阶教程之异步并发控制 之前写了个现在看来很不完美的小爬虫,很多地方没有处理好,比如说在知乎点开一个问题的时候,它的所有回答并不是全部加载好了的,当你拉到回答的尾部时,点击加载更多,回答才会再加载一部分,所以说如果直接发送一个问题的请求链接,取得的页面是不完整的.还有就是我们通过发送链接下载图片的时候,是一张一张来下的,如果图片数量太多的话,真的是下到你睡完觉它还在下,而且我们用nodejs写的爬虫,却竟然没有用到nodejs最牛逼的异步并发的特性,太浪费了啊. 思路 这次的的爬

如何优雅的处理Nodejs中的异步回调

前言 Nodejs最大的亮点就在于事件驱动, 非阻塞I/O 模型,这使得Nodejs具有很强的并发处理能力,非常适合编写网络应用.在Nodejs中大部分的I/O操作几乎都是异步的,也就是我们处理I/O的操作结果基本上都需要在回调函数中处理,比如下面的这个读取文件内容的函数: fs.readFile('/etc/passwd', function (err, data) { if (err) throw err; console.log(data); }); 那,我们读取两个文件,将这两个文件的内

Nodejs异步回调的优雅处理方法

这篇文章主要介绍了Nodejs异步回调的优雅处理方法,本文使用了ES6中的新特性,用一种十分优雅的方式解决了回调问题,需要的朋友可以参考下 前言 Nodejs最大的亮点就在于事件驱动, 非阻塞I/O 模型,这使得Nodejs具有很强的并发处理能力,非常适合编写网络应用.在Nodejs中大部分的I/O操作几乎都是异步的,也就是我们处理I/O的操 作结果基本上都需要在回调函数中处理,比如下面的这个读取文件内容的函数: 复制代码 代码如下: fs.readFile('/etc/passwd', fun

第一个nodejs爬虫:爬取豆瓣电影图片

第一个nodejs爬虫:爬取豆瓣电影图片存入本地: 首先在命令行下 npm install request cheerio express -save; 代码: var http = require('https'); //使用https模块 var fs = require('fs');//文件读写 var cheerio = require('cheerio');//jquery写法获取所得页面dom元素 var request = require('request');//发送reques

用NodeJs做一个小爬虫

作者:北京起步科技前端研究员,专注分享HTML5 App快速开发工具 WeX5 的黑魔法以及相应的前端技术. 前言 利用爬虫可以做很多事情,单身汉子们可以用爬虫来收集各种妹子情报,撩妹族们可以用爬虫收集妹子想要的小东西,赚大钱的人可以用来分析微博言论与股票涨跌的关系诸如此类的,简直要上天了. 你们感受一下 点我点我: 蠢蠢欲动 抛开机器学习这种貌似很高大上的数据处理技术,单纯的做一个爬虫获取数据还是非常简单的.对于前段er们来说,生在有nodejs的年代真是不要太幸福了,下面就用nodejs来做