nodejs 爬蟲

最近在學習nodes,然後就動手跟着教程做了第一個nodejs的小demo—爬蟲,我選取的是菜鳥教程網站首頁(http://www.runoob.com)進行爬取。

但是在跟着教程做的過程中發現輸出的網頁HTML出現了亂碼現象,百度了一下才知道是網站的代碼是進行了壓縮的,於是我便在代碼裏面引用了var zlib = require(“zlib”);進行處理,最後得到正確的html頁面代碼:

ar http=require('http');
var zlib = require("zlib");
var url='http://www.runoob.com'
// 該方法和 Http.request()的不同在於,該方法只以 GET 方式請求,並且會自動調用 req.end()來結束請求。
http.get(url,function(res){
     var html = []
    // 當response有data事件觸發時,調用回調函數
    res.on('data',function(data){
        html.push(data)
    })
    // 在end事件上將html打印出來
    res.on('end',function(){
        // console.log(html)
         var buffer = Buffer.concat(html);
         zlib.gunzip(buffer, function(err, decoded) {
            console.log(decoded.toString());
        })
    })
}).on('error',function(){
    // 在最後綁定一個error事件
    console.log("獲取數據出錯!")
})

此外,數據是gzip壓縮後的數據,所以建議不要直接使用字符串拼接,而是採用Buffer的concat方法。之後使用自帶的zlib進行gunzip即可。
這個寫法只是針對這一個問題而已,實際應用中需要自己去判斷返回內容的encoding。當然了,第三方的工具,比如request和樓下的superAgent會更加方便一些。

2、使用cheerio
cheerio是nodejs的抓取頁面模塊,爲服務器特別定製的,快速、靈活、實施的jQuery核心實現。適合各種Web爬蟲程序。
使用時要現將抓取到的HTML文件給load進去,然後調用cheerio的方法,cheerio的用法和jq類似,很好上手。

之後將獲取到的HTML切了,取得需要的信息放在數組中,最後將打印取出就好了。

3、利用promise對爬蟲代碼進行優化
(1)http和HTTPS的區別:
這裏寫圖片描述
總之,https協議是在http之上加入了SSL/TLS握手以及數據加密傳輸,SSL/TLS是他們的最大區別。
在nodejs 中的HTTPS模塊是用於處理加密訪問的,http和HTTPS兩者的使用是相同的。
4、node模塊加載:
(1)Node.js的模塊分爲兩類,一類爲原生(核心)模塊,*一類爲文件模塊,其中文件模塊又分爲三類*。

這三類文件模塊以後綴來區分,Node.js會根據後綴名來決定加載方法。
.js。通過fs模塊同步讀取js文件並編譯執行。
.node。通過C/C++進行編寫的Addon。通過dlopen方法進行加載。
.json。讀取文件,調用JSON.parse解析加載。

原生模塊在Node.js源代碼編譯的時候編譯進了二進制執行文件,加載的速度最快。另一類文件模塊是動態加載的,加載速度比原生模塊慢。但是Node.js對原生模塊和文件模塊都進行了緩存,於是在第二次require時,是不會有重複開銷的。其中原生模塊都被定義在lib這個目錄下面,文件模塊則不定性。由於通過命令行加載啓動的文件幾乎都爲文件模塊。我們從Node.js如何加載文件模塊開始談起。加載文件模塊的工作,主要由原生模塊module來實現和完成,該原生模塊在啓動時已經被加載,進程直接調用到runMain靜態方法。

require方法中的文件查找策略

由於Node.js中存在4類模塊(原生模塊和3種文件模塊),儘管require方法極其簡單,但是內部的加載卻是十分複雜的,其加載優先級也各自不同。

這裏寫圖片描述

從文件模塊緩存中加載
儘管原生模塊與文件模塊的優先級不同,但是都不會優先於從文件模塊的緩存中加載已經存在的模塊。

從原生模塊加載
原生模塊的優先級僅次於文件模塊緩存的優先級。require方法在解析文件名之後,優先檢查模塊是否在原生模塊列表中。以http模塊爲例,儘管在目錄下存在一個http/http.js/http.node/http.json文件,require(“http”)都不會從這些文件中加載,而是從原生模塊中加載。

原生模塊也有一個緩存區,同樣也是優先從緩存區加載。如果緩存區沒有被加載過,則調用原生模塊的加載方式進行加載和執行。

從文件加載
當文件模塊緩存中不存在,而且不是原生模塊的時候,Node.js會解析require方法傳入的參數,並從文件系統中加載實際的文件,加載過程中的包裝和編譯細節在前一節中已經介紹過,這裏我們將詳細描述查找文件模塊的過程,其中,也有一些細節值得知曉。

require方法接受以下幾種參數的傳遞:

http、fs、path等,原生模塊。
./mod或../mod,相對路徑的文件模塊。
/pathtomodule/mod,絕對路徑的文件模塊。
mod,非原生模塊的文件模塊。

簡而言之,加載的優先順序爲:文件模塊的緩存中加載已經存在的模塊>原生模塊緩存區加載>原生模塊加載>文件模塊加載

(2)commonjs(模塊加載機制被稱爲CommonJS規範,主要爲了JS在後端的表現制定的,他是不適合前端的)

  • 分析前後端js的不同

    服務器端JS :相同的代碼需要多次執行, CPU和內存資源是瓶頸 ,加載時從磁盤中加載
    瀏覽器端JS:代碼需要從一個服務器端分發到多個客戶端執行,帶寬是瓶頸, 加載時需要通過網絡加載


在這個規範下,每個.js文件都是一個模塊,它們內部各自使用的變量名和函數名都互不衝突,例如,hello.js和main.js都申明瞭全局變量var s = ‘xxx’,但互不影響。
一個模塊想要對外暴露變量(函數也是變量),可以用module.exports = variable;,一個模塊要引用其他模塊暴露的變量,用var ref = require(‘module_name’);就拿到了引用模塊的變量。

實現“模塊”功能的奧妙就在於JavaScript是一種函數式編程語言,它支持閉包。如果我們把一段JavaScript代碼用一個函數包裝起來,這段代碼的所有“全局”變量就變成了函數內部的局部變量。

CommonJS定義的模塊分爲:{模塊引用(require)} {模塊定義(exports)} {模塊標識(module)}

require()用來引入外部模塊;exports對象用於導出當前模塊的方法或變量,唯一的導出口;module對象就代表模塊本身。

  • AMD(即Asynchronous Module Definition,中文名是異步模塊定義的意思。它是一個在瀏覽器端模塊化開發的規範)
    由於不是JavaScript原生支持,使用AMD規範進行頁面開發需要用到對應的庫函數,也就是大名鼎鼎RequireJS,實際上AMD 是 RequireJS 在推廣過程中對模塊定義的規範化的產出

requireJS主要解決兩個問題:

1、多個js文件可能有依賴關係,被依賴的文件需要早於依賴它的文件加載到瀏覽器
2、js加載的時候瀏覽器會停止頁面渲染,加載文件越多,頁面失去響應時間越長
- CMD(Common Module Definition,大名遠揚的玉伯寫了seajs,就是遵循他提出的CMD規範,CMD有個瀏覽器的實現SeaJS,SeaJS要解決的問題和requireJS一樣,只不過在模塊定義方式和模塊加載(可以說運行、解析)時機上有所不同 語法 ,Sea.js 推崇一個模塊一個文件,遵循統一的寫法 )

CMD推崇
一個文件一個模塊,所以經常就用文件名作爲模塊id
CMD推崇依賴就近,所以一般不在define的參數中寫依賴,在factory中寫
factory是一個函數,有三個參數,function(require, exports, module)
require 是一個方法,接受 模塊標識 作爲唯一參數,用來獲取其他模塊提供的接口:require(id)
exports 是一個對象,用來向外提供模塊接口
module 是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法

AMD與CMD區別

關於這兩個的區別網上可以搜出一堆文章,簡單總結一下

最明顯的區別就是在模塊定義時對依賴的處理不同

1、AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊
2、CMD推崇就近依賴,只有在用到某個模塊的時候再去require
這種區別各有優劣,只是語法上的差距,而且requireJS和SeaJS都支持對方的寫法

AMD和CMD最大的區別是對依賴模塊的執行時機處理不同,注意不是加載的時機或者方式不同

很多人說requireJS是異步加載模塊,SeaJS是同步加載模塊,這麼理解實際上是不準確的,其實加載模塊都是異步的,只不過AMD依賴前置,js可以方便知道依賴模塊是誰,立即加載,而CMD就近依賴,需要使用把模塊變爲字符串解析一遍才知道依賴了那些模塊,這也是很多人詬病CMD的一點,犧牲性能來帶來開發的便利性,實際上解析模塊用的時間短到可以忽略

爲什麼我們說兩個的區別是依賴模塊執行時機不同,爲什麼很多人認爲ADM是異步的,CMD是同步的(除了名字的原因。。。)

同樣都是異步加載模塊,AMD在加載模塊完成後就會執行改模塊,所有模塊都加載執行完後會進入require的回調函數,執行主邏輯,這樣的效果就是依賴模塊的執行順序和書寫順序不一定一致,看網絡速度,哪個先下載下來,哪個先執行,但是主邏輯一定在所有依賴加載完成後才執行

CMD加載完某個依賴模塊後並不執行,只是下載而已,在所有依賴模塊加載完成後進入主邏輯,遇到require語句的時候才執行對應的模塊,這樣模塊的執行順序和書寫順序是完全一致的

這也是很多人說AMD用戶體驗好,因爲沒有延遲,依賴模塊提前執行了,CMD性能好,因爲只有用戶需要的時候才執行的原因

最後轉載一篇關於模塊化發展的文章:鏈接

5、nodejs 文件系統(fs)
Node.js 文件系統(fs 模塊)模塊中的方法均有異步和同步版本,例如讀取文件內容的函數有異步的 fs.readFile() 和同步的 fs.readFileSync()。
異步的方法函數最後一個參數爲回調函數,回調函數的第一個參數包含了錯誤信息(error)。
建議大家是用異步方法,比起同步,異步方法性能更高,速度更快,而且沒有阻塞。

6、buffer(二進制緩存區,可以用於處理tcp/圖像/文件/網絡等傳輸信息)
Buffer在nodejs中用來處理二進制的數組(js字符串是用utf-8存儲的,處理二進制的能力是很弱的,而網絡層對不同資源的請求,響應等基本以二進制來進行交互),它便創建一個專門存儲二進制的緩存區,並提供了一些方法對這些緩存區的數據做進一步的處理。
buffer在nodejs裏可全局訪問,不需要使用require()來引入和加載它。

buffer是一個對象,也是一個構造函數,具有自己的屬性和方法。通過它new出來的實例實際上是代表了V8引擎分配的一段內存,基本上是一個數組,成員也都是整數值。(一個 Buffer 類似於一個整數數組,但它對應於 V8 堆內存之外的一塊原始內存。)

Buffer的實例化分爲三種方法:
1.通過字符串的形式實例化,eg:new Buffer(‘hello world’);
2。通過定義buffer中size的大小實例化,eg:var buf=new Buffer(8)//定義一個緩衝區長度8的;
3.通過數組的方式實例化,eg:var buf=new Buffer([1,2,3,4])。

new Buff(‘hello 慕課網’); //默認是UTF-8編碼格式
new Buff(‘hello’,’base64’); //指定編碼格式
var buf = new Buffer(8);//超出長度時,超出部分不會被緩存,只存儲8位
var buf=new Buffer([1,2,3,4]);console.log(buf[2])
3//說明可以使用數組的方式來取得內容
(1)buffer的一些靜態方法和屬性:
poolSize:內存載體的容量
isBuffer:是否爲buffer類型對象
compare:用來判斷兩個buffer對象的相對位置
isEncoding:判斷nodejs是否支持某種編碼
concat:將幾個buffer對象連接創建一個新的buffer對象
byteLength:獲得指定編碼下字符串所佔的字節數
這裏寫圖片描述

7、stream(流,Buffer用來保存原始數據,流是用來暫存和移動數據的,二者經常結合起來用。流是以buffer的形式存在,Stream 是一個抽象接口,Node 中有很多對象實現了這個接口。)
所有的 Stream 對象都是 EventEmitter 的實例,也就是說stream是基於事件機制工作的,因此流有異步的特徵,支持事件監聽,可以監聽流任何階段的變化。
(1)管道方法 pipe()
Stream 有四種流類型:
Readable - 可讀操作。(它有兩種模式:流動模式(resume,這時候數據從底層系統讀出,並提供給上層的應用程序)和暫停模式(pause))
Writable - 可寫操作。(用來消費數據)
Duplex - 可讀可寫操作.(雙工流,例如:web socket等)
Transform - 操作被寫入數據,然後讀出結果。(轉換流,也是雙工的,但是並不保存數據,只負責處理流經它的數據,是一箇中間控制器(如水管中部的閥門))

發佈了25 篇原創文章 · 獲贊 10 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章