• NodeJs 入门到放弃 — 常用模块及网络爬虫(二)


    码文不易啊,转载请带上本文链接呀,感谢感谢 https://www.cnblogs.com/echoyya/p/14473101.html

    Buffer (缓冲区)

    JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。二进制可以存储任意类型的数据,电脑中所有的数据都是二进制。

    在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

    Buffer 与字符编码:当在 Buffer 和字符串之间转换时,可以指定字符编码。 如果未指定字符编码,则默认使用 UTF-8 。

    Buffer 创建

    Buffer对象可以通过多种方式创建,v6.0之前直接使用new Buffer()构造函数来创建对象实例,v6.0以后,官方文档建议使用Buffer.from() 创建对象。nodejs中文网菜鸟教程-nodejs

    Buffer.from(buffer):复制传入的 Buffer ,返回一个新的 Buffer 实例

    Buffer.from(string[, encoding]):要编码的字符串。字符编码。默认值: 'utf8'

    Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0

    var buf = Buffer.from('Echoyya'); 
    var buf1 = Buffer.from(buf);   
    console.log(buf);  // <Buffer 45 63 68 6f 79 79 61>
    buf1[0] = 0x65;
    console.log(buf.toString());// Echoyya
    console.log(buf1.toString()); // echoyya
    
    var buf2 = Buffer.from('4563686f797961', 'hex');  // 设置编码
    console.log(buf2);             // <Buffer 45 63 68 6f 79 79 61>
    console.log(buf2.toString());  // Echoyya
    
    var buf3 = Buffer.from('4563686f797961');  // 默认编码
    console.log(buf3);              // <Buffer 34 35 36 33 36 38 36 66 37 39 37 39 36 31>
    console.log(buf3.toString());   // 4563686f797961
    
    var buf4 = Buffer.alloc(4);
    console.log(buf4);      // <Buffer 00 00 00 00>
    

    Buffer 写入

    buf.write(string[, offset[, length]][, encoding])

    参数描述:

    • string - 写入缓冲区的字符串。

    • offset - 缓冲区开始写入的索引值,默认为 0 。

    • length - 写入的字节数,默认为 buffer.length

    • encoding - 使用的编码。默认为 'utf8' 。

    根据 encoding 的字符编码写入 string 到 buf 中的 offset 位置。 length 参数是写入的字节数。

    返回值:返回实际写入的大小。如果 buffer 空间不足, 则只会写入部分字符串。

    //buffer的大小一旦被确定则不能被修改
    var buf5 = Buffer.alloc(4);
    console.log(buf5.length);  // 4
    
    var len = buf5.write("Echoyya");
    console.log(buf5.toString());  // Echo 
    console.log("写入字节数 : "+  len);  // 写入字节数 : 4
    

    Buffer 读取

    读取 Node 缓冲区数据:buf.toString([encoding[, start[, end]]])

    参数描述:

    • encoding - 使用的编码。默认为 'utf8' 。

    • start - 指定开始读取的索引位置,默认为 0。

    • end - 结束位置,默认为缓冲区的末尾。

    返回值:解码缓冲区数据并使用指定的编码返回字符串。

    buf = Buffer.alloc(26);
    for (var i = 0 ; i < 26 ; i++) {
      buf[i] = i + 97;
    }
    console.log(buf); // <Buffer 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a>
    console.log( buf.toString('ascii'));       // abcdefghijklmnopqrstuvwxyz
    console.log( buf.toString('ascii',0,5));   // abcde
    console.log( buf.toString('utf8',0,5));    // abcde
    console.log( buf.toString(undefined,0,5)); // abcde 默认utf8
    

    更多>>

    除上述最基本的读写操作外,还有许多强大的API:

    • 缓冲区合并:Buffer.concat(list[, totalLength])

    • 缓冲区比较:Buffer.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])

    • 缓冲区判断:Buffer.isBuffer(obj)

    • 缓冲区拷贝:Buffer.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

    fs (文件系统)

    Node.js提供一组文件操作API,fs 模块可用于与文件系统进行交互。所有的文件系统操作都具有同步的、回调的、以及基于 promise 的形式。同步与异步的区别,主要在于有无回调函数,同步方法直接在异步方法后面加上Sync

    const fs = require('fs');

    读取文件

    异步:fs.readFile(path, callback)

    同步:fs.readFileSync(path)

    fs.readFile('文件名', (err, data) => {
      if (err) throw err;
      console.log(data);
    });
    
    var data = fs.readFileSync('文件名');
    

    获取文件信息

    异步模式获取文件信息:fs.stat(path, callback)

    • path - 文件路径。
    • callback - 回调函数,带有两个参数如:(err, stats), stats 是 fs.Stats 对象。

    fs.stat(path)执行后,将stats实例返回给回调函数。可以通过提供方法判断文件的相关属性。

    var fs = require('fs');
    
    fs.stat('./data', function (err, stats) {
    	// stats 是文件的信息对象,包含常用的文件信息
    	// size: 文件大小(字节)
    	// mtime: 文件修改时间
    	// birthtime:文件创建时间
    	// 等等...
        console.log(stats.isDirectory());         //true
    })
    

    stats类中的方法有:

    方法 描述
    stats.isFile() 判断是文件返回 true,否则返回 false。
    stats.isDirectory() 判断是目录返回 true,否则返回 false。
    var fs = require("fs");
    
    console.log("准备打开文件!");
    fs.stat('./data', function (err, stats) {
       if (err) {
           return console.error(err);
       }
       console.log(stats);
       console.log("读取文件信息成功!");
       
       // 检测文件类型
       console.log("是否为文件(isFile) ? " + stats.isFile());
       console.log("是否为目录(isDirectory) ? " + stats.isDirectory());    
    });
    

    写入文件

    异步:fs.writeFile(file, data, callback)

    同步:fs.writeFileSync(file, data),没有返回值

    如果文件存在,该方法写入的内容会覆盖原有内容,反之文件不存在,调用该方法写入将创建一个新文件

    const fs = require('fs')
    
    var hello = '<h1>hello fs</h1>'
    fs.writeFile('./index.html',hello,function(err){
      if(err) throw err
      else console.log('文件写入成功');
    })
    
    var helloSync= '<h1>hello fs Sync</h1>'
    fs.writeFileSync('./index.html',helloSync)
    

    删除文件

    异步:fs.unlink(path, callback)

    同步:fs.unlinkSync(path),没有返回值

    对空或非空的目录均不起作用。 若要删除目录,则使用 fs.rmdir()。

    const fs = require('fs')
    
    fs.unlink('./index.html',function(err){
      if(err) throw err
      else console.log('文件删除成功');
    })
    
    fs.unlinkSync('./index.html')
    

    目录操作

    1. 创建目录

      • 异步:fs.mkdir(path, callback)

      • 同步:fs.mkdirSync(path),没有返回值

    2. 读取目录文件

      • 异步:fs.readdir(path, callback)callback 回调有两个参数err, files,files是目录下的文件列表

      • 同步:fs.readdirSync(path)

      const fs = require("fs");
      
      console.log("查看 ./data 目录");
      fs.readdir("./data",function(err, files){
         if(err) throw err
         else{
         	 files.forEach( function (file){
             console.log( file );
           });
         }
      });
      
      var files = fs.readdirSync('./data')
      
    3. 删除空目录

      注:不能删除非空目录

      • 异步:fs.rmdir(path, callback) ,回调函数,没有参数

      • 同步:fs.rmdirSync(path)

    4. 删除非空目录(递归)

      实现思路:

      • fs.readdirSync:读取文件夹中所有文件及文件夹

      • fs.statSync:读取每一个文件的详细信息

      • stats.isFile():判断是否是文件,是文件则删除,否则递归调用自身

      • fs.rmdirSync:删除空文件夹

      const fs = require('fs')
      
      function deldir(p) {
        // 读取文件夹中所有文件及文件夹
        var list = fs.readdirSync(p)
        list.forEach((v, i) => {
          // 拼接路径
          var url = p + '/' + v
          // 读取文件信息
          var stats = fs.statSync(url)
          // 判断是文件还是文件夹
          if (stats.isFile()) {
            // 当前为文件,则删除文件
            fs.unlinkSync(url)
          } else {
            // 当前为文件夹,则递归调用自身
            arguments.callee(url)
          }
        })
        // 删除空文件夹
        fs.rmdirSync(p)
      }
      
      deldir('./data')
      

    Stream (流)

    是一组有序的、有起点、有终点的字节数据的传输方式,在应用程序中,各种对象之间交换与传输数据时:

    1. 总是先将该对象总所包含的数据转换为各种形式的流数据(即字节数据)

    2. 在流传输到达目的对象后,再将流数据转换为该对象中可以使用的数据

    与直接读写文件的区别:可以监听它的'data',一节一节处理文件,用过的部分会被GC(垃圾回收),所以占内存少。 readFile是把整个文件全部读到内存里。然后再写入文件,对于小型的文本文件,没多大问题,但对于体积较大文件,使用这种方法,很容易使内存“爆仓”。理想的方法应该是读一节写一节

    流的类型:

    • Readable - 可读操作。

    • Writable - 可写操作。

    • Duplex - 可读可写操作.

    • Transform - 操作被写入数据,然后读出结果。

    常用的事件:

    • data - 当有数据可读时触发。

    • end - 没有更多的数据可读时触发。

    • error - 在接收和写入过程中发生错误时触发。

    • finish - 所有数据已被写入到底层系统时触发。

    读取流

    var fs = require('fs')
    var data = '';
    
    // 创建可读流
    var readerStream = fs.createReadStream('./file.txt');
    
    // 设置编码为 utf8。
    readerStream.setEncoding('UTF8');
    
    // 处理流事件 --> data, end,  error
    readerStream.on('data', function(chunk) {
       data += chunk;
       console.log(chunk.length)  // 一节65536字节, 65536/1024 = 64kb
    });
    
    readerStream.on('end',function(){
       console.log(data);
    });
    
    readerStream.on('error', function(err){
       console.log(err.stack);
    });
    
    console.log("程序执行完毕");
    

    写入流

    var fs = require('fs')
    var data = '创建一个可以写入的流,写入到文件 file1.txt 中';
    
    // 创建写入流,文件 file1.txt 
    var writerStream = fs.createWriteStream('./file1.txt')
    
    // 使用 utf8 编码写入数据
    writerStream.write(data,'UTF8');
    
    // 标记文件末尾
    writerStream.end();
    
    // 处理流事件 --> finish、error
    writerStream.on('finish', function() {
        console.log("写入完成。");
    });
    
    writerStream.on('error', function(err){
       console.log(err.stack);
    });
    
    console.log("程序执行完毕");
    

    管道 pipe

    管道提供了一个输出流 -> 输入流的机制。通常用于从一个流中获取数据传递到另外一个流中。用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就可实现大文件的复制过程。

    管道语法:reader.pipe(writer);

    读取 input.txt 文件内容,并写入 output.txt 文件,两种实现方式对比:

    var fs = require("fs");
    
    var readerStream = fs.createReadStream('input.txt'); // 创建一个可读流
    var writerStream = fs.createWriteStream('output.txt'); // 创建一个可写流
    
    //1. 以流的方式实现大文件复制
    readerStream.on('data',function(chunk){
    	writerStream.write(chunk)  // 读一节写一节
    })
    
    readerStream.on('end',function(){  // 无可读数据
    	writerStream.end()             // 标记文件末尾
    	writerStream.on('finish',function(){  // 所有数据已被写入
    		console.log('复制完成')
    	})
    })
    
    // 2. 以管道方式实现大文件复制
    readerStream.pipe(writerStream);
    

    链式流

    将多个管道连接起来,实现链式操作

    管道链式来压缩和解压文件:

    var fs = require("fs");
    var zlib = require('zlib');
    
    // 压缩 input.txt 文件为 input.txt.gz
    var reader = fs.createReadStream('input.txt')
    var writer = fs.createWriteStream('input.txt.gz')
    
    reader.pipe(zlib.createGzip()).pipe(writer);
      
    
    // 解压 input.txt.gz 文件为 input.txt
    var reader = fs.createReadStream('input.txt.gz')
    var writer = fs.createWriteStream('input.txt')
    
    reader.pipe(zlib.createGunzip()).pipe(writer);
    

    path (路径)

    是nodejs中提供的系统模块,不需安装,用于格式化或拼接转换路径,执行效果会因应用程序运行所在的操作系统不同,而有所差异。

    常用方法:

    方法 描述
    path.normalize(path) 规范化给定的 path,解析 '..''.' 片段。
    path.join([...paths]) 将所有给定的 path 片段连接到一起(使用平台特定的分隔符作为定界符),然后规范化生成的路径。如果路径片段不是字符串,则抛出 TypeError
    path.dirname(path) 返回路径中文件夹部分
    path.basename(path) 返回路径中文件部分(文件名和扩展名)
    path.extname(path) 返回路径中扩展名部分
    path.parse(path) 解析路径,返回一个对象,其属性表示 path 的有效元素
    var path = require('path')
    
    var p1 = "../../../hello/../a/./b/../c.html"
    var p2 = path.normalize(p1)   
    console.log(path.normalize(p1));  // ../../../a/c.html
    console.log(path.dirname(p2))     // ../../../a
    console.log(path.basename(p2))    // c.html
    console.log(path.extname(p2))     // .html
    console.log(path.parse(p2))       //  {  root: '',  dir: '..\..\..\a',  base: 'c.html',  ext: '.html',  name: 'c'}
    
    
    console.log(path.join('/目录1', '目录2', '目录3/目录4', '目录5'));  // '/目录1/目录2/目录3/目录4'
    
    var pArr = ['/目录1', '目录2', '目录3/目录4', '目录5']
    console.log(path.join(...pArr));  // '/目录1/目录2/目录3/目录4'
    
    // path.join('目录1', {}, '目录2'); // 抛出 ' The "path" argument must be of type string. Received an instance of Object'
    
    

    url (URL)

    url :全球统一资源定位符,对网站资源的一种简洁表达形式,也称为网址

    官方规定完整构成:协议://用户名:密码@主机名.名.域:端口号/目录名/文件名.扩展名?参数名=参数值&参数名2=参数值2#hash哈希地址

    http 协议 URL常见结构:协议://主机名.名.域/目录名/文件名.扩展名?参数名=参数值&参数名2=参数值2#hash哈希地址

    域名是指向IP地址的,需要解析到 IP 地址上,服务器与IP地址形成标识,域名只是人可以看懂的符号,而计算机并看不懂,所以需要 DNS 域名服务器来解析。

    nodejs中提供了两套对url模块进行处理的API功能,二者处理后的结果有所不同:

    1. 旧版本传统的 API

    2. 实现了 WHATWG 标准的新 API

    var url = require('url');
    var uu = 'https://music.163.com:80/aaa/index.html?id=10#/discover/playlist'
    
    // WHATWG 标准API 解析 URL 字符串
    var wUrl = new url.URL(uu);
    console.log(wUrl);
    
    
    // 使用传统的 API 解析 URL 字符串:
    var tUrl = url.parse(uu);
    console.log(tUrl);
    

    http (协议)

    网络是信息传输、接收、共享的虚拟平台,而网络传输数据有一定的规则,称协议,HTTP就是其中之一,且使用最为频繁。

    B/S开发模式

    (Browser/Server,浏览器/服务器模式),浏览器(web客户端)使用HTTP协议就可以访问web服务器上的数据

    客户端:发送请求,等待响应

    服务器:处理请求,返回响应

    定义、约束、交互特点

    定义:HTTP 即 超文本传输协议,是一种网络传输协议,采用的是请求 / 响应方式传递数据,该协议规定了数据在服务器与浏览器之间,传输数据的格式与过程

    约束:

    1. 约束了浏览器以何种格式两服务器发送数据

    2. 约束了服务器以何种格式接收客户端发送的数据

    3. 约束了服务器以何种格式响应数据给浏览器

    4. 约束了以何种格式接收服务器响应的数据

    交互特点:一次请求对应一次响应,多次请求对应多次响应

    http (模块)

    http模块是nodejs中系统模块,用于网络通讯,可以作为客户端发送请求,亦可以作为服务器端处理响应,

    限于篇幅,另总结了一篇博文,介绍了两者的用法,以及客户端向服务器端传参,详见:NodeJs 入门到放弃 — 网络服务器(三)

    网络爬虫

    网络爬虫(又称为网页蜘蛛,网络机器人),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。

    小案例

    需求:写一个爬虫程序批量下载图片 http://www.nipic.com/photo/canyin/xican/index.html

    思路:

    1. 打开对应网站查看内容,找图片地址,找规律

    2. 编写代码获取网站内html代码

    3. 通过正则表达式提取出图片地址

    4. 遍历图片地址数组,请求数据

    5. 将获取到的图片保存下来

    var http = require('http')
    var fs = require('fs')
    var path = require('path')
    
    http.get('http://www.jituwang.com/tuku/index.html',function(res){
      var data = ''  // 用于存放一节一节的HTML数据
      
      // 以流的方式读取数据
      res.on('data',function(chunk){
        data += chunk.toString()
      })
    
      res.on('end',function(){
        // 正则获取所有图片地址
        var reg = /<img src="(.+?)" alt=".+?"/>/ig
        var result = ''
        var imgArr = []
        while(result = reg.exec(data)){
          imgArr.push(result[1])
        }
        // 根据数组中的图片地址 获取图片数据
        for (var i in imgArr) {
          setTimeout(function(i){
            getImg(imgArr[i])
          },1000*i,i)
        }
        // fs.writeFileSync('./b.txt',imgArr)   // 可写入文件,查看图片地址
      })
    })
    
    function getImg(url){
      http.get(url,function(res){
        res.pipe(fs.createWriteStream(path.join('./img',path.basename(url))))
      })
    }
    

    for循环请求数据时,为避免对其服务器造成压力,设置定时器,每隔一秒请求一次,将读到的数据,存储为与服务器上同名。利用上述path模块提供的方法,path.basename(url)

    上集: NodeJs 入门到放弃 — 入门基本介绍(一)

    续集: NodeJs 入门到放弃 — 网络服务器(三)

    作者:Echoyya
    著作权归作者和博客园共有,商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    「题解」:$Six$
    「题解」:$Smooth$
    AFO
    纪念——代码首次达到近50K(更新:78.8K 2019行)
    meet-in-the-middle 基础算法(优化dfs)
    莫队学习笔记
    树链剖分学习笔记
    常用数论模板
    图论模板
    高精度模板(结构体封装,重载运算符)
  • 原文地址:https://www.cnblogs.com/echoyya/p/14473101.html
Copyright © 2020-2023  润新知