初始node第一步,先爬个网站数据

先来熟悉一下几个框架...为我们的爬虫做一下准备,做成目录的形式,是为了方便阅读,无奈似乎不支持我这样写 。。。QAQ就当成目录形式把,别点了。点了也没用。无奈好像不支持

。格式比较丑。留一下github地址把。github地址

  • 1. cheerio
    • 1.1 cheerio 概念
    • 1.2 cheerio API
    • 1.3 cheerio demo
  • 2. superAgent
    • 2.1 superAgent 概念
    • 2.2 superAgent API
    • 2.3 superAgent demo
  • 3. eventproxy
    • 3.1 eventproxy 概念
    • 3.2 eventproxy API
    • 3.3 eventproxy demo
  • 4. 通过cheerio ,superAgent,eventproxy实现爬虫

cheerio

cheerio 概念

github 地址

cheerio是快速、灵活和精益核心jQuery专门为服务器的实现。实现了jquery的核心子集,完全删除了DOM的操作,cheerio能够解析任何HTML,XML页面。从而真正的实现华丽的API。


特色

语法简介
高效快捷
非常灵活

cheerio API


Selectors

  • $( selector, [context], [root] )



Attributes

  • .attr( name, value )

  • .prop( name, value )

  • .data( name, value )

  • .val( [value] )

  • .removeAttr( name )

  • .hasClass( className )

  • .addClass( className )

  • .removeClass( [className] )

  • .toggleClass( className, [switch] )

  • .is( selector )

  • .is( element )

  • .is( selection )

  • .is( function(index) )



  • Forms

  • .serialize()

  • .serializeArray()



  • Traversing

  • .find(selector)

  • .find(selection)

  • .find(node)

  • .parent([selector])

  • .parents([selector])

  • .parentsUntil([selector][,filter])

  • .closest(selector)

  • .next([selector])

  • .nextAll([selector])

  • .nextUntil([selector], [filter])

  • .prev([selector])

  • .prevAll([selector])

  • .prevUntil([selector], [filter])

  • .slice( start, [end] )

  • .siblings([selector])

  • .children([selector])

  • .contents()

  • .each( function(index, element) )

  • .map( function(index, element) )

  • .filter( selector )

    .filter( selection )

    .filter( element )

    .filter( function(index, element) )

  • .not( selector )

    .not( selection )

    .not( element )

    .not( function(index, elem) )

  • .has( selector )

    .has( element )

  • .first()

  • .last()

  • .eq( i )

  • .get( [i] )

  • .index()

  • .index( selector )

  • .index( nodeOrSelection )

  • .end()

  • .add( selector [, context] )

  • .add( element )

  • .add( elements )

  • .add( html )

  • .add( selection )

  • .addBack( [filter] )



  • Manipulation

  • .append( content, [content, ...] )

  • .appendTo( target )

  • .prepend( content, [content, ...] )

  • .prependTo( target )

  • .after( content, [content, ...] )

  • .insertAfter( target )

  • .before( content, [content, ...] )

  • .insertBefore( target )

  • .remove( [selector] )

  • .replaceWith( content )

  • .empty()

  • .html( [htmlString] )

  • .text( [textString] )

  • .wrap( content )

  • .css( [propertName] )

    .css( [ propertyNames] )

    .css( [propertyName], [value] )

    .css( [propertName], [function] )

    .css( [properties] )

  • cheerio demo

    const cheerio = require('cheerio')
    const $ = cheerio.load('

    Hello world

    ') $('h2.title').text('Hello there!') $('h2').addClass('welcome') $.html() //=>

    Hello there!

    方法获取和修改属性 .attr
    .attr( name, value )

    $('ul').attr('id')
    
    $('.apple').attr('id', 'favorite').html()
    //=> 
  • Apple
  • .prop(name,value)

    获取和设置属性的方法。就只有第一个元素的属性值匹配集。

    .serialize() ,返回是一个string类型

    $('
    ').serialize() //=> foo=bar&foo=qux

    .serializeArray(),返回是一个数组

    $('
    ').serializeArray() //=> [ { name: 'foo', value: 'bar' } ]

    Traversing 遍历

    .find(selector)

    .find(selection)

    .find(node)

    $('#fruits').find('li').length
    //=> 3
    $('#fruits').find($('.apple')).length
    //=> 1
    
    $('.pear').parent().attr('id')
    //=> fruits
    

    修改DOM结构的方法

    .append( content, [content, ...] )

    //old
    //=>  
      //
    • Apple
    • //
    • Orange
    • //
    • Pear
    • //
    //now we can use 'append'
    $('ul').append('
  • plum
  • '); $.html(); //new //=>
      //
    • Apple
    • //
    • Orange
    • //
    • Pear
    • //
    • Plum
    • //

    .appendTo( target )

    $('
  • Plum
  • ').appendTo('#fruits') $.html() //=>
      //
    • Apple
    • //
    • Orange
    • //
    • Pear
    • //
    • Plum
    • //

    .prepend( content, [content, ...] )

    $('ul').prepend('
  • Plum
  • ') $.html() //=>
      //
    • Plum
    • //
    • Apple
    • //
    • Orange
    • //
    • Pear
    • //

    .prependTo( target )

    Insert every element in the set of matched elements to the beginning of the target.

    $('
  • Plum
  • ').prependTo('#fruits') $.html() //=>
      //
    • Plum
    • //
    • Apple
    • //
    • Orange
    • //
    • Pear
    • //

    .after( content, [content, ...] )

    $('.apple').after('
  • Plum
  • ') $.html() //=>
      //
    • Apple
    • //
    • Plum
    • //
    • Orange
    • //
    • Pear
    • //

    .insertAfter( target )

    $('
  • Plum
  • ').insertAfter('.apple') $.html() //=>
      //
    • Apple
    • //
    • Plum
    • //
    • Orange
    • //
    • Pear
    • //

    .before( content, [content, ...] )

    $('.apple').before('
  • Plum
  • ') $.html() //=>
      //
    • Plum
    • //
    • Apple
    • //
    • Orange
    • //
    • Pear
    • //

    .insertBefore( target )

    $('
  • Plum
  • ').insertBefore('.apple') $.html() //=>
      //
    • Plum
    • //
    • Apple
    • //
    • Orange
    • //
    • Pear
    • //

    .remove( [selector] )

    $('.pear').remove()
    $.html()
    //=>  
      //
    • Apple
    • //
    • Orange
    • //

    .html( [htmlString] )

    Gets an html content string from the first selected element. If htmlString is specified, each selected element's content is replaced by the new content.

    $('.orange').html()
    //=> Orange
    
    $('#fruits').html('
  • Mango
  • ').html() //=>
  • Mango
  • .text( [textString] )

    $('.orange').text()
    //=> Orange
    
    $('ul').text()
    //=>  Apple
    //    Orange
    //    Pear
    
    

    如果操作The "DOM Node" object还有很多API需要慢慢去学习体会

    • tagName
    • parentNode
    • previousSibling
    • nextSibling
    • nodeValue
    • firstChild
    • childNodes
    • lastChild

    superAgent

    superagent是nodejs里一个非常方便的客户端请求代理模块,当你想处理get,post,put,delete,head请求时,你就应该想起该用它了

    superAgent 概念


    superagent 是一个轻量的,渐进式的ajax api,可读性好,学习曲线低,内部依赖nodejs原生的请求api,适用于nodejs环境下.

    superAgent API

    这是链接,是用Mocha‘s文档自动输出的。下面提供了这个测试文档对应的源文件。

    superAgent demo

    一个简单的post请求,并设置请求头信息的例子

     request
       .post('/api/pet')
       .send({ name: 'Manny', species: 'cat' })
       .set('X-API-Key', 'foobar')
       .set('Accept', 'application/json')
       .end(function(res){
         if (res.ok) {
           alert('yay got ' + JSON.stringify(res.body));
         } else {
           alert('Oh no! error ' + res.text);
         }
       });
    

    一个请求的初始化可以用请求对象里合适的方法来执行,然后调用end()来发送请求,下面是一个简单的get请求

     request
       .get('/search')
       .end(function(res){
    
       });
    

    请求方式也可以通过参数传递:

    request('GET', '/search').end(callback);
    

    node客户端也允许提供绝对路径:

     request
       .get('http://example.com/search')
       .end(function(res){
    
       });
    

    delete,head,post,put和别的http动作都可以使用,来换个方法看看:

    request
      .head('/favicon.ico')
      .end(function(res){
    
      });
    

    delete是一个特列,因为它是系统保留的关键字,所以应该用.del()这个名字:

    request
      .del('/user/1')
      .end(function(res){
    
      });
    

    http请求默认的方法为get,所以就像你看到的,下面的这个例子也是可用的:

    request('/search', function(res){
    
     });
    

    设置头字段
    设置头字段非常简单,只需调用.set()方法,传递一个名称和值就行:

        request
           .get('/search')
           .set('API-Key', 'foobar')
           .set('Accept', 'application/json')
           .end(callback);
    

    你也可以直接传递一个对象进去,这样一次就可以修改多个头字段:

     request
       .get('/search')
       .set({ 'API-Key': 'foobar', Accept: 'application/json' })
       .end(callback);
    

    query

    request
      .get('/search')
      .query({ query: 'Manny', range: '1..5', order: 'desc' })
      .end(function(res){
    
      });
    

    同样支持传递字符串:

    request
        .get('/querystring')
        .query('search=Manny&range=1..5')
        .end(function(res){
    
        });
    

    或者字符串拼接:

    request
        .get('/querystring')
        .query('search=Manny')
        .query('range=1..5')
        .end(function(res){
    
        });
    

    POST/PUT 请求
    一个典型的json post请求看起来就像下面的那样,设置一个合适的Content-type头字段,然后写入一些数据,在这个例子里只是json字符串:

    request.post('/user')
        .set('Content-Type', 'application/json')
        .send('{"name":"tj","pet":"tobi"}')
        .end(callback)
    

    因为json非常通用,所以就作为默认的Content-type,下面的例子跟上面的一样:

    request.post('/user')
        .send({ name: 'tj', pet: 'tobi' })
        .end(callback)
    

    默认发送字符串,将设置Content-type为application/x-www-form-urlencoded,多次调用将会通过&来连接,这里的结果为name=tj&pet=tobi:

    request.post('/user')
        .send('name=tj')
        .send('pet=tobi')
        .end(callback);
    

    Response status
    响应状态标识可以用来判断请求是否成功,除此之外,可以用superagent来构建理想的restful服务器,这些标识目前定义为:

    var type = status / 100 | 0;
    
     // status / class
     res.status = status;
     res.statusType = type;
    
     // basics
     res.info = 1 == type;
     res.ok = 2 == type;
     res.clientError = 4 == type;
     res.serverError = 5 == type;
     res.error = 4 == type || 5 == type;
    
     // sugar
     res.accepted = 202 == status;
     res.noContent = 204 == status || 1223 == status;
     res.badRequest = 400 == status;
     res.unauthorized = 401 == status;
     res.notAcceptable = 406 == status;
     res.notFound = 404 == status;
     res.forbidden = 403 == status;
    

    同样可以通过req.abort()来中止请求.

    跨域资源共享
    .withCredentials()方法可以激活发送原始cookie的能力,不过只有在Access-Control-Allow-Origin不是一个通配符(*),并且Access-Control-Allow-Credentials为’true’的情况下才行.

    request
      .get('http://localhost:4001/')
      .withCredentials()
      .end(function(res){
        assert(200 == res.status);
        assert('tobi' == res.text);
        next();
      })
    

    eventproxy

    世界上本没有嵌套回调,写得人多了,也便有了}}}}}}}}}}}}—— fengmk2

    eventproxy 概念


    EventProxy 仅仅是一个很轻量的工具,但是能够带来一种事件式编程的思维变化。有几个特点:

    • 利用事件机制解耦复杂业务逻辑
    • 移除被广为诟病的深度callback嵌套问题
    • 将串行等待变成并行等待,提升多异步协作场景下的执行效率
    • 友好的Error handling
    • 无平台依赖,适合前后端,能用于浏览器和Node.js
    • 兼容CMD,AMD以及CommonJS模块环境

    eventproxy API

    api文档-链接

    eventproxy demo

    异步协作
    此处以页面渲染为场景,渲染页面需要模板、数据。假设都需要异步读取。

    var ep = new EventProxy();
    ep.all('tpl', 'data', function (tpl, data) { // or ep.all(['tpl', 'data'], function (tpl, data) {})
      // 在所有指定的事件触发后,将会被调用执行
      // 参数对应各自的事件名
    });
    fs.readFile('template.tpl', 'utf-8', function (err, content) {
      ep.emit('tpl', content);
    });
    db.get('some sql', function (err, result) {
      ep.emit('data', result);
    });
    
    all方法将handler注册到事件组合上。当注册的多个事件都触发后,将会调用handler执行,每个事件传递的数据,将会依照事件名顺序,传入handler作为参数。

    快速创建

    EventProxy提供了create静态方法,可以快速完成注册all事件。

    var ep = EventProxy.create('tpl', 'data', function (tpl, data) {
      // TODO
    });
    

    等同于

    var ep = new EventProxy();
    ep.all('tpl', 'data', function (tpl, data) {
      // TODO
    });
    

    重复异步协作

    此处以读取目录下的所有文件为例,在异步操作中,我们需要在所有异步调用结束后,执行某些操作。

    var ep = new EventProxy();
    ep.after('got_file', files.length, function (list) {
      // 在所有文件的异步执行结束后将被执行
      // 所有文件的内容都存在list数组中
    });
    for (var i = 0; i < files.length; i++) {
      fs.readFile(files[i], 'utf-8', function (err, content) {
        // 触发结果事件
        ep.emit('got_file', content);
      });
    }
    

    after方法适合重复的操作,比如读取10个文件,调用5次数据库等。将handler注册到N次相同事件的触发上。达到指定的触发数,handler将会被调用执行,每次触发的数据,将会按触发顺序,存为数组作为参数传入。

    持续型异步协作

    此处以股票为例,数据和模板都是异步获取,但是数据会持续刷新,视图会需要重新刷新。

    var ep = new EventProxy();
    ep.tail('tpl', 'data', function (tpl, data) {
      // 在所有指定的事件触发后,将会被调用执行
      // 参数对应各自的事件名的最新数据
    });
    fs.readFile('template.tpl', 'utf-8', function (err, content) {
      ep.emit('tpl', content);
    });
    setInterval(function () {
      db.get('some sql', function (err, result) {
        ep.emit('data', result);
      });
    }, 2000);
    

    tail与all方法比较类似,都是注册到事件组合上。不同在于,指定事件都触发之后,如果事件依旧持续触发,将会在每次触发时调用handler,极像一条尾巴。

    基本事件
    通过事件实现异步协作是EventProxy的主要亮点。除此之外,它还是一个基本的事件库。携带如下基本API

    • on/addListener,绑定事件监听器
    • emit,触发事件
    • once,绑定只执行一次的事件监听器
    • removeListener,移除事件的监听器
    • removeAllListeners,移除单个事件或者所有事件的监听器

    异常处理

    exports.getContent = function (callback) {
     var ep = new EventProxy();
      ep.all('tpl', 'data', function (tpl, data) {
        // 成功回调
        callback(null, {
          template: tpl,
          data: data
        });
      });
      // 添加error handler
      ep.fail(callback);
    
      fs.readFile('template.tpl', 'utf-8', ep.done('tpl'));
      db.get('some sql', ep.done('data'));
    };
    
    

    具体详细的内容请看github库中,作者的编写的内容

    node中的 异步事件触发:

    var ep = EventProxy.create();
    
    db.check('key', function (err, permission) {
      if (err) {
        return ep.emitLater('error', err);
      }
      ep.emitLater('check', permission);
    });
    
    ep.once('check', function (permission) {
      permission && db.get('key', function (err, data) {
        if (err) {
          return ep.emit('error');
        }
        ep.emit('get', data);
      });
    });
    
    ep.once('get', function (err, data) {
      if (err) {
        return ep.emit('error', err);
      }
      render(data);
    });
    
    ep.on('error', errorHandler);
    

    上面代码中,我们把db.check的回调函数中的事件通过emitLater触发,这样,就算db.check的回调函数被同步执行了,事件的触发也还是异步的,ep在当前事件循环中监听了所有的事件,之后的事件循环中才会去触发check事件。代码顺序将和逻辑顺序保持一致。 当然,这么复杂的代码,必须可以像ep.done()一样通过doneLater来解决:

    var ep = EventProxy.create();
    
    db.check('key', ep.doneLater('check'));
    
    ep.once('check', function (permission) {
      permission && db.get('key', ep.done('get'));
    });
    
    ep.once('get', function (data) {
      render(data);
    });
    
    ep.fail(errorHandler);
    

    注意事项

    • 请勿使用all作为业务中的事件名。该事件名为保留事件。
    • 异常处理部分,请遵循 Node 的最佳实践(回调函数首个参数为异常传递位)。

    通过以上技术,node实现爬虫

    代码示例

    // app.js
    
    //使用 eventproxy 控制并发
    
    // 体会 Node.js 的 callback hell 之美
    
    // 学习使用 eventproxy 这一利器控制并发
    
    // 我们需要取出每个主题的第一条评论,这就要求我们对每个主题的链接发起请求,并用 cheerio 去取出其中的第一条评论。
    
    var eventproxy = require('eventproxy');
    var superagent = require('superagent');
    var cheerio = require('cheerio');
    var express = require('express');
    
    var url = require('url');
    
    var app = express();
    
    var cnodeUrl = 'https://cnodejs.org/';
    
    superagent.get(cnodeUrl)
      .end(function (err, res) {
        if (err) {
          return console.error(err);
        }
        var topicUrls = [];
        var $ = cheerio.load(res.text);
        $('#topic_list .topic_title').each(function (idx, element) {
          var $element = $(element);
          var href = url.resolve(cnodeUrl, $element.attr('href'));
          topicUrls.push(href);
        });
    
        var ep = new eventproxy();
    
        ep.after('topic_html', topicUrls.length, function (topics) {
          topics = topics.map(function (topicPair) {
            var topicUrl = topicPair[0];
            var topicHtml = topicPair[1];
            var $ = cheerio.load(topicHtml);
            return ({
              title: $('.topic_full_title').text().trim(),
              href: topicUrl,
              comment1: $('.reply_content').eq(0).text().trim(),
            });
          });
    
          console.log('final:');
          console.log(topics);
        });
    
        topicUrls.forEach(function (topicUrl) {
          superagent.get(topicUrl)
            .end(function (err, res) {
              console.log('fetch ' + topicUrl + ' successful');
              ep.emit('topic_html', [topicUrl, res.text]);
            });
        });
      });
    

    参考资料

    [译] SuperAgent中文使用文档 -作者 xuwenmin

    github-node-lesson-作者 alsotang

    你可能感兴趣的:(初始node第一步,先爬个网站数据)