JAVA程序员今日看点

nodejs 爬取新浪微博用户信息

2017-03-23  本文已影响562人  Zoemings

1. 摘要

微博作为新生网络应用形式, 在最近几年中得到了迅猛的发展,由于技术门槛低,微博的同质化问题非常严重,因此,发掘信息的主动发布者,是非常必要和有意义
的。
大致思路:根据一个人的关注递归查找出其他被关注者的关注,将信息存入数据库后,根据数据库的用户爬取对应的微博以及微博对应的评论,分析评论内容以及评论数量评估相应用户的影响指数。

2. 实现工具

  • superagent
  • request
  • cherrio
  • mongodb

3.数据库定义

var userSchema = new Schema({
    uid:{type:String,unique:true},  //id唯一
    sex:String,                     //性别
    name: String  ,                 //昵称
    followCnt:  Number,             //所在地
    fansCnt:Number,                 //关注数
    weiboCnt:Number,                //微博数
    score:Number                    //评分
});
var newsSchema = new Schema({
    nId:{type:String,unique:true},
    content: String,    //内容
    author: {           //作者
      type: Schema.Types.ObjectId,
      ref: 'User'
    },
    isforward: Boolean,  //是否转发
    expandInfo:String    //转发内容
});
var commentSchema = new Schema({
    //当前评论微博
    news: {type: ObjectId, ref: 'News'},
    content: String,       //评论内容
    name:String              //评论人
});

4. 页面分析

1.分析页面结构

关注分析.png

2.页面元素分析

代码分析.png

3.用cheerio 处理获取到的html

首先将所有的每一个关注者<li>标签内容存储到数组中,data存储用户id,姓名,性别等基本信息,detail存储详细信息,包括关注数,粉丝数,微博数,identify存储会员,v标识等信息

