背景
最近有一个小需求,就是从网上爬取内容下来,然后在生成对应的markdown文件,本篇主要记录一下这个过程
生成markdown
生成markdown文件过程如下所示

爬取html内容
在nodejs中爬取html文件的方式有多,这里以axios为例
javascript
const url = `https://baike.baidu.com/item/${name}?fromModule=lemma_search-box`
const { data } = axios.get(url) // data就是html
爬取html内容的时候,可能会碰到2个常见的问题
- 接口返回403,这是因为服务端判断了一下请求是否来自浏览器客户端,如果不是则直接禁止访问
- 接口超时,这是因为我们在国内访问国外的资源,网络较慢,或者被墙了导致无法正常访问
解决403的问题,一般在headers里面带上User-Agent即可,如下所示
javascript
// 可以创建一个新实例,所有请求公用
this.request = axios.create({
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
})
// 也可以单个请求的时候传User-Agent
axios.get(movieUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
})
解决超时的问题,一般可以通过设置代理,走我们梯子的本地代理服务来访问国外站点
javascript
const { HttpsProxyAgent } = require("https-proxy-agent");
const httpsAgent = new HttpsProxyAgent(`http://127.0.0.1:1087`);
axios.get(movieUrl, {
// proxy: { // 这种方式不行,response会返回空字符串
// protocol: 'http',
// host: '127.0.0.1',
// port: 1087,
// },
httpsAgent,
proxy:false,
})
注意:在给axios配置代理的时候,先使用的是axios proxy配置,但是发现请求是可以正常响应,但是response.data为空字符串,所以后面换成了传入httpsAgent,并且将proxy:设置成false这种代理设置方式,这样response.data就能正常获取到内容
解析html内容
拿到html之后,怎么从html里面获取到自己想要的信息,这里可以选择的方式有很多种,这里选择最常用的cheerio,通过cheerio来获取html中我们想要的信息
cheerio使用起来相当简单
- 安装依赖
- load html内容
- 通过选择器命中对应的元素
- 通过cheerio提供的方法,操作元素内容、属性、样式等
安装依赖
javascript
pnpm add cheerio
load html内容
javascript
const $ = cheerio.load(html) //$是 cherrio 规定的
通过选择器命中对应元素
javascript
// 标签选择器
$('c-header-content');
// 属性选择器
$('c-list-doc[data-title="测试"]')
// 类选择器
$('.J-lemma-content')
通过cheerio提供的方法,操作元素内容、属性、样式等
javascript
// 获取文本内容
$('.J-lemma-content').text()
// 获取属性值
$('c-doc-content').attr('data-original-src');
下载图片
因为现在很多网站的图片都设置了防盗链,当我们在本地使用链接图片的时候可能没问题,但是当我们生成的markdown发布到掘金、微信公众号等这样的平台时,就会导致图片无法正常展示,所以我们可能需要将图片先下载下来,然后在进行下一步处理
npm里面提供了很多下载图片的包,而这次我们使用的比较简单,直接使用axios下载图片即可,代码如下所示
javascript
async downloadImage(url, filename) {
// 将 responseType设置成arraybuffer即可
const response = await axios.get(url, { responseType: 'arraybuffer' });
// 然后通过fs.writeFile写入到本地
return fs.writeFile(filename, response.data);
}
try {
await downloadImage(图片url, 本地路径)
} catch(err) {
console.log('图片下载失败', err)
}
上传图片
有时候我们并不想自己写一个服务来返回上一步下载的图片,这时候我们会想到图床
常用的免费图床有
- github
- 微博图床
- 聚合图床 - 免费无限图片上传
- hello图床
- Z4A图床
收费图床有
- 又拍云
- 腾讯云cos
- 阿里云的oss
等更多图床内容可以参考最全的图床集合(国内外,站长必备)
这里我们选择免费的hello图床,hello图床有对应的api文档,比如上传图片调用方式如下所示
javascript
loadImage(filePath) {
const data = new FormData();
const file = fs.createReadStream(filePath)
data.append('file', file)
data.append('permission', 0)
// 返回值就会包括图片的url
return this.request.post(`https://www.helloimg.com/api/v1/upload`, data, {
headers: {
'Content-Type': 'multipart/form-data',
"Authorization": "Bearer xxxxxxxxx",
Accept: 'application/json'
}
})
}
需要注意的是file只能传单个图片,不支持传数组,更多的使用方式可以参考接口文档,还有就是免费的是有调用次数、频率、存储限制的,如果要求高的话,可以考虑其它图床
生成模版
当我们数据都准备好之后,最后就只剩生成内容了,生成内容的方式有很多中,比如直接使用模版字符串生成,如下所示
javascript
fs.writeFileSync(`${articsDir}/${name}.md`, `
标题:xxx
图片:${imgUrl}
`)
如果我们的模版内容比较简单,那么通过模版字符串是完全没问题的,但是当我们的模版内容比较复杂,比如有if、else if、遍历等比较复杂的场景时,这时候通过模版字符串就不太好处理,或者代码会不太美观了,这时候我们可以借助模版引擎,只需要两步就可以帮助我们生成内容
- 第一步:创建对应的模版
- 第二步:传入对应的数据
这里使用nunjucks模版引擎
创建模版
javascript
## 概况
{% for item in actorInfo %}
**{{ item.actor }}**<br/>
{% for it in item.info %}{{ it }}<br/>{{'\n'}}{% endfor %}
简介:{{ item.abstract.trim() }}<br/>
**{{ item.comment }}**<br/>
图片:
{% for img in item.tcImageUrls %}

{% endfor %}
{% else %}
主角信息不存在
{% endfor %}
## 介绍
{% if bkSummary %}
{{bkSummary}}
{% else %}
{{summary}}
{% endif %}
传入数据
javascript
const nunjucks = require('nunjucks')
nunjucks.configure({ autoescape: true });
const content = nunjucks.renderString(fs.readFileSync(this.articTemplatePath, 'utf-8'), {
...info,
});
fs.writeFileSync(`${articsDir}/${name}.md`, content)
总结
整个过程并不复杂,让我觉得最有用的可能还是使用模版引擎nunjucks生成markdown的这个步骤,因为可以根据事先定义的模版来生成最终的产物,那么在一些需要动态创建文件的场景就会非常有用,因为这种模版引擎的方式相比于模版字符串的方式,代码的可读性会高很多