欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

记一次node编写爬虫的经历

程序员文章站 2022-06-17 10:25:26
...

记一次node编写爬虫的经历

1. 爬取网站选定

我的学校的新闻站点
页面如下 准备爬取该新闻的标题以及内容(不包括图片)
记一次node编写爬虫的经历

2.分析

2.1 分析新闻列表请求

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

2.2分析新闻列表的jsp

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

2.3分析新闻内容请求

我随意点击了一条新闻,请求如下
记一次node编写爬虫的经历
依旧是同一个链接,后面加上了我们点击新闻的href属性的值

2.4分析新闻内容记一次node编写爬虫的经历

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

3.代码开始

3.1选定插件

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

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条新闻数据,如下
记一次node编写爬虫的经历
本人大三学生,学习经验尚有欠缺,如有错误请指出,如需转载请表明出处,谢谢。

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

相关标签: nodejs爬虫