NodeJs 入門到放棄 — 常用模組及網路爬蟲(二)

Echoyya、發表於2021-03-03

碼文不易啊,轉載請帶上本文連結呀,感謝感謝 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 (模組)

由於大多數請求都是不帶請求體的 GET 請求,因此最最最常用的方法:

  • http.get(url[, options][, callback])

    var http = require('http')
    var fs = require('fs')
    
    http.get('http://www.baidu.com/',function(res){
      // console.log(res);   
      // res 返回的即為一個可讀流, 
      res.pipe(fs.createWriteStream('./a.html'))
    })
    

網路爬蟲

網路爬蟲(又稱為網頁蜘蛛,網路機器人),是一種按照一定的規則,自動地抓取全球資訊網資訊的程式或者指令碼。

小案例

需求:寫一個爬蟲程式批量下載圖片 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)

相關文章