文章来源:小青年原创
发布时间: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...