NodeJs 入門到放棄 — 常用模組及網路爬蟲(二)
阿新 • • 發佈:2021-03-03
### 碼文不易啊,轉載請帶上本文連結呀,感謝感謝 https://www.cnblogs.com/echoyya/p/14473101.html
[toc]
### Buffer (緩衝區)
JavaScript 語言自身只有字串資料型別,沒有二進位制資料型別。二進位制可以儲存任意型別的資料,電腦中所有的資料都是二進位制。
在處理像TCP流或檔案流時,必須使用到二進位制資料。因此在 Node.js中,定義了一個 Buffer 類,該類用來建立一個專門存放二進位制資料的快取區。
**Buffer 與字元編碼**:當在 `Buffer` 和字串之間轉換時,可以指定字元編碼。 如果未指定字元編碼,則預設使用 UTF-8 。
#### Buffer 建立
Buffer物件可以通過多種方式建立,v6.0之前直接使用new Buffer()建構函式來建立物件例項,v6.0以後,官方文件建議使用**Buffer.from()** 建立物件。[nodejs中文網](http://nodejs.cn/api/buffer.html)、[菜鳥教程-nodejs](https://www.runoob.com/nodejs/nodejs-buffer.html)
`Buffer.from(buffer)`:複製傳入的 Buffer ,返回一個新的 Buffer 例項
`Buffer.from(string[, encoding])`:要編碼的字串。字元編碼。**預設值:** `'utf8'`。
`Buffer.alloc(size[, fill[, encoding]])`: 返回一個指定大小的 Buffer 例項,如果沒有設定 fill,則預設填滿 0
```js
var buf = Buffer.from('Echoyya');
var buf1 = Buffer.from(buf);
console.log(buf); //
buf1[0] = 0x65;
console.log(buf.toString());// Echoyya
console.log(buf1.toString()); // echoyya
var buf2 = Buffer.from('4563686f797961', 'hex'); // 設定編碼
console.log(buf2); //
console.log(buf2.toString()); // Echoyya
var buf3 = Buffer.from('4563686f797961'); // 預設編碼
console.log(buf3); //
console.log(buf3.toString()); // 4563686f797961
var buf4 = Buffer.alloc(4);
console.log(buf4); //
```
#### Buffer 寫入
`buf.write(string[, offset[, length]][, encoding])`
**引數描述:**
- **string** - 寫入緩衝區的字串。
- **offset** - 緩衝區開始寫入的索引值,預設為 0 。
- **length** - 寫入的位元組數,預設為 buffer.length
- **encoding** - 使用的編碼。預設為 'utf8' 。
根據 encoding 的字元編碼寫入 string 到 buf 中的 offset 位置。 length 引數是寫入的位元組數。
**返回值:**返回實際寫入的大小。如果 buffer 空間不足, 則只會寫入部分字串。
```js
//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** - 結束位置,預設為緩衝區的末尾。
**返回值:**解碼緩衝區資料並使用指定的編碼返回字串。
```js
buf = Buffer.alloc(26);
for (var i = 0 ; i < 26 ; i++) {
buf[i] = i + 97;
}
console.log(buf); //
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)`
```js
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)`,沒有返回值
如果檔案存在,該方法寫入的內容會覆蓋原有內容,反之檔案不存在,呼叫該方法寫入將建立一個新檔案
```js
const fs = require('fs')
var hello = ' '
fs.writeFile('./index.html',hello,function(err){
if(err) throw err
else console.log('檔案寫入成功');
})
var helloSync= ' {
// 拼接路徑
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** - 所有資料已被**寫入**到底層系統時觸發。
#### 讀取流
```js
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("程式執行完畢");
````
#### 寫入流
```js
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)連線兩個桶使得水從一個桶流入另一個桶,這樣就可實現大檔案的複製過程。
![](https://img2020.cnblogs.com/blog/1238759/202103/1238759-20210303104047459-318210872.png)
管道語法:`reader.pipe(writer);`
**讀取 input.txt 檔案內容,並寫入 output.txt 檔案**,兩種實現方式對比:
```js
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);
```
#### 鏈式流
將多個管道連線起來,實現鏈式操作
用**管道**和**鏈式**來壓縮和解壓檔案:
```js
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`](http://nodejs.cn/api/errors.html#errors_class_typeerror)。 |
| **path.dirname(path)** | 返回路徑中`資料夾`部分 |
| **path.basename(path)** | 返回路徑中`檔案`部分(檔名和副檔名) |
| **path.extname(path)** | 返回路徑中`副檔名`部分 |
| **path.parse(path)** | 解析路徑,返回一個物件,其屬性表示 `path` 的有效元素 |
```js
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);
```
![](https://img2020.cnblogs.com/blog/1238759/202103/1238759-20210303104112334-672222547.png)
### http (協議)
網路是資訊傳輸、接收、共享的虛擬平臺,而網路傳輸資料有一定的規則,稱協議,HTTP就是其中之一,且使用最為頻繁。
#### B/S開發模式
(Browser/Server,瀏覽器/伺服器模式),瀏覽器(web客戶端)使用HTTP協議就可以訪問web伺服器上的資料
**客戶端:傳送請求,等待響應**
**伺服器:處理請求,返回響應**
#### 定義、約束、互動特點
**定義:**`HTTP 即 超文字傳輸協議`,是一種網路傳輸協議,採用的是`請求 / 響應`方式傳遞資料,該協議規定了資料在伺服器與瀏覽器之間,傳輸資料的格式與過程
**約束:**
1. 約束了瀏覽器以何種格式兩伺服器傳送資料
2. 約束了伺服器以何種格式接收客戶端傳送的資料
3. 約束了伺服器以何種格式響應資料給瀏覽器
4. 約束了以何種格式接收伺服器響應的資料
**互動特點:**一次請求對應一次響應,多次請求對應多次響應
### http (模組)
由於大多數請求都是不帶請求體的 GET 請求,因此最最最常用的方法:
- [`http.get(url[, options][, callback])`](http://nodejs.cn/api/http.html#http_http_get_url_options_callback)
```js
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. 將獲取到的圖片儲存下來
```js
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 = / /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)**
![](https://img2020.cnblogs.com/blog/1238759/202103/1238759-20210303104241549-12194820