记一次node编写爬虫的经历

记一次node编写爬虫的经历

1. 爬取网站选定

我的学校的新闻站点
页面如下 准备爬取该新闻的标题以及内容(不包括图片)
这里写图片描述

2.分析

2.1 分析新闻列表请求

新闻列表请求
从图中分析可以看到,该GET请求返回的是一个jsp,page参数为页数其他未知,但这两个足够完成我所需要的爬取。
那我就可以直接从利用node的cherrio插件分析jsp即可得到我所需要的数据

2.2分析新闻列表的jsp

我查看了每条新闻元素
新闻连接
可以得到:每条新闻的href均指向一个jsp并且传递了一个NewsID
那么,我们可以从这里直接分析到每条新闻的ID(还有标题 哈哈哈)

2.3分析新闻内容请求

我随意点击了一条新闻,请求如下
新闻内容请求
依旧是同一个链接,后面加上了我们点击新闻的href属性的值

2.4分析新闻内容新闻内容

没啥好分析的 ,直接获取就好了

3.代码开始

3.1选定插件

superagent、cheerio、还有async(这个是后添的,哼哼,我以为我自己可以写回调解决,但是。。。。。。回调地狱你懂的)
数据库:mongo
实现该爬虫的流程为:
流程示意

3.2 code
3.2.1 获取页数
var getPageNum = function (callBack) {
    superagent
        .get("http://news.hebeu.edu.cn/news_more.asp?lm2=1")
        .charset("gbk")
        .set("Accept","*/*")
        .end(function (err,res) {
            if(res){
                var dom = htmlparser2.parseDOM(res.text, options);
                var $ = cheerio.load(dom);
                pageNum =                $("#cframe1").find('select').children("option").length;
                callBack(pageNum)
            }
        });
};

接受参数是一个回调,这是为了实现下一步的获取新闻。
警告:这里利用了两个其他插件,包括superagent-charset、还有htmlparser2
因为该网站采用gbk编码,而superagent默认为utf-8编码,所以需要进行编码转换
htmlparser2解决了整个网页的编写错误(如未关闭标签等)
通过 获取页面当中select下的option标签的个数即可获取页数 (pageNum = 170)

3.2.2 抓取新闻列表
var getNews = function (pageNum,callBack) {
    superagent
        .get("http://news.hebeu.edu.cn/news_more.asp?page="+(pageNum++)+"&word=&lm=&lm2=1&lmname=&open=&n=&hot=&tj=")
        .charset("gbk")
        .set("Accept","*/*")
        .end(function (err,res) {
            console.log("...开始抓取第"+ pageNum + "页的新闻");
            if(res){
                var dom = htmlparser2.parseDOM(res.text, options);
                var $ = cheerio.load(dom);
                var news = [];
                $("#cframe1").find('a[target="_blank"]').each(function(i, el) {
                    var data = {
                        newsId:$(this).attr('href'),
                        title:$(this).children("font").text()
                    };
                    news.push(data);
                });
                async.mapLimit(news,5,function (news,callback) {
                    getNewsInfo(news,callback)
                },function (err,result) {
                    console.log("@@@第"+ pageNum + "页的新闻抓取完毕@@@");
                    concurrencyCount = 0;
                    callBack(null,pageNum);
                })
            }
            else{
                concurrencyCount = 0;
                callBack("响应异常",pageNum);
            }
        });
};

需要讲的是cherrio 的each函数为:迭代一个cheerio对象,为每个匹配元素执行一个函数。
通过这个函数我们可以迭代整个新闻列表的超链接标签,然后获取href属性的值(newsId)以及标题(title)。
再利用async的mapLimit属性进行并发获取新闻内容(该函数接受参数为迭代的值数组/对象等,并发数也就是同时执行的方法数,执行的方法,当所有迭代函数完成时调用的回调,或者有错误的回调。(callback也是每一个函数完成的标志,只有执行该callback函数async才确认该函数执行完毕)。
所以在获取完所有的新闻内容后返回获取新闻列表的callBack来标志获取下一页的新闻列表
个人理解:async的mapLimit是创建一个并发池,假设并发数是5,那么就是同时执行五个函数,其中任意一个函数执行完毕后,下一个函数立即进入该并发池,依次类推。而不是创建五个为一组的同步执行,组组之间进行异步执行的流程。

3.2.3 抓取新闻内容
var getNewsInfo = function (news,callback) {
    concurrencyCount++;
    superagent
        .post("http://news.hebeu.edu.cn/"+news.newsId)
        .charset("gbk")
        .set("Accept","text/html,application/xhtml+xml,
      application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
        .end(function (err,res) {
            console.log("%%%正在抓取标题为"+news.title+"的新闻" + "当前并发量:"+concurrencyCount+"%%%");
            if(res){
                var dom = htmlparser2.parseDOM(res.text, options);
                var $ = cheerio.load(dom);
                news.newInfo = $("#ccontent").text();
                insertData(mongoConn,news,function (result) {
                    console.log(result)
                });
                concurrencyCount--;
                callback(null,news.newsId)
            }
            else{
                callback("响应异常",news.newsId);
            }
        });
};

数据库插入代码

var insertData = function (db,data,callback) {
    var collection = db.collection('newsInfo');
    collection.insert(data,function (err,result) {
        if(err){
            console.log(err);
            return;
        }
        else{
            callback(data.newsId+"插入成功");
        }
    })
};

这里不过多解释了,请大家自己看吧

3.2.4 程序执行
async.series([function (callBack) {
    MongoClient.connect(DB_DONN_STR,function (err,db) {
        if(err){
            callBack(true,"数据库连接失败")
        }
        else{
            mongoConn = db;
            callBack(null,"数据库连接成功");
        }
    });
},function (callBack) {
    getPageNum(function (pageNum) {
        async.timesSeries(pageNum,function (pageNum,callBack) {
            getNews(pageNum,callBack);
        },function (err,result) {
            console.log("###所有新闻抓取完毕,请等待数据库完成操作###")
        });
    });
    callBack(null,"开始爬取我的大学新闻")
}],function (err,result) {
    console.log(result)
});

程序开始时先创建了mongo的链接,然后再执行获取新闻列表
这里的timesSeries用来指定执行异步函数次数的方法与time函数区别在于一次只执行一个异步函数。

4.总结

好了,整个网站新闻的爬取过程就是这样,结果为3600条新闻数据,如下
爬取结果
本人大三学生,学习经验尚有欠缺,如有错误请指出,如需转载请表明出处,谢谢。

更新 请将所有的域名换为 http://xuanchuan.hebeu.edu.cn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值