最近在作一個書城項目,數據用爬蟲爬取,百度了一下找到這個網站,以擇天記這本小說爲例。html
爬蟲用到了幾個模塊,cheerio,superagent,async。node
superagent是一個http請求模塊,詳情可參考連接。mysql
cheerio是一個有着jQuery相似語法的文檔解析模塊,你能夠簡單理解爲nodejs中的jQuery。sql
async是一個異步流程控制模塊,在這裏咱們主要用到async的mapLimit(coll, limit, iteratee, callback)express
async.mapLimit(urls, 10, function (url, callback) { fetchUrl(url, callback, id) }, function (err, results) { //TODO })
第一個參數coll是一個數組,保存了小說的章節url,第二個參數limit是控制併發數,第三個參數iteratee接受一個回調函數,該回調函數的第一個參數就是單獨某一章的url,第二個參數也是一個回調函數,這個回調函數執行後會把結果(在這裏就是每一章的內容)保存到第四個參數callback的results中,results是一個數組,保存了全部章節的內容。npm
咱們在fetchUrl獲取章節數據。數組
首先咱們要根據小說的主頁url獲取全部章節的url保存到數組urls中:併發
superagent.get(url) .charset('gbk') //該網站編碼爲gbk,用到了superagent-charset .end(function (err, res) { var $ = cheerio.load(res.text); //res.text爲獲取的網頁內容,經過cheerio的load方法處理後,以後就是jQuery的語法了 let urls = [] total = $('#list dd').length console.log(`共${$('#list dd').length}章`) $('#list dd').each(function (i, v) { if (i < chapters) { urls.push('http://www.zwdu.com' + $(v).find('a').attr('href')) } })
fetchUrl函數app
function fetchUrl(url, callback, id) { superagent.get(url) .charset('gbk') .end(function (err, res) { let $ = cheerio.load(res.text) //obj爲構建的包含章節信息的對象 callback(null, obj) //將obj傳遞給第四個參數中的results }) }
完整代碼:異步
/** * Created by tgxh on 2017/7/4. */ const cheerio = require('cheerio') const express = require('express') const app = express() const superagent = require('superagent') require('superagent-charset')(superagent) const async = require('async'); let total = 0 //總章節數 let id = 0 //計數器 const chapters = 10 //爬取多少章 const url = 'http://www.zwdu.com/book/8634/' //去除先後空格和 轉義字符 function trim(str) { return str.replace(/(^\s*)|(\s*$)/g, '').replace(/ /g, '') } //將Unicode轉漢字 function reconvert(str) { str = str.replace(/(&#x)(\w{1,4});/gi, function ($0) { return String.fromCharCode(parseInt(escape($0).replace(/(%26%23x)(\w{1,4})(%3B)/g, "$2"), 16)); }); return str } function fetchUrl(url, callback, id) { superagent.get(url) .charset('gbk') .end(function (err, res) { let $ = cheerio.load(res.text) const arr = [] const content = reconvert($("#content").html()) //分析結構後分割html const contentArr = content.split('<br><br>') contentArr.forEach(elem => { const data = trim(elem.toString()) arr.push(data) }) const obj = { id: id, err: 0, bookName: $('.footer_cont a').text(), title: $('.bookname h1').text(), content: arr.join('-') //因爲須要保存至mysql中,不支持直接保存數組,因此將數組拼接成字符串,取出時再分割字符串便可 } callback(null, obj) }) } app.get('/', function (req, response, next) { superagent.get(url) .charset('gbk') .end(function (err, res) { var $ = cheerio.load(res.text); let urls = [] total = $('#list dd').length console.log(`共${$('#list dd').length}章`) $('#list dd').each(function (i, v) { if (i < chapters) { urls.push('http://www.zwdu.com' + $(v).find('a').attr('href')) } }) async.mapLimit(urls, 10, function (url, callback) { id++ fetchUrl(url, callback, id) //須要對章節編號,因此經過變量id來計數 }, function (err, results) { response.send(results) }) }) }) app.listen(3378, function () { console.log('server listening on 3378') })
結果以下: