所谓爬虫,就是用来爬取网页的内容。
在 nodejs 中,可以使用 http 和 https 模块来实现简单爬虫。会使用到一个模块,cheerio 模块。该模块可以将返回的字符串转为 jQuery 中的 $ 对象,从而可以使用 jQuery 中的各种方法。
const https = require('https');
const cheerio = require('cheerio');
// 爬虫的思路很简单,其实就是发送 http 请求,去请求那个页面
// 接下来拿到响应的内容,然后筛选有用的信息
https.get('https://tuijian.hao123.com/hotrank',(req)=>{
let data = ''; // 装回来的数据
req.on('data',(chunk)=>{
data += chunk
});
req.on('end',()=>{
filter(data);
})
})
function filter(data){
let result = []; // 存放筛选后的数据
const $ = cheerio.load(data); // 将字符串传递给 cheerio 模块,会生成一个 jQuery 对象,我们就可以在服务器端使用 jQuery 方法
const temp_arr = $('[monkey="ss"]').find('.point-bd').find('.point-title')
temp_arr.each((index,item)=>{
result.push($(item).text());
})
console.log(result);
}
数据IO操作相关模块
这里会涉及到 Buffer、Stream 文件流。
Buffer
在最早的时候,js在浏览器里面运行,也就是说意味着 js 是在客户端运行,所以,那个时候,js只能操作字符串。随着node的出现,js 可以做服务器端的开发,所以也就有操作二进制数据的需求,Buffer 可以让我们操作二进制数据。
我们简单介绍一下Buffer 相关的方法。
Buffer相当于是在内存里面开辟了一段空间。我们程序员可以手动的去指定这段内存空间的大小。
let buf1 = new Buffer.alloc(5);
console.log(buf1); // <Buffer 00 00 00 00 00>
往buffer里面写入数据,如下:
let buf1 = new Buffer.alloc(5);
buf1.write('a');
console.log(buf1);
再来看一个buffer的示例:
let buf = new Buffer.alloc(26);
for(let i=0;i<26;i++){
buf[i] = i + 97;//小a的按下去的码
}
console.log(buf.toString());
Stream 文件流
流的概念
所谓流,就像水流一样。例如,我们在网上在线看电影,或者下载电影,不是说电影这个资源一瞬间就达到我们客户端,而是以流的形式,一点一点过来的。这里其实就涉及到了流的概念。
理想的方式,读一部分,写一部分,不管文件有多大,只要时间允许,总会处理完。
在 nodejs 中,就允许我们来操作这个流。
模块名为 stream,提供了几个常用的事件:
- data:当有数据可读时触发
- end:没有更多的数据可读时会触发
- error:发生错误的时候,会触发
- finish:所有数据写入底层的时候会触发
在stream中,常见的流有2块:
- readable:可读流
- writable:可写流
前面我们学习了fs模块,在fs中,读取文件的api是readFile,但是这个api其实是nodejs已经给你封装过一次,其实读取文件的底层,仍然是采用流的操作来读取。接下来,我们来通过流的形式来读取文件:
const fs = require('fs');
// 创建一个可读流
const rs = fs.createReadStream('./test.txt');
rs.setEncoding('utf8');
rs.on('data',(chunk)=>{
console.log(chunk);
console.log('-----------------------------');
})
可写流
通过可写流来复制文件。
const fs = require('fs');
// 创建一个可读流
const rs = fs.createReadStream('./test.txt');
// 创建一个可写流
const ws = fs.createWriteStream('./test2.txt');
rs.setEncoding('utf8');//使用这个不会乱码
rs.on('data',(chunk)=>{
ws.write(chunk);
})
rs.on('end',()=>{
console.log('复制完成!');
})
使用pipe方法来处理流
使用 pipe 方法,当我们处理大型文件,效率就要高一些,它相当于在两个流文件之间建立了一个管道。
接下来我们来使用 pipe 方法简化上面的文件操作的步骤:
const fs = require('fs');
// 创建一个可读流
const rs = fs.createReadStream('./test.txt');
// 创建一个可写流
const ws = fs.createWriteStream('./test3.txt');
rs.setEncoding('utf8');
rs.pipe(ws);
资源压缩
在做web 开发的时候,服务器端向客户端返回数据,往往这个数据需要将其压缩,
(1)要明确浏览器端是否接受压缩文件
在浏览器发送http请求的时候,请求头里面会有一个键值对,Accept-Encoding,表示我这个浏览器可以接收的编码格式,如果里面包含 gzip,说明该浏览器可以接收压缩文件
(2)nodejs里面如何对文件进行压缩
会使用nodejs里面的一个内置模块zlib。
const fs = require('fs');
const zlib = require('zlib');
// 创建一个压缩文件的对象,这个对象负责压缩文件
const gzip = zlib.createGzip();
const rs = fs.createReadStream('./test.txt'); // 创建了一个可读流
const ws = fs.createWriteStream('./test.txt.gz'); // 创建了一个可写流
rs.pipe(gzip).pipe(ws);
解压缩其实就是压缩的一个反向操作。
const zlib = require('zlib');
// 创建一个压缩文件的对象,这个对象负责压缩文件
const gunzip = zlib.createGunzip();
const rs = fs.createReadStream('./test.txt.gz');
const ws = fs.createWriteStream('./test10.txt');
rs.pipe(gunzip).pipe(ws);
(3)实际应用
在实际开发中,我们需要首先判断用户的浏览器是否支持压缩文件,示例代码如下:
const fs = require('fs');
const zlib = require('zlib');
const http = require('http');
const filePath = './index2.html'; // 假设这是我要向客户端返回的文件
http.createServer((req,res)=>{//创建服务器
// 获取 accept-encoding 的值
const acceptEncoding = req.headers['accept-encoding'];//请求头的键值对
if(acceptEncoding.indexOf('gzip')!=-1){//判断浏览器是否支持压缩;indeOf是找到键值对的下标
// 进入 if,说明存在 gzip 字符,也就是说,支持压缩
res.writeHead(200,{'Content-type':'gzip'})// 修改响应头里面的某些键值对,gzip压缩格式
const gzip = zlib.createGzip();//zlib.createGzip()返回方法给gzip
fs.createReadStream(filePath).pipe(gzip).pipe(res);//把客户端返回的文件弄成可读流,然后弄成压缩文件给响应体渲染出去
} else {
// 说明不支持
fs.createReadStream(filePath).pipe(res);//直接把可读流渲染出去
}
}).listen(3000);