在學習了 alsotang 大神的 《Node.js包教不包會》後的一個爬蟲小練習,期間也碰到挺多小問題,也學到了不少小東西。「單押×3」前端
這裏是大神的教程地址 github.com/alsotang/no…,node
下面是本身的爬取效果圖git
同時推薦一個谷歌插件 JSONView,會將 JSON 格式的數據轉化成上面圖的格式github
imoocSpider練習源碼express
首先,搭建一個 http 服務數組
var http = require('http');
var express = require('express');
var app = express();
http.createServer(app).listen(8080);
app.get('/', function(req, res) {
//code here...
})
複製代碼
用的是 express
搭建的,固然也能夠用原生的,在這裏本身比較習慣用 express安全
在這裏用到的是 superagent
和 cheerio
來爬取頁面,這裏有相關文檔能夠參考: superagent中文文檔、cheerio,都是來自 cnode 社區,固然英語能力較好的也能夠參考原文檔。在這裏就只貼出這兩個服務器
爬取頁面連接 https://www.imooc.com/course/list?c=fe併發
咱們是要爬取慕課網前端開發八個頁面的課程中的一些信息,可是打開此連接發現每一個頁面只有課程的名稱,並無老師的名稱和一些課程的主要信息。所以咱們還須要獲取並根據每一個課程的 url 進行爬取。app
獲取課程詳情頁連接
那咱們先來爬取八個頁面的全部課程詳情頁的 url
經過點擊對應頁面的按鈕,發現每次都會發送一個新的 get 請求,請求的連接就是對應的頁面,而這裏的連接只有 page
屬性是不一樣的,所以咱們經過動態改變 page
就能夠模擬點擊對應頁來獲取對應頁的信息
var pages = 1;
var baseUrl = 'https://www.imooc.com/course/list/';
var params = {
c: 'fe',
page: page
};
superagent
.get(baseUrl)
.query(params)
.end(function(err, content) {
var topicUrls = [];
var $ = cheerio.load(content.text);
var courseCard = $('.course-card-container');
courseCard.each(function(index, element) {
var $element = $(element);
var href = url.resolve(
homeUrl,
$element.find('.course-card').attr('href')
);
topicUrls.push(href);
});
console.log(topicUrls);
});
複製代碼
這樣就能夠獲取到了第一個頁面的 25 個課程的詳情頁的 url,那要如何獲取八個頁面呢。
async
由於有些網站一般都會有安全限制,不會容許同一個域名有過大的高併發數,所以須要限制併發數,在這裏用咱們用到了 async
這個庫。這裏是其 github
咱們首先把前面代碼封裝成一個函數
var baseUrl = 'https://www.imooc.com/course/list/';
var fetchUrl = function(page, callback) {
count++;
console.log('當前併發數', count);
var params = {
c: 'fe',
page: page
};
superagent
.get(baseUrl)
.query(params)
.end(function(err, content) {
var topicUrls = [];
var $ = cheerio.load(content.text);
var courseCard = $('.course-card-container');
courseCard.each(function(index, element) {
var $element = $(element);
var href = url.resolve(
homeUrl,
$element.find('.course-card').attr('href')
);
topicUrls.push(href);
});
callback(err, topicUrls);
count--;
console.log('釋放併發數後當前併發數', count);
});
};
複製代碼
而後用 async
控制併發數和八個頁面的抓取
var pages = [1, 2, 3, 4, 5, 6, 7, 8];
async.mapLimit(
pages,
5,
function(page, callback) {
fetchUrl(page, callback);
},
function(err, result) {
if (err) console.log(err);
console.log(result)
}
);
});
複製代碼
這樣全部的 url 就被打印出來,這裏要注意一下,async
會自動把第三個函數參數的返回值合併成一個數組給第四個函數參數的 result
參數。剛開始寫的時候我把 topicUrls
聲明在了全局,以致於返回成下面這組數據
爬取課程詳情頁的信息
在咱們有了全部課程詳情頁的 url 後,咱們開始爬取裏面的內容。首先定義一個函數
var fetchMsg = function(topicUrl, callback) {
console.log('開啓新一輪抓取')
superagent
.get(topicUrl)
.end(function(err, content){
var Item = [];
var $ = cheerio.load(content.text);
var title = $('.hd .l').text().trim();//課程名字
var teacher = $('.tit a').text().trim();//老師名字
var level = $('.meta-value').eq(0).text().trim();//難度
var time = $('.meta-value').eq(1).text().trim();//時長
var grade = $('.meta-value').eq(3).text().trim();//評分
Item.push({
title: title,
teacher: teacher,
level: level,
time: time,
grade: grade,
href: topicUrl
})
callback(null, Item);
})
};
複製代碼
而後用 async
控制併發爬取
//result 爲上文中的 result,下面的代碼也都是在上文中的第四個參數中
var topicUrls = result; //獲取全部 url ,可是大數組裏面有 8 個小數組
var Urls = [];
//將大數組合並
for(let i=0,l=topicUrls.length;i<l;i++){
Urls = Urls.concat(topicUrls[i]);
}
async.mapLimit(
Urls,
5,
function(url,callback){
fetchMsg(url, callback);
},
function(err, result) {
//避免亂碼
res.writeHead(200, {'Content-Type': 'text/plain;charset=utf8'})
res.end(JSON.stringify(result));
}
複製代碼
這裏要注意一個小問題,就是 result
獲取到的 url 結構是一個大數組裏麪包含八個小數組,所以須要將其小數組先合併成一個大數組。
天天探索一點點,天天進步一點點。