文章來源:小青年原創
發佈時間:2016-09-29
關鍵詞:JavaScript,nodejs,http,url ,Query String,爬蟲
轉載需標註本文原始地址: http://zhaomenghuan.github.io...javascript
一直以來想學習一下node,一來是本身目前也沒有什麼時間去學習服務器端語言,可是有時候又想本身擼一下服務器端,本着愛折騰的精神開始寫一寫關於node的文章記錄學習心得。本系列文章不會過多去講解node安裝、基本API等內容,而是經過一些實例去總結經常使用用法。本文主要講解node網絡操做的相關內容,node中的網絡操做依賴於http模塊,http模塊提供了兩種使用方式:html
畢竟做爲一個前端,咱們常常須要本身搭建一個服務器作測試,這裏咱們先來說一下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
固然這裏咱們能夠直接在後臺設置響應頭進行跨域(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.parse方法將url字符串轉成Url對象,用法以下:
url.parse(urlStr, [parseQueryString], [slashesDenoteHost])
接收參數:
parseQueryString:參數爲true時,query會被解析爲JSON格式,不然爲普通字符串格式,默認爲false;如:
query: { userid: 'xiaoqingnian', callback: 'jsonpcallback' }
query: 'userid=xiaoqingnian&callback=jsonpcallback'
> 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字符串,用法爲:
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或 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'
querystring.escape('appkey=123&version=1.0.0+') // 'appkey%3D123%26version%3D1.0.0%2B'
querystring.unescape('appkey%3D123%26version%3D1.0.0%2B') // 'appkey=123&version=1.0.0+'
querystring.stringify(obj[, sep][, eq][, options]) querystring.encode(obj[, sep][, eq][, options])
接收參數:
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(str[, sep][, eq][, options]) querystring.decode(str[, sep][, eq][, options])
接收參數:
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相關的東西。這裏咱們首先看一個爬蟲的小例子,下面以SF爲例加以說明(但願不要被封號)。
options能夠是一個對象或一個字符串。若是options是一個字符串, 它將自動使用url.parse()解析。http.request() 返回一個 http.ClientRequest類的實例。ClientRequest實例是一個可寫流對象。若是須要用POST請求上傳一個文件的話,就將其寫入到ClientRequest對象。使用http.request()方法時都必須老是調用req.end()以代表這個請求已經完成,即便響應body裏沒有任何數據。若是在請求期間發生錯誤(DNS解析、TCP級別的錯誤或實際HTTP解析錯誤),在返回的請求對象會觸發一個'error'事件。
Options配置說明:
agent:控制Agent的行爲。當使用了一個Agent的時候,請求將默認爲Connection: keep-alive。可能的值爲:
由於大部分的請求是沒有報文體的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...