记一次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
上一篇: 测试环境搭建—JDK环境配置
下一篇: 拖拽至目标区域