node學習之路(一)—— 網絡請求

文章來源:小青年原創
發佈時間:2016-09-29
關鍵詞:JavaScript,nodejs,http,url ,Query String,爬蟲
轉載需標註本文原始地址: http://zhaomenghuan.github.io...javascript

前言

一直以來想學習一下node,一來是本身目前也沒有什麼時間去學習服務器端語言,可是有時候又想本身擼一下服務器端,本着愛折騰的精神開始寫一寫關於node的文章記錄學習心得。本系列文章不會過多去講解node安裝、基本API等內容,而是經過一些實例去總結經常使用用法。本文主要講解node網絡操做的相關內容,node中的網絡操做依賴於http模塊,http模塊提供了兩種使用方式:html

  • 做爲服務器端使用,建立一個http服務器,監聽http客戶端請求並返回響應;
  • 做爲客戶端使用,發起一個http客戶端請求,獲取服務器端響應。

node http模塊建立服務器

node 處理 get 請求實例

畢竟做爲一個前端,咱們常常須要本身搭建一個服務器作測試,這裏咱們先來說一下node http模塊做爲服務器端使用。首先咱們須要,使用createServer建立一個服務,而後經過listen監聽客服端http請求。前端

咱們能夠建立一個最簡單的服務器,在頁面輸出hello world,咱們能夠建立helloworld.js,內容以下:java

var http = require('http');

http.createServer(function(request, response){
    response.writeHead(200, { 'Content-Type': 'text-plain' });
    response.end('hello world!')
}).listen(8888);

在命令行輸入node helloworld.js便可,咱們打開在瀏覽器打開http://127.0.0.1:8888/就能夠看到頁面輸出hello world!。node

下面咱們在本地寫一個頁面,經過jsonp訪問咱們建立的node服務器:git

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <div id="output"></div>
        
        <script type="text/javascript">
            // 建立script標籤
            function importScript(src){
                var el = document.createElement('script');
                el.src = src;
                el.async = true;
                el.defer = true;
                document.body.appendChild(el);
            }
            
            // 響應的方法
            function jsonpcallback(rs) {
                console.log(JSON.stringify(rs));
                document.getElementById("output").innerHTML = JSON.stringify(rs);
            }
            
            // 發起get請求
            importScript('http://127.0.0.1:8888?userid=xiaoqingnian&callback=jsonpcallback');
        </script>
    </body>
</html>

咱們固然須要將上述node服務器中的代碼稍做修改:es6

var http = require('http'); // 提供web服務
var url = require('url');    // 解析GET請求
  
var data = {
    'name': 'zhaomenghuan', 
    'age': '22'
};
  
http.createServer(function(req, res){  
    // 將url字符串轉換成Url對象
    var params = url.parse(req.url, true);  
    console.log(params);
    // 查詢參數
    if(params.query){
        // 根據附件條件查詢
        if(params.query.userid === 'xiaoqingnian'){
            // 判斷是否爲jsonp方式請求,如果則使用jsonp方式,不然爲普通web方式
            if (params.query.callback) {  
                var resurlt =  params.query.callback + '(' + JSON.stringify(data) + ')';
                res.end(resurlt);  
            } else {  
                res.end(JSON.stringify(data));
            }
        } 
    }      
}).listen(8888);

咱們在命令行能夠看到:github

Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?userid=xiaoqingnian&callback=jsonpcallback',
  query: { userid: 'xiaoqingnian', callback: 'jsonpcallback' },
  pathname: '/',
  path: '/?userid=xiaoqingnian&callback=jsonpcallback',
  href: '/?userid=xiaoqingnian&callback=jsonpcallback' }

通過服務器端jsonp處理,而後返回一個函數:web

jsonpcallback({"name":"zhaomenghuan","age":"22"})

而咱們在頁面中定義了一個jsonpcallback()的方法,因此當咱們在請求頁面動態生成script調用服務器地址,這樣至關於在頁面執行了下咱們定義的函數。jsonp的實現原理主要是script標籤src能夠跨域執行代碼,相似於你引用js庫,而後調用這個js庫裏面的方法;這是這裏咱們能夠認爲反過來了,你是在本地定義函數,調用的邏輯經過服務器返回的一個函數執行了,因此jsonp並無什麼神奇的,和XMLHttpRequest、ajax半毛錢關係都沒有,並且JSONP須要服務器端支持,始終是無狀態鏈接,不能獲悉鏈接狀態和錯誤事件,並且只能走GET的形式。ajax

