1. 程式人生 > >NodeJs 入門到放棄 — 常用模組及網路爬蟲(二)

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

### 碼文不易啊,轉載請帶上本文連結呀,感謝感謝 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 = '

hello fs

' fs.writeFile('./index.html',hello,function(err){ if(err) throw err else console.log('檔案寫入成功'); }) var helloSync= '

hello fs Sync

' fs.writeFileSync('./index.html',helloSync) ``` #### 刪除檔案 非同步:`fs.unlink(path, callback)` 同步:`fs.unlinkSync(path)`,沒有返回值 對空或非空的目錄均不起作用。 若要刪除目錄,則使用 fs.rmdir()。 ```js 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)` ```js 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:**刪除空資料夾 ```js 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** - 所有資料已被**寫入**到底層系統時觸發。 #### 讀取流 ```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