使用NodeJs爬取数据
前言
最近因为一个外行朋友让我帮忙整理一个网站的数据,我第一时间就想到了就写爬虫去爬取,相对于NodeJs来,我更熟练一点python
,但是我也不会专业的爬虫,只会简单的请求和文本分析这样。所以我找到了这个网站的数据页面,然后简单的使用python写了三行代码:导入request
包、请求网页、打印结果,如下:
import requests
page = requests.get("http://xxx.com")
print(page.content)
然而,可怕的事情发生了,我发现输出的结果和我看到的不太一样,我又用浏览器访问那个那个url,我大吃一惊,网页上显示说监测到我使用了爬虫工具,然后对于这个页面,我的IP被封了,o(╥﹏╥)o。
也许对于专业些的人来说,这些都是常事,这些应该是叫反爬措施(好像是这样叫)吧,但是对于我来说就一脸茫然与恐惧。
不过我分析觉得解决办法应该也不难,可能他们的反爬措施就是根据header或者cookie来判断的,也许是因为python
的request
的请求里面包含了什么标识符什么的,因为我使用postman是可以拿到那个网页的,但是,说的简单,我也不熟悉哇o(╥﹏╥)o,最后我尝试使用了NodeJs去请求那个URL,发现可以,没有将我封掉,而且chrome
那里可以针对请求 copy as NodeJs fetch
✌️,我仿佛找到了新大陆,NodeJS之前也会玩下,但是因为用的很少,所以这次因为使用NodeJS解决了我的问题,我对NodeJs也是更加喜爱,所以为了熟练使用,于是,就有了这篇文章记录我使用NodeJs爬取数据时的一些步骤以及方法。
网络请求
原始方法
NodeJs 对于GET/POST的元素请求方法是使用
http
包中的request
方法来进行请求,数据通过querystring
来序列化等,下面是一个简单的GET请求:var http = require('http'); var qs = require('querystring'); var data = { a: 123, time: new Date().getTime() };//这是需要提交的数据 var content = qs.stringify(data); var options = { hostname: '127.0.0.1', port: 10086, path: '/getTest/?' + content, method: 'GET' }; var req = http.request(options, function (res) { console.log('STATUS: ' + res.statusCode); console.log('HEADERS: ' + JSON.stringify(res.headers)); res.setEncoding('utf8'); res.on('data', function (chunk) { console.log('BODY: ' + chunk); }); }); req.on('error', function (e) { console.log('problem with request: ' + e.message); }); req.end();
(¦3」∠) 对于习惯了用第三方包的我来说,看到这样一个简单的请求还需要这么繁琐就觉得非常难受,所以这种方式我也是不建议使用的。推荐的方式是使用
axios
和node-fetch
Axios
axios我最开始接触是那个时候在做vue开发的时候接触的,怎么说呢,它的api通俗易懂、简单易用,非常容易上手,所以我在使用NodeJs做网络请求的时候第一时间就想到了这个包。
安装方式也非常简单:
npm install axios | bower install axios | yarn add axios
它的使用也是非常的简单的,比如一个POST请求:
const axios = require('axios');
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(response=> {
console.log(response);
})
.catch(error=> {
console.log(error);
});
是否非常简单? 更多使用方式可以参考主页地址。
Node-fetch
好吧,
fetch
我之前是听过,只是因为那个时候在用axios
用的挺舒服的,所以也就没有去专研过它,这次用到他其实也是因为chrome
上面的copy as NodeJs fetch
才接触,感觉其实也挺好的,不过遇到个问题就是:如果这个请求不是json格式的返回数据只是html页面的时候该如何获取?
fetch
是一种HTTP数据请求的方式,是XMLHttpRequest的一种替代方案。fetch
不是ajax的进一步封装,而是原生js。Fetch函数就是原生js,没有使用XMLHttpRequest对象。而
node-fetch
你可以认为是NodeJS版的fetch
。安装方式也非常简单:
npm install node-fetch
-
请求text或者html页面
const fetch = require('node-fetch'); (async () => { const response = await fetch('https://baidu.com'); const body = await response.text(); console.log(body); })();
-
请求JSON
const fetch = require('node-fetch'); (async () => { const response = await fetch('https://api.github.com/users/github'); const json = await response.json(); console.log(json); })();
-
POST请求
const fetch = require('node-fetch'); (async () => { const body = {a: 1}; const response = await fetch('https://httpbin.org/post', { method: 'post', body: JSON.stringify(body), headers: {'Content-Type': 'application/json'} }); const json = await response.json(); console.log(json); })();
更多详细说明请参考 官方网站
网页分析工具 cheerio
有的时候我们去网上爬取数据的时候,不一定有现成的数据接口,这个时候你只有获取到一个html的文本数据,这个时候你就需要通过一些方式去分析这个文本里面拿你想要的数据了,在我知道的方法里有两个方法:一是通过正则的方式去匹配,这种方式感觉好像效率不是很高,不推荐用,但是是万能的。二是通过DOM的方法根据节点来查找。
但是网页里面可以通过Jquery或者Document的方式来获取节点,面对一个html文本的时候NodeJs该如何去分析出数据呢?通过网上的查找我发现了可以使用 cheerio这个库,可以加载html文本,然后像jquery一样来操作即可。
安装方式:
npm install cheerio
使用示例:
const cheerio = require('cheerio'),
let html = '
- Apple
- Orange
- Pear
';
$ = cheerio.load(html);
// 使用选择器去获取数据
$('.apple', '#fruits').text()
//=> Apple
$('ul .pear').attr('class')
//=> pear
$('li[class=orange]').html()
//=> Orange
cheerio是一个非常强大的工具,感兴趣的话可以去参考其 官网
文件系统
有的时候,我们数据处理好了之后,需要写入到文件中,这个时候我们就要使用到NodeJS的文件系统了,因为NodeJs的文件系统的接口非常的好用,我也没有去找过有没有第三方相关的包了,这里也推荐使用NodeJs的内置文件系统的Api
Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
使用示例:
const fs = require('fs')
// 异步读取
function readTest() {
console.log("开始读取...");
fs.readFile("./temp.txt",{encoding:'utf-8'},(err,data)=>{
if (err) {
console.log("err:"+err);
}
console.log("文件内容为:"+data);
})
console.log("程序执行完毕!!!");
}
// 同步读取
function readSyncTest() {
console.log("开始读取...");
let data = fs.readFileSync("./temp.txt",'utf-8')
console.log("文件内容为:"+data);
console.log("程序执行完毕!!!");
}
// 异步写入
function writeTest() {
console.log("文件开始写入...");
let content = "Hello world"
fs.writeFile('./temp2.txt',content,err=>{
if (err) {
console.log("err:"+err);
}
console.log("文件写入完毕");
})
console.log("程序执行完毕");
}
// 同步写入
function writeSyncTest() {
console.log("文件开始写入...");
let content = "Hello world"
fs.writeFileSync('./temp2.txt',content)
console.log("程序执行完毕");
}
一些接口:
序号 | 方法 & 描述 |
---|---|
1 | fs.rename(oldPath, newPath, callback)异步 rename().回调函数没有参数,但可能抛出异常。 |
2 | fs.ftruncate(fd, len, callback)异步 ftruncate().回调函数没有参数,但可能抛出异常。 |
3 | fs.ftruncateSync(fd, len)同步 ftruncate() |
4 | fs.truncate(path, len, callback)异步 truncate().回调函数没有参数,但可能抛出异常。 |
5 | fs.truncateSync(path, len)同步 truncate() |
6 | fs.chown(path, uid, gid, callback)异步 chown().回调函数没有参数,但可能抛出异常。 |
7 | fs.chownSync(path, uid, gid)同步 chown() |
8 | fs.fchown(fd, uid, gid, callback)异步 fchown().回调函数没有参数,但可能抛出异常。 |
9 | fs.fchownSync(fd, uid, gid)同步 fchown() |
10 | fs.lchown(path, uid, gid, callback)异步 lchown().回调函数没有参数,但可能抛出异常。 |
11 | fs.lchownSync(path, uid, gid)同步 lchown() |
12 | fs.chmod(path, mode, callback)异步 chmod().回调函数没有参数,但可能抛出异常。 |
13 | fs.chmodSync(path, mode)同步 chmod(). |
14 | fs.fchmod(fd, mode, callback)异步 fchmod().回调函数没有参数,但可能抛出异常。 |
15 | fs.fchmodSync(fd, mode)同步 fchmod(). |
16 | fs.lchmod(path, mode, callback)异步 lchmod().回调函数没有参数,但可能抛出异常。Only available on Mac OS X. |
17 | fs.lchmodSync(path, mode)同步 lchmod(). |
18 | fs.stat(path, callback)异步 stat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。 |
19 | fs.lstat(path, callback)异步 lstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。 |
20 | fs.fstat(fd, callback)异步 fstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。 |
21 | fs.statSync(path)同步 stat(). 返回 fs.Stats 的实例。 |
22 | fs.lstatSync(path)同步 lstat(). 返回 fs.Stats 的实例。 |
23 | fs.fstatSync(fd)同步 fstat(). 返回 fs.Stats 的实例。 |
24 | fs.link(srcpath, dstpath, callback)异步 link().回调函数没有参数,但可能抛出异常。 |
25 | fs.linkSync(srcpath, dstpath)同步 link(). |
26 | fs.symlink(srcpath, dstpath[, type], callback)异步 symlink().回调函数没有参数,但可能抛出异常。 type 参数可以设置为 'dir', 'file', 或 'junction' (默认为 'file') 。 |
27 | fs.symlinkSync(srcpath, dstpath[, type])同步 symlink(). |
28 | fs.readlink(path, callback)异步 readlink(). 回调函数有两个参数 err, linkString。 |
29 | fs.realpath(path[, cache], callback)异步 realpath(). 回调函数有两个参数 err, resolvedPath。 |
30 | fs.realpathSync(path[, cache])同步 realpath()。返回绝对路径。 |
31 | fs.unlink(path, callback)异步 unlink().回调函数没有参数,但可能抛出异常。 |
32 | fs.unlinkSync(path)同步 unlink(). |
33 | fs.rmdir(path, callback)异步 rmdir().回调函数没有参数,但可能抛出异常。 |
34 | fs.rmdirSync(path)同步 rmdir(). |
35 | fs.mkdir(path[, mode], callback)S异步 mkdir(2).回调函数没有参数,但可能抛出异常。 访问权限默认为 0777。 |
36 | fs.mkdirSync(path[, mode])同步 mkdir(). |
37 | fs.readdir(path, callback)异步 读取目录的内容。 |
38 | fs.readdirSync(path)同步 readdir() 返回文件数组列表。 |
39 | fs.close(fd, callback)异步 close().回调函数没有参数,但可能抛出异常。 |
40 | fs.closeSync(fd)同步 close(). |
41 | fs.open(path, flags[, mode], callback)异步打开文件。 |
42 | fs.openSync(path, flags[, mode])同步 version of fs.open(). |
43 | fs.utimes(path, atime, mtime, callback) |
44 | fs.utimesSync(path, atime, mtime)修改文件时间戳,文件通过指定的文件路径。 |
45 | fs.futimes(fd, atime, mtime, callback) |
46 | fs.futimesSync(fd, atime, mtime)修改文件时间戳,通过文件描述符指定。 |
47 | fs.fsync(fd, callback)异步 fsync.回调函数没有参数,但可能抛出异常。 |
48 | fs.fsyncSync(fd)同步 fsync. |
49 | fs.write(fd, buffer, offset, length[, position], callback)将缓冲区内容写入到通过文件描述符指定的文件。 |
50 | fs.write(fd, data[, position[, encoding]], callback)通过文件描述符 fd 写入文件内容。 |
51 | fs.writeSync(fd, buffer, offset, length[, position])同步版的 fs.write()。 |
52 | fs.writeSync(fd, data[, position[, encoding]])同步版的 fs.write(). |
53 | fs.read(fd, buffer, offset, length, position, callback)通过文件描述符 fd 读取文件内容。 |
54 | fs.readSync(fd, buffer, offset, length, position)同步版的 fs.read. |
55 | fs.readFile(filename[, options], callback)异步读取文件内容。 |
56 | fs.readFileSync(filename[, options]) |
57 | fs.writeFile(filename, data[, options], callback)异步写入文件内容。 |
58 | fs.writeFileSync(filename, data[, options])同步版的 fs.writeFile。 |
59 | fs.appendFile(filename, data[, options], callback)异步追加文件内容。 |
60 | fs.appendFileSync(filename, data[, options])The 同步 version of fs.appendFile. |
61 | fs.watchFile(filename[, options], listener)查看文件的修改。 |
62 | fs.unwatchFile(filename[, listener])停止查看 filename 的修改。 |
63 | fs.watch(filename[, options][, listener])查看 filename 的修改,filename 可以是文件或目录。返回 fs.FSWatcher 对象。 |
64 | fs.exists(path, callback)检测给定的路径是否存在。 |
65 | fs.existsSync(path)同步版的 fs.exists. |
66 | fs.access(path[, mode], callback)测试指定路径用户权限。 |
67 | fs.accessSync(path[, mode])同步版的 fs.access。 |
68 | fs.createReadStream(path[, options])返回ReadStream 对象。 |
69 | fs.createWriteStream(path[, options])返回 WriteStream 对象。 |
70 | fs.symlink(srcpath, dstpath[, type], callback)异步 symlink().回调函数没有参数,但可能抛出异常。 |
更多详情参考 官网说明
Demo
可参考我的另一篇文章:使用TypeScript写node脚本爬取小说