node 處理 post 請求實例

固然這裏咱們能夠直接在後臺設置響應頭進行跨域(CORS),如:

var http = require("http");    // 提供web服務
var query = require("querystring");    // 解析POST請求

http.createServer(function(req,res){
      // 報頭添加Access-Control-Allow-Origin標籤,值爲特定的URL或"*"(表示容許全部域訪問當前域)
      res.setHeader("Access-Control-Allow-Origin","*");
      
      var postdata = '';
      // 一旦監聽器被添加,可讀流會觸發 'data' 事件
    req.addListener("data",function(chunk){
        postdata += chunk;
    })
    // 'end' 事件代表已經獲得了完整的 body
    req.addListener("end",function(){
        console.log(postdata);     // 'appid=xiaoqingnian'
        // 將接收到參數串轉換位爲json對象
        var params = query.parse(postdata);
        if(params.userid == 'xiaoqingnian'){
            res.end('{"name":"zhaomenghuan","age":"22"}');
        }
    })
    
}).listen(8080);

咱們經過流的形式接收前端post傳遞的參數,經過監聽data和end事件,後面在講解event模塊的時候再深刻探究。

CORS默認只支持GET/POST這兩種http請求類型,若是要開啓PUT/DELETE之類的方式,須要在服務端在添加一個"Access-Control-Allow-Methods"報頭標籤:

res.setHeader(
      "Access-Control-Allow-Methods",
      "PUT, GET, POST, DELETE, HEAD, PATCH"
);

前端訪問代碼以下:

var xhr = new XMLHttpRequest();
xhr.onload = function () {
    console.log(this.responseText);
};
xhr.onreadystatechange = function() {
    console.log(this.readyState);
};

xhr.open("post", "http://127.0.0.1:8080", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("userid=xiaoqingnian");

url 模塊API詳解

url.parse——解析url字符串

上述代碼中比較關鍵的是咱們經過url.parse方法將url字符串轉成Url對象,用法以下:

url.parse(urlStr, [parseQueryString], [slashesDenoteHost])

接收參數:

  • urlStr:url字符串
  • parseQueryString:參數爲true時,query會被解析爲JSON格式,不然爲普通字符串格式,默認爲false;如:

    • 參數爲true:query: { userid: 'xiaoqingnian', callback: 'jsonpcallback' }
    • 參數爲false:query: 'userid=xiaoqingnian&callback=jsonpcallback'
  • slashesDenoteHost:默認爲false,當url是 ‘http://’ 或 ‘ftp://’ 等標誌的協議前綴打頭的,或直接以地址打頭,如 ‘127.0.0.1’ 或 ‘localhost’ 時候是沒有區別的;當且僅當以2個斜槓打頭的時候,好比 ‘//127.0.0.1’ 纔有區別。這時候,若是其值爲true,則第一個單個 ‘/’ 以前的部分被解析爲 ‘host’ 和 ‘hostname’,如 」 host : ‘127.0.0.1’ 「,若是爲false,包括2個反斜槓在內的全部字符串被解析爲pathname。如:
> url.parse('//www.foo/bar',true,true)
Url {
  protocol: null,
  slashes: true,
  auth: null,
  host: 'www.foo',
  port: null,
  hostname: 'www.foo',
  hash: null,
  search: '',
  query: {},
  pathname: '/bar',
  path: '/bar',
  href: '//www.foo/bar' }
> url.parse('//www.foo/bar',true,false)
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '',
  query: {},
  pathname: '//www.foo/bar',
  path: '//www.foo/bar',
  href: '//www.foo/bar' }

這裏的URL對象和瀏覽器中的location對象相似,location中若是咱們須要使用相似的方法,咱們須要本身構造。

url.format——格式化URL對象

咱們能夠經過url.format方法將一個解析後的URL對象格式化成url字符串,用法爲:

url.format(urlObj)

例子:

url.format({
  protocol: 'http:',
  slashes: true,
  auth: 'user:pass',
  host: 'host.com:8080',
  port: '8080',
  hostname: 'host.com',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' 
})

結果爲:
'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'

url.resolve——拼接url字符串

