最近有空又重新看了vue2.0的文档,发现变动还是挺大的,多了虚拟dom,也像react有了对应JSX语法的render函数了,整个vue对象的生命周期也有了改动,而为了迎合2.0的标准版脚手架而不是每次都用2.0的简化版脚手架,我便决定开始慢慢的学习Node.js,其实之前也大概看过文档,但是总觉得没有需求,也不知从何下手,而现在个人发现语言和框架其实是为了解决问题而诞生了,那么学习其实也是在问题产生之后,需要解决问题而去主动学习。
我们最终的目的其实都是解决问题,这才是关键。
学习node的过程中,不带着需求学习更是茫然,看了文档却不知道去干什么~然而我突然很想写爬虫,总觉得很酷,所以毅然决定将这个需求带进node,用node来实现就好,又可以满足自己的需求又可以学习node,更是一举两得。
教程来自于一个简单的爬虫程序,是爬北大软件学院的新闻网的一些信息和图片,我已经收藏至我的收藏夹了,大家有兴趣可以看看,在这里我也把链接贴出来,由于是原创,所以不能随便转载
IT_石头的csdn博客-手把手教你做爬虫—基于NodeJs
坑爹的是,我爬了一次后,竟然就报超时了,刷新一下原网站,发现他们网站竟然崩了….有点逗了~
如果大家没看原作者的教程,那么请大家自行创建项目,去安装必要的东西
npm install request
这是node的第三方工具,不是自带的http的request,大家注意。
这里先声明一下,如果大家去爬的是完全同步请求的网站,记得要加载cheerio模块,它可以使你在node上实现jquery的方法,方便你去爬取对应标签,在原作者的教程中便是这个例子,大家自己去看看吧,A站由于电影列表有点特殊,是异步的get请求,所以这里做法是有区别的,如果按照原作者的思路走,你爬出来的电影列表一定是空的,因为请求地址已经不一样了。
好了,爬之前我们先看看A站电影列表的请求格式
我们可以明确看到,请求是get的方式,而参数也很明显为channelId、sort、pageSize、pageNo,其中页码为pageNo控制,所以后续我们的get请求去改变pageNo即可,主机名为www.acfun.tv,请求地址为主机名底下的list/getlist,如果为post请求,那么我们根据node的文档将代码中改为http.request()的方式,methods方法为post即可,大家自己去研究吧,这里我就不说了~
好了,理解之后,于是我觉得去爬一些自己想爬的东西,所以就从经常上的网站开始吧,acfun的电影列表!show me my code
//我将其取名为acfun-movie.js
var http = require('http');//加载http模块
var fs = require('fs'); //加载文档流模块
var request = require('request'); //加载request模块
//初始页码
var page = 1;
//初始url,经过多次切换页码,我们确定只有pageNo是改变的,其他都是既定的
var url = 'http://www.acfun.tv/list/getlist?channelId=96&sort=0&pageSize=20&pageNo='+page;
//封装一层函数,我们优雅一点
function fetchPage(x){
startRequest(x);
}
function startRequest(x){
//利用http模块向服务器发起一次get请求
http.get(x,function(res){
var allData = ''; //用于存储请求网页的整个内容
res.setEncoding('utf-8');
//监听data事件,每次取一块数据
res.on('data',function(chunk){
allData += chunk;
});
//监听end事件,如果整个网页内容的html都获取完毕,就执行回调函数
res.on('end',function(){
//将取回的json格式字符串转为对象,并访问数据数组
var resData = JSON.parse(allData).data.data;
console.log('正在爬取第'+page+'页');
saveContent(resData,page);//用于保存电影列表的内容
saveImg(resData,page); //用于保存电影列表的图片
//下一篇的url
var nextLink = 'http://www.acfun.tv/list/getlist?channelId=96&sort=0&pageSize=20&pageNo='+page;
//亮点:递归函数控制爬虫的结束
if(page<=10){
fetchPage(nextLink);
}
});
}).on('error',function(err){
console.log(err);
});
}
function saveContent(resData,page){
var str = '';
for(var i=0;i<resData.length;i++){
//构造内容
str += resData[i].title +'\r\n'+'链接:http://www.acfun.tv'+resData[i].link+'\r\n'+'up主'+resData[i].username+' 上传时间:'+resData[i].contributeTimeFormat+' 被观看次数:'+resData[i].viewCount+'\r\n\r\n'
}
//存储文本内容-参数为文件名,文件内容,文件格式,错误控制的回调函数
fs.appendFile('./data/第'+page+'页电影列表.txt',str,'utf-8',function(err){
if(err){
console.log(err);
}
});
}
function saveImg(resData,page){
for(var i=0;i<resData.length;i++){
//此正则为中文取名,将名字'\'替换为'/'否则读取会被当做路径格式读取,现已注释,因为中文取名多容易发生错误
// var img_title = resData[i].title;
// img_title = img_title.replace(/\\/g,'/');
//图片名,对应页码与排位
var img_title = page+'-'+(i+1);
//图片类型处理
var img_type = resData[i].coverImage;
img_type = img_type.slice(img_type.lastIndexOf('.'));
//图片请求路径
var img_src = resData[i].coverImage;
console.log(img_src);
//用request请求去验证图片超链接是否存在,这一步即使没有也可以运行,但最好利用head请求去 请求头部验证超链接是否存在与被修改,以防万一
request.head(img_src,function(err,res,body){
if(err){
console.log(err)
}
});
//request利用pipe流去存储图片,一定要写错误控制,否则会报错,未写错误控制
console.log(img_title+img_type);
request(img_src).pipe(fs.createWriteStream('./image/'+img_title+img_type)).on('error',function(err){
if(err){
console.log(err);
}
});
console.log(img_title+'Done');
}
}
//执行函数
fetchPage(url);
最后在项目文件夹下运行node acfun-movie.js
跑起来即可,这里就算完成了,希望大家不要用cv大法,毕竟只有自己打出来理解了才是自己的东西~
有一些图片的爬取会报错:no such file or directory,没有这个文件或这个目录,其实是A站的问题,因为。。。
如你所见,图片地址也是网络请求,这里我就没处理了,因为毕竟还是很少的~如果你有兴趣就自己去处理下吧~
补上完成图