2018-08-13 node 爬虫

这次分享下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

你可能感兴趣的:(2018-08-13 node 爬虫)