nodejs(2)-如何使用node来向其他服务器发送请求
##### URL模块
这个模块可以帮助我们解析url地址,从里面提取很多有用的内容供我们使用;
假设这是一个url地址http://localhost:8080/a/b/c?a=1&b=2#abc,里面包含的部分:
```
protocol: 'http:',
host: 'localhost:8080',
port: '8080',
hostname: 'localhost',
hash: '#abc',
search: '?a=1&b=2',
query: 'a=1&b=2',
pathname: '/a/b/c',
path: '/a/b/c?a=1&b=2',
href: 'http://localhost:8080/a/b/c?a=1&b=2#abc'
```
url.parse(urlString[, parseQueryString[, slashesDenoteHost]])
会返回一个解析后的对象,第一个参数为要解析的url地址,第二个参数为是否将query字符串解析成对象格式,第二个参数来控制在没有协议的情况下,是否解析域名等内容
url.format(urlObject)
将一个url解析后的对象还原成一个url地址
url.resolve(from, to)
可以将我们两段url解析成一个url地址
```
console.log(url.resolve('http://www.baidu.com','/api/index.html'))
//'http://www.baidu.com/api/index.html'
```
---
### queryString
可以将我们的queryString字符串(a=1&b=2&c=3)解析或反编译
querystring.stringify(obj[, sep[, eq[, options]]]):
可以将一个对象(键值对)解析成一个querystring,第二个参数可以设置分割符号(&),第三个参数确定键值对之间的链接符号(=)
querystring.parse(str[, sep[, eq[, options]]])
可以将一个qs字符串解析成一个对象,后面的参数是按照某种规则去解析
querystring.escape(str),querystring.unescape(str)
可以将我们的中文解析成百分号编码
```
console.log(qs.escape('北京'))//%E5%8C%97%E4%BA%AC
console.log(qs.unescape('%E5%8C%97%E4%BA%AC'))//北京
```
---
#### http小爬虫
##### 首先我们先研究一下什么是SEO
Search Engine Optimization 搜索引擎优化,是一种技术,目的是提高网页在搜索引擎的排行
如何能提高排行:
1. 给百度花钱
2. 找专业的优化团队
3. 开发过程中注意优化,例如,在不影响页面结构的情况下多使用语义化标签!img的title等也需要设置,title标签必须有,通过meta标签设置description、keywords、author;不需要使用ajax获取的数据就不要获取了,尽量可以使用服务端渲染数据的方式
---
利用http.get方法去获取到某网址的html文件内容,然后利用cheerio工具进行关键内容的提取
1. 获取到 https://www.lagou.com这个接口的数据(其实就是拉钩的首页文件)
因为http、https模块可以向其他的服务器发送请求,依靠的是request方法或者其子方法:get、post;
```
let target = 'https://www.lagou.com'
const https = require('https')
//发送一个get请求,res是一个返回的对象,里面并没有直接包含数据,需要使用data事件来接收返回的数据
//且每次返回的只是一个片段,所以会触发多次,因为可能请求到的数据量特大,如果一次性返回的话可能导致服务器崩溃,所以Node采取事件的方式多次返回
https.get(target, function(res) {
let result = ''
res.on("data",(chunk)=>{
result += chunk
})
res.on("end",()=>{//当接收完后触发
//在这里就可以处理数据result
})
})
```
2. 爬取其上面的一级、二级标题的内容
因为获取到的是一个大的html格式的字符串,所以想要爬取其中某些标签的内容不能直接使用jq,用正则匹配又有些繁琐,所以使用cheerio工具,这个工具可以在服务端将html格式的字符串进行编译后返回一个$对象,这样就可以像使用JQ一样去操作了
```
const cheerio = require("cheerio")
let spideContent = (result,callback)=>{
let $ = cheerio.load(result)
let menus = []
$(".menu_main").each(function (i) {
let title = $(this).find('h2').text().trim()
let navs = Array.prototype.slice.call($(this).find('a')).map((item)=>{
return $(item).text()
})
menus.push({title,navs})
})
callback(menus)//通过回调函数来处理解析后的数据
}
```
3. 再把爬取出的内容存到一个文件里
```
spideContent(result,(menus)=>{
fs.writeFileSync('./lagou.json',JSON.stringify(menus))
})
```
##### JS小知识扩展
如何将伪数组转换成真正的数组?
1. 常见的伪数组都有哪些?arguments、通过document.getElements..获取到的内容;
2. 伪数组有什么特点,具有length属性,也是一个一个的元素组成的,但是构造器不是Array,不能使用数组的方法
3. 转换为真正数组的方法:
通过遍历将伪数组里元素放入到一个新的数组里
```
let arg = arguments//这就是一个典型的伪数组
let arr = []
for (var i = 0; i < arg.length; i++) {
arr.push(arg[i])
}
console.log(arr)
```
通过call改变数组slice方法里的this指向
因为我想要让伪数组也能使用数组的方法,为什么伪数组就不能使用数组方法,为什么数组就能使用push方法了
一个数组都是由它的构造器实例化出来的,var a = []//这是js的语法糖;正规的用法:var a = new Array()
因为Array是一个构造函数,每一个构造函数都有原型,且构造函数构造出来的实例可以使用原型上的方法,也就是说因为Array的原型上有一些方法,所以每一个数组都可以使用这些push等等的方法
因为伪数组的构造器不是Array,当然不能使用Array原型上的push方法
现在数组有一个方法slice,这个方法每次都会返回一个新数组,如果不传参数的话,返回的新数组的元素和原数组的元素是一模一样的
如果伪数组也能执行这个slice方法的话,那么是不是就会返回一个新的真正的数组,并且元素一样,但是不能直接执行
所以我们使用偷梁换柱的方法,让一个真正的数据,或者直接从Array.prototype上执行slice方法,但是在执行的时候通过call来将里面的this换成咱们的伪数组,这样的话,就会返回一个元素和伪数组元素一样的真正数组了
```
let arr = [].slice.call(arg) //Array.prototype.slice.call(arg)
```
---
##### requestGet/requestPost
1. 通过request来get数据(请求的是豆瓣电影的api):
```
let target = 'http://api.douban.com/v2/movie/in_theaters'
const http = require('http')
const url_info = require('url').parse(target,true)
//定义配置选项
let options = {
hostname: url_info.hostname,//要请求的接口的域名
port: url_info.port||80,
path: url_info.pathname,
method: 'GET'
};
//创建一个请求的对象
var req = http.request(options, function(res) {//回调函数能接收到响应对象
//状态码 res.statusCode
//响应头 res.headers
res.setEncoding('utf8');//设置字符编码
let result = ''
res.on('data', function (chunk) {//通过data事件来接收数据
result+=chunk
});
res.on('end', function () {//接收完成后触发
fs.writeFileSync('./movie.json',result)
});
});
//绑定error事件,如果出错会执行
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
//标识请求完成
req.end();
```
2. 通过request的post方法来请求:
注意,get方法发送的参数是拼接在url上的,post是以formdata存在的,但是发送过去的必须都得是字符串格式
大部分步骤基本一样,但是需要注意的是,post请求的时候需要设置请求头里的Content-Length,需要通过req.write方法来写入请求信息
```
const target = 'http://post.baibaoyun.com/api/0a953068ff01781ce22c0822c075018c'
const qs = require('querystring')
const http = require('http')
let url_info = require('url').parse(target)
//定义好要发送的数据
var postData = qs.stringify({
'a':'text1',
'b':'text2'
});//必须变成这样的格式 'a=text1&b=text2'
var options = {
hostname: url_info.hostname,
port: url_info.port||80,
path: url_info.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length//post发送的时候需要将请求头的Content-Length设置为发送数据的leng
}
};
var req = http.request(options, function(res) {
res.setEncoding('utf8');
let result = ''
res.on('data', function (chunk) {
result+=chunk
});
res.on('end', function () {
console.log(result)
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
//写入请求发送的数据
req.write(postData);
req.end();
```
###### 小作业
总结POST和GET的区别,每位同学都写一个md文档
---
##### fileStystem模块
后端语言都有操作文件系统的能力,在nodejs里我们依靠的是fs模块
每种操作的方法基本都有同异步的两种不同方法
1.查看文件信息(多用来判断文件是否存在)exists
```
var fs=require('fs');
//异步查询文件信息
fs.stat("../sources/temp.txt",(err,data)=>{
if(err){
console.log(err);
}else{
console.log(data)
//判断是否是文件
console.log(`文件:${data.isFile()}`)
//判断是否是路径
console.log(`目录:${data.isDirectory()}`)
}
})
//同步查询
//fs.statSync("../sources/temp.txt")
console.log(1)
```
2.创建一个目录
```
//创建一个目录,如果目录已存在的话就会返回错误信息
fs.mkdir("logs",function(err){
if(err){
console.log(err);
}else{
console.log('success');
}
})
```
3.写入文件
```
//给文件写内容,当文件不存在会创建一个文件,第二个参数为写入的内容,每次写入都会覆盖
fs.writeFile('logs/hello.log','hello everyone\n',function(err){
if(err){
console.log(err);
}else{
console.log('success')
}
})
//给文件中追加内容
fs.appendFile("logs/hello.log",'hello world\n',function(err){
if(err){
console.log(err);
}else{
console.log('success')
}
})
```
4.读取文件内容
```
//读取文件内容的方法,第二个参数可选,为读取的编码格式
fs.readFile("logs/hello.log",'utf-8',function(err,data){
if(err){
console.log(err);
}else{
console.log(data)
}
})
```
5.读取目录内容
```
//读取目录内容,返回一个数组
fs.readdir("logs",function(err,files){
if(err){
console.log(err);
}else{
console.log(files)
}
})
```
6.文件重命名
```
//重命名方法
fs.rename("logs/hello.log","logs/world.log",function(err){
if(err){
console.log(err)
}else{
console.log('success')
}
})
```
7.删除目录、文件
fs.rmdir可以删除目录但是,必须是空目录,fs.unlink可以删除文件,如果我们要删除一个目录及它下面的文件或子目录的话,我们需要先读取出来,删除完成后再进行根目录的删除
8.小练习:创建一个copy复制文件的函数
```
const fs = require('fs')
const _path = require('path')
let copy = (path,target)=>{
fs.stat(path,(err)=>{
if(err){
console.log('要复制的文件不存在')
}else{
let content = fs.readFileSync(path)
fs.writeFile(target+'/'+_path.basename(path).replace('.','-副本.'),content,(err)=>{
if(err){console.log('目标目录不存在')}else{
console.log('success')
}
})
}
})
}
copy('./sources/hello.txt','./sources/a')
```
9.fs-extra模块
这是一个第三方的fs模块,需要下载 npm install fs-extra
fs模块上的方法它都有,并且还封装了一些很好用的方法,比如:copy、remove..
[文档地址](https://www.npmjs.com/package/fs-extra)
###### ES6小知识
Object.assign方法可以将一些对象中的属性和方法扩展到某一个对象上
```
var a = {y:2}
var b ={x:1}
var c = {z:3}
Object.assign(a,b,c)
console.log(a)//{x:1,y:2,z:3}
```
---