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')
  1. 刪除空目錄

    注:不能刪除非空目錄

    • 異步:fs.rmdir(path, callback) ,回調函數,沒有參數

    • 同步:fs.rmdirSync(path)

  2. 刪除非空目錄(遞歸)

    實現思路:

    • 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 入門到放棄 — 網絡服務器(三)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM