“凡是能用JS 写出来的,最终都会用JS 写”,这是一个非常著名的定律,用在爬虫这里再合适不过了。
一说到爬虫很多人都会想到python,的确,python语法简洁,还有scrapy这一类强大的工具可以使用。
但是如果只是想写一个小爬虫,爬取论坛里的几张帖子,帖子里面的几个楼层,然后合成一篇文章。这点小数据量使用scrapy就有点杀鸡用牛刀了,而且还得设置一堆东西,非常麻烦,不够灵活。
而JavaScript就非常适合这一类小爬虫,首先是自带异步架构,能同时爬取多张网页内容,效率上来说比python高多了,我用两个语言写过爬取代理ip的爬虫,当JavaScript爬完时吓了我一跳,这速度快极了。
当然,python也可以通过开启多线程、多协程来实现同时爬取多张网页,但是这就比默认就异步的JavaScript麻烦多了。
所以,如果想简单、高效地写个小爬虫,非JavaScript莫属。
有多快多简单呢?现在就来写个豆瓣top250的爬虫,爬取10张网页,250部电影的名字、评分和封面地址;
1.如何安装
要通过js写爬虫,需要用到三个模块,request、cheerio和fs,其中fs内置了,只需要安装前两个即可,安装命令:
npm install request cheerio
2.获取网页内容
request可以链接网页,爬取内容,这里我们只需要给它传递两个参数就行,一个为url(网址),另一个为回调函数;
request会向回调函数传递三个参数,分别是error(错误信息),response(响应信息),body(网页内容):
var request = require('request')
var cheerio = require('cheerio')
var fs = require('fs')
var movies = []
var requstMovie = function(url){
request('https://movie.douban.com/top250',function(error, response, body)){
//res.statusCode 为200则表示链接成功
if(error === null && response.statusCode === 200){
console.log('链接成功')
//使用cheerio来解析body(网页内容),提取我们想要的信息
var e = cheerio.load(body)
//通过分析网页结构,我们发现豆瓣每部电影都通过item属性隔开
var movieDiv = e('.item')
//通过for循环来提取每部电影里的信息
for (let i = 0; i < movieDiv.length; i++) {
//takeMovie函数能提取电影名称、评分和封面
let movieInfo = takeMovie(movieDiv[i])
log('正在爬取' + movieInfo.name)
//将提取到的电影放入数组
movies.push(movieInfo)
}
}
})
}
3.提取电影信息
通过创建一个类来包含我们想要的属性,在每次调用takeMovie函数提取信息时都会初始化这个类,然后赋值给相应的属性;
之后放入movies数组里;
//电影的类
var movie = function(){
this.id = 0
this.name = ''
this.score = 0
this.pic = ''
}
var takeMovie = function(div){
var e = cheerio.load(div)
//将类初始化
var m = new movie()
m.name = e('.title').text()
m.score = e('.rating_num').text()
var pic = e('.pic')
//cheerio如果要提取某个属性的内容,可以通过attr()
m.pic = pic.find('img').attr('src')
m.id = pic.find('em').text()
return m
}
4.爬取所有top250
现在要爬取所有的top250信息,总共有10张网页,每张包含25部电影信息,创建一个函数来生成每张网页的网址,然后通过request进行爬取:
var top250Url = function(){
let l = ['https://movie.douban.com/top250']
var urlContinue = 'https://movie.douban.com/top250?start='
let cont = 25
for (let i = 0; i < 10; i++) {
l.push(urlContinue+cont)
cont += 25
}
return l
}
//爬取所有网页
var __main = function(){
var url = top250Url()
for (let i = 0; i < url.length; i++) {
requstMovie(url[i])
}
}
__main()
5.异步架构的坑
当我们爬取完所有的网页后就会发现,movies里的电影信息并不按我们爬取的顺序,这也是异步架构一个需要注意的大坑;
在爬取第一张网页时,JavaScript不会等到处理结束才接着爬第二张,有时候各个网页返回的速度有所差异,会造成先爬取的不一定会先出结果,因此在电影排序上会出现混乱;
所以我们还需要对爬取下来的内容重新进行排序,然后保存:
//sortMovie回调函数能比较两个对象属性大小
var sortMovie = function(id){
return function(obj ,obj1){
var value = obj[id]
var value1 = obj1[id]
return value - value1
}
}
//保存文件
var saveMovie = function(movies){
var path = 'movie.txt'
var data = JSON.stringify(movies, null, 2)
fs.appendFile(path, data, function(error){
if(error == null){
log('保存成功!')
} else {
log('保存失败',error)
}
})
}
我们需要等到所有网页都爬取分析完才执行sortMovie和saveMovie函数,由于JavaScript是异步,即使这两个函数放在最底部也会在分析完之前执行;
一般会通过Promise来控制异步,但是为了方便,这里我们通过if来判断,在每次爬取网页后,都会判断movies里是否包含250条信息,如果有则说明全部爬取到了:
var requstMovie = function(url){
request(url, function(error, response, body){
if (error === null && response.statusCode === 200){
var e = cheerio.load(body)
var movieDiv = e('.item')
for (let i = 0; i < movieDiv.length; i++) {
let movieInfo = takeMovie(movieDiv[i])
log('正在爬取' + movieInfo.name)
movies.push(movieInfo)
}
//判断movies数量
if (movies.length === 250){
//通过sort将数组内每两个元素放入比较函数
var sortM = movies.sort(sortMovie('id'))
//保存文件
saveMovie(sortM)
}
} else {
log('爬取失败', error)
}
})
}
到这里,爬虫已经写完了,来运行一下:
完整代码可以通过github查看:DoubanMovies.JS
也可以访问我的网站,获取更多文章:Nothlu.com