nodejs爬虫实战_精通 Node 爬虫 -03专栏爬虫实战

精通 Node 爬虫 -03- 知乎专栏爬虫实战

2018 年 6 月 6 日 15:35:00

一年多前在慕课网手记里发布了篇 《nodejs 爬虫——知乎专栏》,并没有详细的说明这个爬虫的全部工作原理,只是贴了一份代码。经过了一年,原来的代码已经做了很多优化,并使用了知乎的 api 模块,后来发现 api 模块的作者很久没有维护导致API不可以,所以自己手动维护了知乎专栏的一小部分,不过并没PR(作者issues都不回复了,PR也没人理的)。

使用到的模块

由于知乎专栏现在使用的是API,抓取直接请求API就好,获取到数据,把数据转成markdown格式存在本地,方便以后看。所以使用的模块就三个。

"lodash"

"mkdirp"

"request"

"turndown"

抓取原理

在编写代码前,需要先对整个逻辑有个大概的思路:

使用request请求知乎专栏的数据

使用turndown把html内容转为markdown格式

保存为本地文件

原理是很简单,但是实际操作起来并没有这么容易。在写代码的过程中,就会发现各种各样的问题:

知乎专栏的api每次只能获取每次20条的数据。

turndown处理代码块的标签嵌套和知乎API返回的不一样。

要是保存到一个并不存在的文件夹下,NodeJS的fs模块并不支持自动创建文件夹。

基于这几个问题,我们需要另外写对应的方法来解决问题。

编写对应的方法,输入知乎专栏的文章数量,返回一个包含所有文章URL的数组,遍历这个数组即可得到获取到所有的文章。

使用正则把代码块的标签嵌套替换成turndown能识别的格式,方便得到我们想要的MarkDown格式的文档。

文件路径不存在就创建路径。

经过合理的封装,最顶部的代码就5行:

async function zhuanlan(postID, localPath = './') {

console.log(`----- ${postID} start -----`);

mkdir(path.resolve(localPath, postID));

markdown(localPath, postID, await Posts(postID));

};

实际上正真起作用的代码就两行,创建文件夹,将专栏API返回的所有数据转成markdown,并储存。

代码解析

上面的代码可以分成三个大模块,分别是mkdir,Posts和MarkDown。

mkdir 创建文件夹

因为fs的mkdir方法只能创建一层的文件夹,如果想创建更深层的文件夹,还是使用已经封装好的mkdirp模块。

function mkdir(filePath) {

if (fs.existsSync(`${filePath}`)) {

console.log(` ${path.basename(filePath)} 文件夹已经存在`);

} else {

mkdirp(`${filePath}`, (err) => {

if (err) {

console.error(err);

} else {

console.log(`? 创建 ${path.basename(filePath)}文件夹成功`);

}

});

}

}

文件夹是存放markdown文档的基础,只有先创建好文件夹,才能保证爬虫抓取的数据有用处。要不然爬了也是白爬取。

Posts 抓取逻辑

为什么这个方法叫Posts,因为知乎的专栏文章的API就使用这个为路径。

在这个方法里,实现了将所有的文章API的URL处理成数组,并循环抓取所有的文章数据。

获取专栏的信息,专栏信息中有专栏文章的数量值,需要先获取到这个信息才能知道循环请求何时停止。使用lodash的模板方法,得到url,因为返回的数据是压缩过的,需要request解压,所以要设置gzip: true。

const zhuanlanInfo = async (columnsID) => {

const urlTemplate = _.template(API.post.columns)({ columnsID });

let object = {};

object = {

url: urlTemplate,

gzip: true,

};

return JSON.parse((await request(object)).body);

}

将文章数量转换为循环次数。每次访问20条数据,减少请求的数量,节省时间的同时,还能减少对知乎服务器的负担,没毛病。(这里没写延时函数,如果持续抓取,建议加个延时函数)。

let rateMethod = (count, cycle) => {

count = count === undefined ? 20 : count;

cycle = cycleMethod(cycle);

let posts = count % cycle;

let times = (count - posts) / cycle;

return {

times,

count,

cycle,

writeTimes: 0,

allObject: {}

}

}

let cycleMethod = (cycle) => {

let defaultCycle = 20;

if (cycle && cycle !== defaultCycle) {

cycle = cycle % defaultCycle;

}

cycle = cycle || defaultCycle;

return cycle;

}

这里得到的循环次数是从0开始计算的。

循环函数获取所有文章

let loopMethod = (config, callback) => {

let { urlTemplate, ...options } = config.options;

let opts = {

url: url.resolve(urlTemplate, `?limit=${config.cycle}&offset=${config.writeTimes * 20}`),

...options

}

request(opts).then((c) => {

c = JSON.parse(c.body);

forEach(c, (item, index) => {

config.allObject[index + config.writeTimes * 20] = item;

});

if (config.writeTimes === config.times) {

callback(config.allObject);

} else {

config.writeTimes += 1;

loopMethod(config, callback);

}

});

}

这里使用的是callback,当然也可以写成Promise,谁写成Promise记得给我PR一下哈。

抓取过程到这里就完了。下面是数据处理的工作了。

MarkDown 数据处理与储存

获取到数据,最后需要保存成MarkDown的格式。这个模块处理的事情就是将所有的数据转换成MarkDown语法格式,保存到本地。

这一块的代码还是存在一些bug,在windows系统下,文件命名不能存在特殊字符,有心的朋友可以给我PR修复下问题哦。具体怎么写的可以看看我的代码仓库,这里就不多说了。

食用方法

emmmmmm......自己看README吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值