這次分享下node爬蟲,通過實(shí)踐學(xué)習(xí)下后端的一些知識(shí)煌张。
訪問頁面苛白,獲取頁面內(nèi)容
首先我們要像瀏覽器一樣娃豹,可以發(fā)送一個(gè)頁面請(qǐng)求,并且可以解析頁面內(nèi)容购裙。
superagent 是一個(gè)客戶端HTTP請(qǐng)求庫懂版,可以用來模擬瀏覽器發(fā)送請(qǐng)求。
cheerio是一個(gè)轉(zhuǎn)換工具躏率。通過這個(gè)工具躯畴,我們可以用類似jquery的方式查詢和處理獲得的頁面內(nèi)容。
superagent.get('xxx') // 首先訪問文章列表頁禾锤,獲取各個(gè)章節(jié)的url
.charset('gbk') // 文章內(nèi)容是中文私股,這里設(shè)定字符集
.end((err, sres) => {
// 常規(guī)的錯(cuò)誤處理
if (err) {
return next(err);
}
let $ = cheerio.load(sres.text, {decodeEntities: false}); // 通過cheerio實(shí)現(xiàn)jquery接口
let items = [];
$('#content p').each((idx, element) => {
let $element = $(element);
items.push({
title: $element.html(),
href: $element.href,
});
});
fs.appendFile('./test.txt', 'abc', (err)=> { //將獲得的文章列表輸出到文件里
if(!err) console.log('追加內(nèi)容完成');
});
});
這樣就獲得了所有章節(jié)的url摹察。
現(xiàn)在開始獲取章節(jié)里的文章內(nèi)容恩掷。原理和獲取文章列表一致,首先通過循環(huán)發(fā)送請(qǐng)求獲取內(nèi)容供嚎。
結(jié)果并沒有獲得所有的章節(jié)黄娘,總是有些章節(jié)丟失峭状。
這下要打些日志看看到底哪里有問題。
日志
通過日志記錄請(qǐng)求文章內(nèi)容時(shí)的具體狀態(tài)逼争,方便排查問題优床。這里使用的是winston,記錄下請(qǐng)求發(fā)送的時(shí)間以及返回狀態(tài)誓焦。
日志級(jí)別
下面羅列了6種日志級(jí)別胆敞,和每種日志的使用場(chǎng)景。
參考文獻(xiàn): https://blog.csdn.net/qq_31332467/article/details/77198158
Verbose: 開發(fā)調(diào)試過程中一些詳細(xì)信息杂伟,不應(yīng)該編譯進(jìn)產(chǎn)品中移层,只在開發(fā)階段使用。(參考api文檔的描述:Verbose should never be compiled into anapplication except during development)
Debug: 用于調(diào)試的信息赫粥,編譯進(jìn)產(chǎn)品,但可以在運(yùn)行時(shí)關(guān)閉。(參考api文檔描述:Debug logs are compiled in but stripped a truntime)
Info:例如一些運(yùn)行時(shí)的狀態(tài)信息尝哆,這些狀態(tài)信息在出現(xiàn)問題的時(shí)候能提供幫助仰禀。
Warn:警告系統(tǒng)出現(xiàn)了異常,即將出現(xiàn)錯(cuò)誤秦叛。
Error:系統(tǒng)已經(jīng)出現(xiàn)了錯(cuò)誤晦溪。
日志設(shè)置
const levels = { //這個(gè)是日志級(jí)別。在winston里通過設(shè)置level可以設(shè)置哪些級(jí)別的日志可以輸出挣跋。如果設(shè)置成info尼变,則低于2的warn,error也會(huì)被輸出浆劲。
error: 0,
warn: 1,
info: 2,
verbose: 3,
debug: 4,
silly: 5
};
var winston = require("winston")
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(), // 日志信息的格式
transports: [
//
// - 將所有info嫌术,warn,error日志輸出到combined.log
// - 將所有error日志輸出到error.log
//
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
關(guān)于日志格式牌借,可以自定義度气。可以參考下面的代碼膨报。
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf } = format;
const myFormat = printf(info => {
return `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`;
});
const logger = createLogger({
format: combine(
label({ label: 'right meow!' }),
timestamp(),
myFormat
),
transports: [new transports.Console()]
});
// 輸入的日志樣式
2018-08-15T07:16:05.923Z [right meow!] info: 297開始請(qǐng)求頁面
使用方式
// 打日志磷籍,以下兩種方式都可以
logger.log({
level: 'info',
message: 'Hello distributed log files!'
});
logger.info('Hello again distributed logs');
結(jié)果分析
通過分析日志,發(fā)現(xiàn)并不是每個(gè)頁面請(qǐng)求成功的回調(diào)都執(zhí)行了现柠。下面這句話也許就是原因院领。
在Node中,長(zhǎng)時(shí)間的CPU占用會(huì)導(dǎo)致后續(xù)的異步I/O發(fā)不出調(diào)用够吩,已完成的I/O的回調(diào)函數(shù)也會(huì)得不到及時(shí)執(zhí)行比然。 -- 深入淺出Node.js
通過async控制并發(fā)
既然并發(fā)太多會(huì)有問題,那么就控制下并發(fā)數(shù)量周循。 使用async這個(gè)庫强法,通過里面的mapLimit方法控制同時(shí)發(fā)送的請(qǐng)求數(shù)目万俗。
async.mapLimit(
urls, // url數(shù)組
5, // 設(shè)置同時(shí)發(fā)送的請(qǐng)求數(shù)目上限
function(url, callback) {
fetch(url, callback); // 在fetch方法里,當(dāng)頁面返回結(jié)束后饮怯,調(diào)用callback函數(shù)闰歪,表明這個(gè)請(qǐng)求已經(jīng)結(jié)束,這樣就可以發(fā)送下一個(gè)請(qǐng)求蓖墅。
},
(err, results) => {
if (err) throw err;
console.log(results);
}
);
這樣就可以獲得所有章節(jié)库倘,可以慢慢看。
在上面提到了论矾,Node服務(wù)每秒只能處理若干請(qǐng)求于樟,即使內(nèi)存,CPU和網(wǎng)絡(luò)都沒有飽和拇囊。
參考文獻(xiàn)
Squeeze the juice out of Node— an exploration of how Node.js handles HTTP connections