咱們能夠經過url.resolve爲URL或 href 插入 或 替換原有的標籤,接收參數:
from源地址,to須要添加或替換的標籤。

url.resolve(from, to)

例子爲:

url.resolve('/one/two/three', 'four')
=> '/one/two/four'
url.resolve('http://example.com/', '/one')    
=> 'http://example.com/one'
url.resolve('http://example.com/one', '/two') 
=> 'http://example.com/two'

Query String 模塊Query String

querystring.escape——字符串編碼

querystring.escape('appkey=123&version=1.0.0+')
// 'appkey%3D123%26version%3D1.0.0%2B'

querystring.unescape——字符串解碼

querystring.unescape('appkey%3D123%26version%3D1.0.0%2B')
// 'appkey=123&version=1.0.0+'

querystring.stringify(querystring.encode)——序列化對象

querystring.stringify(obj[, sep][, eq][, options])
querystring.encode(obj[, sep][, eq][, options])

接收參數

  • obj: 欲轉換的對象
  • sep:設置分隔符,默認爲 ‘&'
  • eq:設置賦值符,默認爲 ‘='
querystring.stringify({foo: 'bar', baz: ['qux', 'quux'], corge: ''})
// 'foo=bar&baz=qux&baz=quux&corge='

querystring.stringify({foo: 'bar', baz: ['qux', 'quux'], corge: ''},',',':')
// 'foo:bar,baz:qux,baz:quux,corge:'

querystring.parse(querystring.decode)——解析query字符串

querystring.parse(str[, sep][, eq][, options])
querystring.decode(str[, sep][, eq][, options])

接收參數

  • str:欲轉換的字符串
  • sep:設置分隔符,默認爲 ‘&'
  • eq:設置賦值符,默認爲 ‘='
  • [options] maxKeys 可接受字符串的最大長度,默認爲1000
querystring.parse('foo=bar&baz=qux&baz=quux&corge=')
// { foo: 'bar', baz: [ 'qux', 'quux' ], corge: '' }

querystring.parse('foo:bar,baz:qux,baz:quux,corge:',',',':')
{ foo: 'bar', baz: [ 'qux', 'quux' ], corge: '' }

node http模塊發起請求

平時喜歡看博客,畢竟買書要錢並且有時候沒有耐心讀完整本書,因此很喜歡逛一些網站,可是不少時候把全部的站逛一下又沒有那麼多時間,哈哈,因此就準備把常去的網站的文章爬出來作一個文章列表,一來省去收集的時間,二來藉此熟悉熟悉node相關的東西。這裏咱們首先看一個爬蟲的小例子,下面以SF爲例加以說明(但願不要被封號)。

http.request與http.get的區別

http.request(options, callback)

options能夠是一個對象或一個字符串。若是options是一個字符串, 它將自動使用url.parse()解析。http.request() 返回一個 http.ClientRequest類的實例。ClientRequest實例是一個可寫流對象。若是須要用POST請求上傳一個文件的話,就將其寫入到ClientRequest對象。使用http.request()方法時都必須老是調用req.end()以代表這個請求已經完成,即便響應body裏沒有任何數據。若是在請求期間發生錯誤(DNS解析、TCP級別的錯誤或實際HTTP解析錯誤),在返回的請求對象會觸發一個'error'事件。

Options配置說明:

  • host:請求發送到的服務器的域名或IP地址。默認爲'localhost'。
  • hostname:用於支持url.parse()。hostname比host更好一些
  • port:遠程服務器的端口。默認值爲80。
  • localAddress:用於綁定網絡鏈接的本地接口。
  • socketPath:Unix域套接字(使用host:port或socketPath)
  • method:指定HTTP請求方法的字符串。默認爲'GET'。
  • path:請求路徑。默認爲'/'。若是有查詢字符串,則須要包含。例如'/index.html?page=12'。請求路徑包含非法字符時拋出異常。目前,只否決空格,不過在將來可能改變。
  • headers:包含請求頭的對象。
  • auth:用於計算認證頭的基本認證,即'user:password'
  • agent:控制Agent的行爲。當使用了一個Agent的時候,請求將默認爲Connection: keep-alive。可能的值爲:

    • undefined(默認):在這個主機和端口上使用[全局Agent][]。
    • Agent對象:在Agent中顯式使用passed。
    • false:在對Agent進行資源池的時候,選擇停用鏈接,默認請求爲:Connection: close。
  • keepAlive:{Boolean} 保持資源池周圍的套接字在將來被用於其它請求。默認值爲false
  • keepAliveMsecs:{Integer} 當使用HTTP KeepAlive的時候,經過正在保持活動的套接字發送TCP KeepAlive包的頻繁程度。默認值爲1000。僅當keepAlive被設置爲true時才相關。

