nodejs抓取动态网页(使用nodejs写爬虫过程中常用模块和一些必须掌握的js模块)
优采云 发布时间: 2021-11-23 10:12nodejs抓取动态网页(使用nodejs写爬虫过程中常用模块和一些必须掌握的js模块)
本文是使用 nodejs 编写爬虫系列教程的第一篇。介绍了使用nodejs编写爬虫过程中常用的模块以及一些必须掌握的js语法。
常用模块
常用的模块如下:
fs-extrasuperagentcheeriolog4jssequelizechalkpuppeteerfs-extra
使用async/await的前提是接口必须封装成promise。这是一个简单的例子:
const sleep = (milliseconds) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), milliseconds)
})
}
const main = async () => {
await sleep(5000);
console.log('5秒后...');
}
main();
复制代码
通过在 async 函数中使用 await 调用 promise 来组织异步代码就像同步代码一样,非常自然,有助于我们分析代码的执行流程。
在 node 中, fs 模块是一个非常常用的原生模块,用于操作文件。fs(文件系统)模块提供了一些与文件系统相关的同步和异步API。有时需要使用同步 API。在自己编写的模块中访问文件后导出一些接口时,此时使用同步API是非常实用的,但是为了充分发挥node异步的优势,还是应该尽量使用异步接口.
我们可以使用 fs-extra 模块代替 fs 模块。类似的模块包括 mz。fs-extra 收录了 fs 模块的所有接口,同时也为每个异步接口提供了 promise 支持。更好的是,fs-extra 还提供了一些其他有用的文件操作功能,例如删除和移动文件。更详细的介绍请查看官方仓库fs-extra。
超级代理
Superagent是一个节点的http客户端,类似于java中的httpclient和okhttp和python中的requests。让我们模拟http请求。超级代理库具有许多实用功能。
superagent会根据响应的content-type自动序列化,序列化的返回内容可以通过response.body获取
这个库会自动缓存和发送cookies,不需要我们手动管理cookies
第二点就是它的api是链式调用的风格,调用起来很爽,但是使用的时候要注意调用顺序。
它的异步 API 都返回承诺。
非常方便。官方文档是一个很长的页面,目录很清晰,很容易搜索到你需要的内容。最后,superagent 还支持插件集成。比如需要超时后自动重发,可以使用superagent-retry。更多插件可以到npm官网搜索关键词superagent-。更多详情请看官方文档superagent
// 官方文档的一个调用示例
request
.post('/api/pet')
.send({ name: 'Manny', species: 'cat' })
.set('X-API-Key', 'foobar')
.set('Accept', 'application/json')
.then(res => {
alert('yay got ' + JSON.stringify(res.body));
});
复制代码
啦啦队
写过爬虫的都知道,我们经常需要解析html,从网页源代码中爬取信息应该是最基本的爬取方式。python中有beautifulsoup,java中有jsoup,node中有cheerio。
Cheerio 是为服务器端设计的,给你一个近乎完整的jquery 体验。使用cheerio解析html获取元素,调用方法与jquery操作dom元素的用法完全一样。它还提供了一些方便的接口,比如获取html,看一个例子:
const cheerio = require('cheerio')
const $ = cheerio.load('Hello world')
$('h2.title').text('Hello there!')
$('h2').addClass('welcome')
$.html()
//=> Hello there!
复制代码
官方仓库:cheerio
日志4js
log4j 是一个为 node 设计的日志模块。类似的模块也有debug模块,不过我觉得log4js满足了我对日志库的需求。调试模块可以用于简单的场景。事实上,他们有不同的定位。debug 模块只是为了调试而设计的,而 log4js 是一个日志库,必须提供文件输出和分类功能。
log4js 模块的名字有点符合java中著名的日志库log4j。Log4j 具有以下特点:
您可以自定义 appender(输出目的地),lo4js 甚至提供了一个输出到邮件和其他目的地的 appender。通过组合不同的appender,可以实现不同目的的记录器(loggers)提供日志分级功能。官方FAQ提到如果要进行appender级别的过滤,可以使用logLevelFilter提供滚动日志和自定义输出格式
通过我最近的爬虫项目的配置文件来感受一下下面这个库的特点:
const log4js = require('log4js');
const path = require('path');
const fs = require('fs-extra');
const infoFilePath = path.resolve(__dirname, '../out/log/info.log');
const errorFilePath = path.resolve(__dirname, '../out/log/error.log');
log4js.configure({
appenders: {
dateFile: {
type: 'dateFile',
filename: infoFilePath,
pattern: 'yyyy-MM-dd',
compress: false
},
errorDateFile: {
type: 'dateFile',
filename: errorFilePath,
pattern: 'yyyy-MM-dd',
compress: false,
},
justErrorsToFile: {
type: 'logLevelFilter',
appender: 'errorDateFile',
level: 'error'
},
out: {
type: 'console'
}
},
categories: {
default: {
appenders: ['out'],
level: 'trace'
},
qupingce: {
appenders: ['out', 'dateFile', 'justErrorsToFile'],
level: 'trace'
}
}
});
const clear = async () => {
const files = await fs.readdir(path.resolve(__dirname, '../out/log'));
for (const fileName of files) {
fs.remove(path.resolve(__dirname, `../out/log/${fileName}`));
}
}
module.exports = {
log4js,
clear
}
复制代码
续集
我们在写项目的时候经常会有持久化的需求。简单的场景可以使用JSON来保存数据。如果数据量比较大,又容易管理,那就要考虑使用数据库了。如果是操作mysql和sqllite,建议使用sequelize,如果是mongodb,我建议使用专门为mongodb设计的mongoose
sequelize 的一些东西我觉得还是有点不好的,比如默认的生成id(主键)、createdAt 和updatedAt。
撇开一些自以为是的小问题不谈,sequelize 设计的很好,内置的算子、钩子、验证器都很有意思。Sequelize 还提供 promise 和 typescript 支持。如果您正在使用 typescript 开发项目,那么有一个很好的 orm 选项:typeorm。详情请看官方文档:sequelize
粉笔
粉笔在中文中是粉笔的意思。该模块是一个非常有特色和实用的node模块。它可以为您的输出添加颜色、下划线、背景颜色和其他装饰。当我们在写项目的时候需要输出请求信息等内容时,我们可以适当的使用chalk来高亮一些内容,比如请求的URL加下划线。
const logRequest = (response, isDetailed = false) => {
const URL = chalk.underline.yellow(response.request.url);
const basicInfo = `${response.request.method} Status: ${response.status} Content-Type: ${response.type} URL=${URL}`;
if (!isDetailed) {
logger.info(basicInfo);
} else {
const detailInfo = `${basicInfo}\ntext: ${response.text}`;
logger.info(detailInfo);
}
};
复制代码
调用上面的logRequest效果:
更多信息请看官方仓库粉笔
傀儡师
如果你没有听说过这个库,你可能听说过 selenium。puppeteer 是 Google Chrome 团队开源的一个节点模块,它通过 devtools 协议操作 chrome 或 Chromium。质量有保证。该模块提供了一些高级 API。默认情况下,通过该库操作的浏览器用户是看不到界面的,这就是所谓的无头浏览器。当然也可以通过配置以界面方式启动模式。在 chrome 中还有一些专门用于记录 puppeteer 操作的扩展,例如 Puppeteer Recorder。使用这个库,我们可以抓取一些由js渲染的信息,而不是直接存在于页面的源代码中。比如spa页面中,页面的内容是js渲染的。这时候puppeteer就为我们解决了这个问题。当页面上出现某个标签时,我们可以调用puppeteer来获取页面渲染出来的html。事实上,很多高难度爬虫的终极法宝就是操纵浏览器。
Pre-js 语法 async/await
首先要提到的是async/await,因为node很早就已经支持async/await(node 8 LTS),现在写后端项目时没有理由不使用async/await。使用 async/await 可以让我们摆脱回调炼狱的困境。这里主要讲一下使用async/await时可能遇到的问题。如何使用 async/await 进行并发?
看一段测试代码:
const sleep = (milliseconds) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), milliseconds)
})
}
const test1 = async () => {
for (let i = 0, max = 3; i < max; i++) {
await sleep(1000);
}
}
const test2 = async () => {
Array.from({
length: 3}).forEach(async () => {
await sleep(1000);
});
}
const main = async () => {
console.time('测试 for 循环使用 await');
await test1();
console.timeEnd('测试 for 循环使用 await');
console.time('测试 forEach 调用 async 函数')
await test2();
console.timeEnd('测试 forEach 调用 async 函数')
}
main();
复制代码
操作的结果是:
测试 for 循环使用 await: 3003.905ms
测试 forEach 调用 async 函数: 0.372ms
复制代码
我想有些人可能会认为测试 forEach 的结果会在 1 秒左右。实际上,test 2 等价于如下代码:
const test2 = async () => {
// Array.from({length: 3}).forEach(async () => {
// await sleep(1000);
// });
Array.from({
length: 3}).forEach(() => {
sleep(1000);
});
}
复制代码
转载于: