##### URL模塊
這個模塊可以幫助我們解析url地址,從里面提取很多有用的內(nèi)容供我們使用;
假設這是一個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]])
會返回一個解析后的對象制圈,第一個參數(shù)為要解析的url地址,第二個參數(shù)為是否將query字符串解析成對象格式,第二個參數(shù)來控制在沒有協(xié)議的情況下淹接,是否解析域名等內(nèi)容
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,第二個參數(shù)可以設置分割符號(&),第三個參數(shù)確定鍵值對之間的鏈接符號(=)
querystring.parse(str[, sep[, eq[, options]]])
可以將一個qs字符串解析成一個對象,后面的參數(shù)是按照某種規(guī)則去解析
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 搜索引擎優(yōu)化叛溢,是一種技術塑悼,目的是提高網(wǎng)頁在搜索引擎的排行
如何能提高排行:
1. 給百度花錢
2. 找專業(yè)的優(yōu)化團隊
3. 開發(fā)過程中注意優(yōu)化,例如楷掉,在不影響頁面結構的情況下多使用語義化標簽厢蒜!img的title等也需要設置,title標簽必須有,通過meta標簽設置description斑鸦、keywords愕贡、author;不需要使用ajax獲取的數(shù)據(jù)就不要獲取了巷屿,盡量可以使用服務端渲染數(shù)據(jù)的方式
---
利用http.get方法去獲取到某網(wǎng)址的html文件內(nèi)容固以,然后利用cheerio工具進行關鍵內(nèi)容的提取
1. 獲取到 https://www.lagou.com這個接口的數(shù)據(jù)(其實就是拉鉤的首頁文件)
因為http、https模塊可以向其他的服務器發(fā)送請求攒庵,依靠的是request方法或者其子方法:get嘴纺、post;
```
let target = 'https://www.lagou.com'
const https = require('https')
//發(fā)送一個get請求,res是一個返回的對象浓冒,里面并沒有直接包含數(shù)據(jù)栽渴,需要使用data事件來接收返回的數(shù)據(jù)
//且每次返回的只是一個片段,所以會觸發(fā)多次稳懒,因為可能請求到的數(shù)據(jù)量特大闲擦,如果一次性返回的話可能導致服務器崩潰,所以Node采取事件的方式多次返回
https.get(target, function(res) {
let result = ''
res.on("data",(chunk)=>{
result += chunk
})
res.on("end",()=>{//當接收完后觸發(fā)
//在這里就可以處理數(shù)據(jù)result
})
})
```
2. 爬取其上面的一級场梆、二級標題的內(nèi)容
因為獲取到的是一個大的html格式的字符串墅冷,所以想要爬取其中某些標簽的內(nèi)容不能直接使用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)//通過回調(diào)函數(shù)來處理解析后的數(shù)據(jù)
}
```
3.? 再把爬取出的內(nèi)容存到一個文件里
```
spideContent(result,(menus)=>{
fs.writeFileSync('./lagou.json',JSON.stringify(menus))
})
```
##### JS小知識擴展
如何將偽數(shù)組轉(zhuǎn)換成真正的數(shù)組顶岸?
1. 常見的偽數(shù)組都有哪些腔彰?arguments、通過document.getElements..獲取到的內(nèi)容辖佣;
2. 偽數(shù)組有什么特點霹抛,具有l(wèi)ength屬性,也是一個一個的元素組成的卷谈,但是構造器不是Array杯拐,不能使用數(shù)組的方法
3. 轉(zhuǎn)換為真正數(shù)組的方法:
通過遍歷將偽數(shù)組里元素放入到一個新的數(shù)組里
```
let arg = arguments//這就是一個典型的偽數(shù)組
let arr = []
for (var i = 0; i < arg.length; i++) {
arr.push(arg[i])
}
console.log(arr)
```
通過call改變數(shù)組slice方法里的this指向
因為我想要讓偽數(shù)組也能使用數(shù)組的方法,為什么偽數(shù)組就不能使用數(shù)組方法世蔗,為什么數(shù)組就能使用push方法了
一個數(shù)組都是由它的構造器實例化出來的端逼,var a = []//這是js的語法糖;正規(guī)的用法:var a = new Array()
因為Array是一個構造函數(shù)凸郑,每一個構造函數(shù)都有原型,且構造函數(shù)構造出來的實例可以使用原型上的方法裳食,也就是說因為Array的原型上有一些方法,所以每一個數(shù)組都可以使用這些push等等的方法
因為偽數(shù)組的構造器不是Array芙沥,當然不能使用Array原型上的push方法
現(xiàn)在數(shù)組有一個方法slice,這個方法每次都會返回一個新數(shù)組,如果不傳參數(shù)的話而昨,返回的新數(shù)組的元素和原數(shù)組的元素是一模一樣的
如果偽數(shù)組也能執(zhí)行這個slice方法的話救氯,那么是不是就會返回一個新的真正的數(shù)組,并且元素一樣歌憨,但是不能直接執(zhí)行
所以我們使用偷梁換柱的方法着憨,讓一個真正的數(shù)據(jù),或者直接從Array.prototype上執(zhí)行slice方法务嫡,但是在執(zhí)行的時候通過call來將里面的this換成咱們的偽數(shù)組甲抖,這樣的話,就會返回一個元素和偽數(shù)組元素一樣的真正數(shù)組了
```
let arr = [].slice.call(arg) //Array.prototype.slice.call(arg)
```
---
##### requestGet/requestPost
1. 通過request來get數(shù)據(jù)(請求的是豆瓣電影的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'
};
//創(chuàng)建一個請求的對象
var req = http.request(options, function(res) {//回調(diào)函數(shù)能接收到響應對象
//狀態(tài)碼 res.statusCode
//響應頭 res.headers
res.setEncoding('utf8');//設置字符編碼
let result = ''
res.on('data', function (chunk) {//通過data事件來接收數(shù)據(jù)
result+=chunk
});
res.on('end', function () {//接收完成后觸發(fā)
fs.writeFileSync('./movie.json',result)
});
});
//綁定error事件心铃,如果出錯會執(zhí)行
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
//標識請求完成
req.end();
```
2. 通過request的post方法來請求:
注意准谚,get方法發(fā)送的參數(shù)是拼接在url上的,post是以formdata存在的,但是發(fā)送過去的必須都得是字符串格式
大部分步驟基本一樣去扣,但是需要注意的是柱衔,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)
//定義好要發(fā)送的數(shù)據(jù)
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發(fā)送的時候需要將請求頭的Content-Length設置為發(fā)送數(shù)據(jù)的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);
});
//寫入請求發(fā)送的數(shù)據(jù)
req.write(postData);
req.end();
```
###### 小作業(yè)
總結POST和GET的區(qū)別,每位同學都寫一個md文檔
---
##### fileStystem模塊
后端語言都有操作文件系統(tǒng)的能力愉棱,在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.創(chuàng)建一個目錄
```
//創(chuàng)建一個目錄唆铐,如果目錄已存在的話就會返回錯誤信息
fs.mkdir("logs",function(err){
if(err){
console.log(err);
}else{
console.log('success');
}
})
```
3.寫入文件
```
//給文件寫內(nèi)容,當文件不存在會創(chuàng)建一個文件奔滑,第二個參數(shù)為寫入的內(nèi)容,每次寫入都會覆蓋
fs.writeFile('logs/hello.log','hello everyone\n',function(err){
if(err){
console.log(err);
}else{
console.log('success')
}
})
//給文件中追加內(nèi)容
fs.appendFile("logs/hello.log",'hello world\n',function(err){
if(err){
console.log(err);
}else{
console.log('success')
}
})
```
4.讀取文件內(nèi)容
```
//讀取文件內(nèi)容的方法艾岂,第二個參數(shù)可選,為讀取的編碼格式
fs.readFile("logs/hello.log",'utf-8',function(err,data){
if(err){
console.log(err);
}else{
console.log(data)
}
})
```
5.讀取目錄內(nèi)容
```
//讀取目錄內(nèi)容朋其,返回一個數(shù)組
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.小練習:創(chuàng)建一個copy復制文件的函數(shù)
```
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}
```
---