「凡是能用JS 寫出來的,最終都會用JS 寫」,這是一個很是著名的定律,用在爬蟲這裏再合適不過了。javascript
一說到爬蟲不少人都會想到python,的確,python語法簡潔,還有scrapy這一類強大的工具可使用。java
可是若是隻是想寫一個小爬蟲,爬取論壇裏的幾張帖子,帖子裏面的幾個樓層,而後合成一篇文章。這點小數據量使用scrapy就有點殺雞用牛刀了,並且還得設置一堆東西,很是麻煩,不夠靈活。python
而JavaScript就很是適合這一類小爬蟲,首先是自帶異步架構,能同時爬取多張網頁內容,效率上來講比python高多了,我用兩個語言寫過爬取代理ip的爬蟲,當JavaScript爬完時嚇了我一跳,這速度快極了。git
固然,python也能夠經過開啓多線程、多協程來實現同時爬取多張網頁,可是這就比默認就異步的JavaScript麻煩多了。github
因此,若是想簡單、高效地寫個小爬蟲,非JavaScript莫屬。npm
有多快多簡單呢?如今就來寫個豆瓣top250的爬蟲,爬取10張網頁,250部電影的名字、評分和封面地址;數組
要經過js寫爬蟲,須要用到三個模塊,request、cheerio和fs,其中fs內置了,只須要安裝前兩個便可,安裝命令:多線程
npm install request cheerio
複製代碼
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)
}
}
})
}
複製代碼
經過建立一個類來包含咱們想要的屬性,在每次調用takeMovie函數提取信息時都會初始化這個類,而後賦值給相應的屬性; 以後放入movies數組裏;app
//電影的類
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
}
複製代碼
如今要爬取全部的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()
複製代碼
當咱們爬取完全部的網頁後就會發現,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