“凡是能用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