本篇是使用 nodejs 写爬虫系列教程的第一篇, 介绍了使用 nodejs 写爬虫过程当中经常使用的模块和一些必须掌握的 js 语法javascript
经常使用模块有如下几个:html
使用 async/await 的前提是必须将接口封装成 promise, 下面举一个简单的例子:java
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
在 node 中 fs 模块是一个很经常使用的操做文件的 native 模块,fs (file system) 模块提供了和文件系统相关的一些同步和异步的 api, 有时候使用同步 api 很是有必要,好比你要在一个本身写的模块中的在访问文件后导出一些接口时,这个时候用同步的 api 就很实用,可是为了充分发挥 node 异步的优点,咱们仍是应该尽可能使用异步接口。python
咱们彻底可使用 fs-extra 模块来代替 fs 模块, 相似的模块还有 mz。fs-extra 包含了全部的 fs 模块的接口,还对每一个异步接口提供了promise 支持,更棒的是 fs-extra 还提供了一些其它的实用文件操做函数, 好比删除移动文件的操做。更详细的介绍请查看官方仓库 fs-extra。mysql
superagent 是一个 node 的 http client, 能够类比 java 中的 httpclient 和 okhttp, python 中的 requests。可让咱们模拟 http 请求。superagent 这个库有不少实用的特色。jquery
superagent 会根据 response 的 content-type 自动序列化,经过 response.body 就能够获取到序列化后的返回内容git
这个库会自动缓存和发送 cookies, 不须要咱们手动管理 cookiesgithub
再来就是它的 api 是链式调用风格的,调用起来很爽,不过使用的时候要注意调用顺序web
它的异步 api 都是返回 promise的。
很是方便有木有😋。官方文档就是很长的一页,目录清晰,很容易就搜索到你须要的内容。最后,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('<h2 class="title">Hello world</h2>')
$('h2.title').text('Hello there!')
$('h2').addClass('welcome')
$.html()
//=> <h2 class="title welcome">Hello there!</h2>
复制代码
官方仓库: cheerio
log4j 是一个为 node 设计的日志模块,相似的模块还有 debug 模块,不过我是以为 log4js 符合我对日志库的需求,场景简单状况下可使用 debug 模块。 其实他俩定位也不同,debug 模块只是为调试而设计的,log4js 则是一个日志库,确定得提供文件输出和分级等功能。
log4js 模块看名字有点向 java 中颇有名的日志库 log4j 看齐的节奏。log4j 有如下特色:
下面经过我最近一个爬虫项目的配置文件来感觉如下这个库的特色:
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
(primary key), createdAt
和 updatedAt
。
抛开一些自做主张的小毛病,sequelize 设计的仍是很好的,内置的操做符,hooks, 还有 validators 颇有意思。sequelize 还提供了 promise 和 typescript 支持。若是是使用 typescript 开发项目,还有一个很好的 orm 选择 : typeorm。更多详情查看官方文档: sequelize
chalk 中文意思是粉笔的意思,这个模块是 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效果:
更多信息查看官方仓库chalk
若是这个库没据说过,你可能据说过 selenium。puppeteer 是 Google Chrome 团队开源的一个经过 devtools 协议操纵 chrome 或者Chromium 的 node 模块。质量有所保证。这个模块提供了一些高级的 api, 默认状况下,这个库操纵的浏览器用户是看不到界面的,也就是所谓的无头(headless)浏览器。固然能够经过配置启动有界面的模式。在 chrome 中还有一些专门录制 puppeteer 操做的扩展, 好比Puppeteer Recorder。使用这个库咱们能够用来抓取一些经过 js 渲染而不是直接存在于页面源代码中的信息。好比 spa 页面,页面内容都是 js 渲染出来的。这个时候 puppeteer 就为咱们解决了这个问题,咱们能够调用 puppeteer 在页面某个标签出现时获取到页面当时的渲染出来的 html。事实上,每每不少比较困难的爬虫解决的最终法宝就是操纵浏览器。
首先要提的就是 async/await, 由于 node 在很早的时候(node 8 LTS)就已经支持 async/await, 如今写后端项目没理由不用 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 秒左右,事实上测试2等同于如下代码:
const test2 = async () => {
// Array.from({length: 3}).forEach(async () => {
// await sleep(1000);
// });
Array.from({length: 3}).forEach(() => {
sleep(1000);
});
}
复制代码