这次分享下node爬虫,通过实践学习下后端的一些知识。
访问页面,获取页面内容
首先我们要像浏览器一样,可以发送一个页面请求,并且可以解析页面内容。
superagent 是一个客户端HTTP请求库,可以用来模拟浏览器发送请求。
cheerio是一个转换工具。通过这个工具,我们可以用类似jquery的方式查询和处理获得的页面内容。
superagent.get('xxx') // 首先访问文章列表页,获取各个章节的url
.charset('gbk') // 文章内容是中文,这里设定字符集
.end((err, sres) => {
// 常规的错误处理
if (err) {
return next(err);
}
let $ = cheerio.load(sres.text, {decodeEntities: false}); // 通过cheerio实现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('追加内容完成');
});
});
这样就获得了所有章节的url。
现在开始获取章节里的文章内容。原理和获取文章列表一致,首先通过循环发送请求获取内容。
结果并没有获得所有的章节,总是有些章节丢失。
这下要打些日志看看到底哪里有问题。
日志
通过日志记录请求文章内容时的具体状态,方便排查问题。这里使用的是winston,记录下请求发送的时间以及返回状态。
日志级别
下面罗列了6种日志级别,和每种日志的使用场景。
参考文献: https://blog.csdn.net/qq_31332467/article/details/77198158
Verbose: 开发调试过程中一些详细信息,不应该编译进产品中,只在开发阶段使用。(参考api文档的描述:Verbose should never be compiled into anapplication except during development)
Debug: 用于调试的信息,编译进产品,但可以在运行时关闭。(参考api文档描述:Debug logs are compiled in but stripped a truntime)
Info:例如一些运行时的状态信息,这些状态信息在出现问题的时候能提供帮助。
Warn:警告系统出现了异常,即将出现错误。
Error:系统已经出现了错误。
日志设置
const levels = { //这个是日志级别。在winston里通过设置level可以设置哪些级别的日志可以输出。如果设置成info,则低于2的warn,error也会被输出。
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' })
]
});
关于日志格式,可以自定义。可以参考下面的代码。
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开始请求页面
使用方式
// 打日志,以下两种方式都可以
logger.log({
level: 'info',
message: 'Hello distributed log files!'
});
logger.info('Hello again distributed logs');
结果分析
通过分析日志,发现并不是每个页面请求成功的回调都执行了。下面这句话也许就是原因。
在Node中,长时间的CPU占用会导致后续的异步I/O发不出调用,已完成的I/O的回调函数也会得不到及时执行。 -- 深入浅出Node.js
通过async控制并发
既然并发太多会有问题,那么就控制下并发数量。 使用async这个库,通过里面的mapLimit方法控制同时发送的请求数目。
async.mapLimit(
urls, // url数组
5, // 设置同时发送的请求数目上限
function(url, callback) {
fetch(url, callback); // 在fetch方法里,当页面返回结束后,调用callback函数,表明这个请求已经结束,这样就可以发送下一个请求。
},
(err, results) => {
if (err) throw err;
console.log(results);
}
);
这样就可以获得所有章节,可以慢慢看。
在上面提到了,Node服务每秒只能处理若干请求,即使内存,CPU和网络都没有饱和。
参考文献
Squeeze the juice out of Node— an exploration of how Node.js handles HTTP connections
网友评论