资讯专栏INFORMATION COLUMN

node学习之路(一)—— 网络请求

bovenson / 967人阅读

摘要:域套接字使用或指定请求方法的字符串。请求路径包含非法字符时抛出异常。保持资源池周围的套接字在未来被用于其它请求。默认值为当使用的时候,通过正在保持活动的套接字发送包的频繁程度。

文章来源:小青年原创
发布时间:2016-09-29
关键词:JavaScript,nodejs,http,url ,Query String,爬虫
转载需标注本文原始地址: http://zhaomenghuan.github.io...

前言

一直以来想学习一下node,一来是自己目前也没有什么时间去学习服务器端语言,但是有时候又想自己撸一下服务器端,本着爱折腾的精神开始写一写关于node的文章记录学习心得。本系列文章不会过多去讲解node安装、基本API等内容,而是通过一些实例去总结常用用法。本文主要讲解node网络操作的相关内容,node中的网络操作依赖于http模块,http模块提供了两种使用方式:

作为服务器端使用,创建一个http服务器,监听http客户端请求并返回响应;

作为客户端使用,发起一个http客户端请求,获取服务器端响应。

node http模块创建服务器 node 处理 get 请求实例

毕竟作为一个前端,我们经常需要自己搭建一个服务器做测试,这里我们先来讲一下node http模块作为服务器端使用。首先我们需要,使用createServer创建一个服务,然后通过listen监听客服端http请求。

我们可以创建一个最简单的服务器,在页面输出hello world,我们可以创建helloworld.js,内容如下:

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!。

下面我们在本地写一个页面,通过jsonp访问我们创建的node服务器:



    
        
        
    
    
        

我们当然需要将上述node服务器中的代码稍作修改:

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);

我们在命令行可以看到:

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处理,然后返回一个函数:

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

而我们在页面中定义了一个jsonpcallback()的方法,所以当我们在请求页面动态生成script调用服务器地址,这样相当于在页面执行了下我们定义的函数。jsonp的实现原理主要是script标签src可以跨域执行代码,类似于你引用js库,然后调用这个js库里面的方法;这是这里我们可以认为反过来了,你是在本地定义函数,调用的逻辑通过服务器返回的一个函数执行了,所以jsonp并没有什么神奇的,和XMLHttpRequest、ajax半毛钱关系都没有,而且JSONP需要服务器端支持,始终是无状态连接,不能获悉连接状态和错误事件,而且只能走GET的形式。

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 实现模拟登录的思路。

参考

nodejs官网API文档

进击Node.js基础(一)

Node.js v4.2.4 文档 中文版

文章代码源码下载:https://github.com/zhaomenghu...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84564.html

相关文章

  • 次网站的性能优化之路 -- 天下武功,唯快不破

    摘要:百度搜索资源平台有闪电算法的支持,为了能够保障用户体验,给予优秀站点更多面向用户的机会,闪电算法在年月初上线。下栏是每一个指标的细化性能评估。最后优化之路漫漫,永无止境,天下武功,唯快不破。 showImg(https://segmentfault.com/img/remote/1460000018537491); 首屏作为直面用户的第一屏,其重要性不言而喻,如何加快加载的速度是非常重...

    mudiyouyou 评论0 收藏0
  • node爬虫的升级打怪之路

    摘要:我是一个知乎轻微重度用户,之前写了一只爬虫帮我爬取并分析它的数据,我感觉这个过程还是挺有意思,因为这是一个不断给自己创造问题又去解决问题的过程。所以这只爬虫还有登陆知乎搜索题目的功能。 我一直觉得,爬虫是许多web开发人员难以回避的点。我们也应该或多或少的去接触这方面,因为可以从爬虫中学习到web开发中应当掌握的一些基本知识。而且,它还很有趣。 我是一个知乎轻微重度用户,之前写了一只爬...

    shiweifu 评论0 收藏0
  • Node.js学习之路11——创建TCP客户端

    摘要:创建客户端对象与服务器的参数属性一样此时端口有下边的几个属性连接另一端所使用的远程地址连接另一端所使用的端口号本地用于建立连接的地址本地用于建立连接的端口号端口对象可以被用来写入向客户端或服务器端发送的流数据当流数据被写入后将立即发送到客户 1. 创建TCP客户端 const net = require(net); let socket = new net.Socket([option...

    cnsworder 评论0 收藏0
  • 「真®全栈之路」Web前端开发的后端指南

    前言 在若干次前的一场面试,面试官看我做过python爬虫/后端 的工作,顺带问了我些后端相关的问题:你觉得什么是后端? 送命题。当时脑瓦特了,答曰:逻辑处理和数据增删改查。。。 showImg(https://user-gold-cdn.xitu.io/2019/4/24/16a4ed4fc8c18078); 当场被怼得体无完肤,羞愧难当。事后再反思这问题,结合资料总结了一下。发现自己学过的Re...

    chuyao 评论0 收藏0
  • Node.js学习之路25——Express的request对象

    摘要:对象表示请求并且具有请求查询字符串参数正文标题头等属性对应用程序实例的引用保存了很多对使用中间件的应用程序实例的引用挂载在路由实例上的路径请求主体和和包含在请求正文中提交的数据的键值对默认情况下它是未定义的当您使用体解析中间件如和时将被填 2. request req对象表示http请求,并且具有请求查询字符串,参数,正文,http标题头等属性 app.get(/user/:id, ...

    cocopeak 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<