var follows = $('.follow_item ').toArray();
     for (var i = 0; i < follows.length; i++) {
          var data = $(follows[i]).attr("action-data").split("&");
          var detail = $(follows[i]).find(".info_connect span .count");
          var identify=$(follows[i]).find(".info_name a");

前提条件:

  1. 基本信息为三即此人有姓名,id,性别;
  2. 详细信息为三即此人有粉丝,关注人,微博数
  3. 标识信息大于三即此人至少为微博会员或者大V
    目的:为了排除普通用户和特殊用户如下图用户


    排除.PNG
 if (data.length === 3 && detail.length === 3 && identify.length>=3 ) {

分析出结果并存储,ps:用户id存储为flag前6位+uid,原因:实践表明,访问"http://weibo.com/" + uid+ "/follow"的时间比http://weibo.com/p/" + flag+uid+ "/follow慢得多

var flag=$(follows[i]).find('.info_name a').attr('href').split(/[_=]/)[2].substr(0,6);
var user={};
  user.uid = flag+data[0].split("=")[1];
  user.name = data[1].split("=")[1];
  user.sex = data[2].split("=")[1];
  user.followCnt = parseInt($(detail[0]).text());
  user.fansCnt = parseInt($(detail[1]).text());
  user.weiboCnt = parseInt($(detail[2]).text());
  saveUser(user);

5. 模块代码

function start() {
    var userName = "XXXX";  //用户
    var password = "XXXXX"; //密码
    var preLoginUrl = "http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su=&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.11)&_=" + (new Date()).getTime();
   async.waterfall([
        function (callback) {
            request({
                "uri": preLoginUrl,
                "encoding": "utf-8"
            }, callback);
        },
        function (res, body, callback) {
            var responseJson = getJsonObj(body);
            var loginUrl = 'http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.18)';
            var loginPostData = {
                entry: "weibo",
                gateway: "1",
                from: "",
                savestate: "7",
                useticket: "1",
                vsnf: "1",
                su: "",
                service: "miniblog",
                servertime: "",
                nonce: "",
                pwencode: "rsa2",
                rsakv: "xxxx",
                sp: "",
                sr: "xxxx",
                encoding: "UTF-8",
                prelt: "282",
                url: "http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack",
                returntype: "META"
            };
            //用户名base64加密
            loginPostData.su = new Buffer(userName).toString('base64');
            //密码RSA加密
            var rsaKey = new RsaEncrypt();
            rsaKey.setPublic(responseJson.pubkey, '10001');
            var pwd = rsaKey.encrypt([responseJson.servertime, responseJson.nonce].join("\t") + "\n" + password);
            loginPostData.sp = pwd;
            loginPostData.servertime = responseJson.servertime;
            loginPostData.nonce = responseJson.nonce;
            loginPostData.rsakv = responseJson.rsakv;
            //表单post
            request.post({
                "uri": loginUrl,
                "encoding": null,  
                form: loginPostData
            }, callback);
        },

        function (res, body, callback) {
            body = iconv.decode(body,"GBK");
            var errReason = /reason=(.*?)\"/;
            var errorLogin = body.match(errReason);
            if (errorLogin) {
                callback("登录失败,原因:" + errorLogin[1]);
            }
            else {
                var urlReg = /location\.replace\(\'(.*?)\'\)./;
                var urlLoginAgain = body.match(urlReg);           
                if (urlLoginAgain) {
                    request({
                        "uri": urlLoginAgain[1],
                        "encoding": "utf-8"
                    }, callback);
                }
                else {
                    callback("match failed");
                }
            }
        },
        function (res, body, callback) {
            console.log("开始分析... ");
             getfollow("xxxx"); //源用户
        }
    ], function (err) {
        console.log(err)
    });
}

用户信息保存,保存成功后递归查询该用户的关注者信息

function saveUser(user){
    var user = new User({
        uid:user.uid,
        sex:user.sex,
        name:user.name,
        followCnt:user.followCnt,
        fansCnt:user.fansCnt,
        weiboCnt:user.weiboCnt
    });
    user.save(function (err, usr) {
        if (err) {
            console.log("失败");
        }
        else {
            console.log(user);
            getfollow(user.uid);
            sleep(1000);
            console.log("保存成功");
        }
    });
}

参考:cheerio API

6. 遇到的困难及措施

递归并发访问会造成短时间大量请求,IP很容易被封,如下:

哎.PNG

1种是如上输入验证码可以继续,1种是你们太快了,之后就有一段时间完全无法访问微博,试想想,递归查找由一个用户源开始,爬取所有关注者的所有关注人,因此并发是难以想象的,这时setTimeout没有用,自动给你优化掉了。简单的sleep函数能有效阻塞。

function sleep(milliSeconds) {
    var startTime = new Date().getTime();
    while (new Date().getTime() < startTime + milliSeconds);
};

参考:nodejs入门

7. 爬取热门微博

根据爬取存储进数据库的大V信息选取微博粉丝数目>500万,微博数大于100的大V的热门微博

function reptileMove(url,callback){
    //延迟毫秒数
    var delay = parseInt((Math.random() * 10000000000000) % 7000, 10);
    setTimeout(function() {
        var ids=url.split(" ");
        var urls="http://weibo.com/p/"+ids[0]+"?profile_ftype=1&is_hot=1#_0";
        superagent
            .get(urls)
            .set('Accept', 'application/json')
            .set('User-Agent', 'BaiduSpider')
            .end(function (err, res) {
                // sleep(1500);
                count=count+1;
                console.log(count+" "+urls);
                var body =  res.text;
                body.replace(/(\\n|\\t|\\r)/g, " ").replace(/\\/g, "");
                var $ = cheerio.load(body);
                var x=$('.WB_feed_detail .WB_detail ').toArray();
                var y=$('.WB_feed_handle ul').toArray();
                setTimeout(function () {
                for(var i = 0; i < x.length; i++) {
                    var info=($(y[i]).find('li')).toArray();
                    var news={};
                    news.content=($(x[i]).find('.WB_text').text()).replace(/^(\s|\xA0)+|(\s|\xA0)+$/g, '');
                    news.time=($(x[i]).find('.WB_from  a').attr('title'));
                    news.nid=($(x[i]).find('.WB_from  a').attr('href')).split("?")[0];
                    news.commentCnt=($(info[2]).find('span em')).text().substring(1);       //评论
                    news.praiseCnt=($(info[3]).find('span em')).text().substring(1);        //点赞
                    news.author=ids[1];
                    saveNews(news);
                }
                }, delay)

            });
        callback(null,url +'Call back content');
    }, delay);
}

8. 代码地址

https://github.com/HZNU-QUANTA/NODE-ZMM/tree/master/sina

上一篇 下一篇

猜你喜欢

热点阅读