码迷,mamicode.com
首页 > Web开发 > 详细

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

时间:2016-04-21 07:22:10      阅读:172      评论:0      收藏:0      [点我收藏+]

标签:

代码如下:

/**
* 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。

 

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

标签:

原文地址:http://www.cnblogs.com/Totooria-Hyperion/p/5415415.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!