nodejs抓取动态网页(使用nodejs写爬虫过程中常用模块和一些必须掌握的js模块)

优采云 发布时间: 2021-11-23 10:12

  nodejs抓取动态网页(使用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);

});

}

复制代码

  转载于:

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线