http.get(options, callback)

由於大部分的請求是沒有報文體的GET請求,因此Node提供了這種便捷的方法。該方法與http.request()的惟一區別是它設置的是GET方法並自動調用req.end()。

爬蟲實例

這裏咱們使用es6的新特性寫:

const https = require('https');
https.get('https://segmentfault.com/blogs', (res) => {
    console.log('statusCode: ', res.statusCode);
      console.log('headers: ', res.headers);
      var data = '';
      res.on('data', (chunk) => {
        data += chunk;
      });
      res.on('end', () => {
          console.log(data);
      })
}).on('error', (e) => {
      console.error(e);
});

這樣一小段代碼咱們就能夠拿到segmentfault的博客頁面的源碼,須要說明的是由於這裏請求的網站是https協議,因此咱們須要引入https模塊,用法同http一致。下面須要作的是解析html代碼,下面咱們須要作的就是解析源碼,這裏咱們能夠引入cheerio,一個node版的類jQuery模塊,npm地址:https://www.npmjs.com/package...

首先第一步安裝:

npm install cheerio

而後就是將html代碼load進來,以下:

var cheerio = require('cheerio'),
var $ = cheerio.load(html);

最後咱們就是分析dom結構咯,經過相似於jQuery的方法獲取DOM元素的內容,而後就將數據從新組裝成json結構的數據。這裏就是分析源碼而後,這裏我就不詳細分析了,直接上代碼:

function htmlparser(html){
    var baseUrl = 'https://segmentfault.com';
    
    var $ = cheerio.load(html);
    var bloglist = $('.stream-list__item');
    
    var data = [];
    
    bloglist.each(function(item){
        var page = $(this);
        var summary = page.find('.summary');
        var blogrank = page.find('.blog-rank');
        
        var title = summary.find('.title a').text();
        var href = baseUrl + summary.find('.title a').attr('href');
        var author = summary.find('.author li a').first().text().trim();
        var origin = summary.find('.author li a').last().text().trim();
        var time = summary.find('.author li span')[0].nextSibling.data.trim();
        var excerpt = summary.find('p.excerpt').text().trim();
        var votes = blogrank.find('.votes').text().trim();
        var views = blogrank.find('.views').text().trim();
        
        data.push({
            title: title,
            href: href,
            author: author,
            origin: origin,
            time: time,
            votes: votes,
            views: views,
            excerpt: excerpt
        })
    })
    
    return data;
}

結果以下:

[{ title: '轉換流',
    href: 'https://segmentfault.com/a/1190000007036273',
    author: 'SwiftGG翻譯組',
    origin: 'SwiftGG翻譯組',
    time: '1 小時前',
    votes: '0推薦',
    views: '14瀏覽',
    excerpt: '做者:Erica Sadun,原文連接,原文日期:2016-08-29譯者:Darren;校對:shank
s;定稿:千葉知風 我在不少地方都表達了我對流的喜好。我在 Swift Cookbook 中介紹了一些。現
在,我將經過 Pearson 的內容更新計劃...' },
......
]

這裏咱們只是抓取了文章列表的一頁,若是須要抓取多頁,只須要將內容再次封裝一下,傳入一個地址參數?page=2,如:https://segmentfault.com/blog...
另外咱們也沒有將詳情頁進一步爬蟲,畢竟文章的目的只是學習,同時方便本身查看列表,這裏保留原始地址。

舒適提示:你們不要都拿sf作測試哦,否則玩壞了就很差。

模擬登錄

哈哈,寫到這裏已經很晚了,用node試了試模擬登錄SF,結果404,暫時沒有什麼思路,等有時間再試試專門開篇講解咯。這裏推薦一篇以前看到的文章:記一次用 NodeJs 實現模擬登陸的思路

參考

文章代碼源碼下載:https://github.com/zhaomenghu...

相關文章
相關標籤/搜索