说在前面
📖之前自己搞了个个人博客网站,但文章主要还是发布在掘金平台,没有同步发布到自己的个人博客网站上,现在就想着同步一下文章,所以首先我要先将自己在掘金的文章都导出到本地,导出后的文章可以备份到git上,也是后续同步到数据库的准备工作🧑💻
1、获取个人文章列表
(1)进入个人主页文章列表
直接打开个人主页的文章列表,比如我的主页:juejin.cn/user/440244…
(2)查看列表接口
在个人主页文章列表页面打开控制台,我们可以看到下面这个请求:
这个就是获取文章列表的接口
(3)获取所有文章列表数据
知道了文章列表的接口之后我们就可以编写代码来获取所有的文章数据了。
分析一下请求参数,我们不难看出翻页数据就是由cursor
参数控制,所以循环修改cursor
参数进行请求即可获取所有的文章列表数据。
返回的响应参数中有一个has_more
属性,这个是判断是否还有下一页,我们可以根据这个来判断是否结束,或者直接判断获取的总数和返回的count
也行。
const run = async () => {
let list = [];
const params = {
user_id: "440244290727294",
sort_type: 2,
cursor: "0",
};
let has_more = true;
while (has_more) {
const res = await queryList(params);
params.cursor = parseInt(params.cursor) + 10 + "";
has_more = res.has_more;
res.data && list.push(...res.data);
console.log(`正在导出${params.cursor}/${res.count}`);
await sleep(2000);
}
fs.writeFileSync("list.json", JSON.stringify(list, null, 2));
console.log("已全部导出");
};
这里我们每次请求后都调用一下sleep(2000)
,等待2秒钟再进行下一次调用,避免请求太过频繁被拦截。
睡眠函数具体实现如下:
async function sleep(times = 1000) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, times);
});
}
获取文章列表具体实现如下:
const queryList = async (params) => {
return new Promise((resolve) => {
axios
.post(
"https://api.juejin.cn/content_api/v1/article/query_list?aid=2608&uuid=7248590178503558696&spider=0",
params
)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
console.error(error);
});
});
};
这样我们就成功地将个人所有的文章列表保存到了本地的list.json
文件中了。
2、获取文章内容
前面我们已经获取了文章的列表数据,但是文章的内容并不会再列表数据中返回,我们还需要根据文章id进一步去获取文章详情内容。
(1)分析详情接口
我们进入到详情页面,打开控制台分析一下,比如:juejin.cn/post/743369…
经过一番搜索,发现找不到详情接口,那该怎么办呢?
(2)爬虫爬取文章内容
既然不能直接通过接口来获取,那我们就换个法子,直接通过爬虫来爬取(这里只用于备份自己的文章,不作其他用途,请官方大大谅解🙌
)
首先我们要分析一下文章详情页url的规律,经过简单的比较后,我们不难发现文章详情页面的url其实就是https://juejin.cn/post/${文章id}
,而文章id就在我们前面获取到的文章列表中可以取到。
遍历文章列表,获取文章id
const run = async () => {
const config = require("./config.json");
let index = config.lastIndex || 0;
try {
const list = require("./list.json");
const len = list.length;
for (index; index < len; index++) {
const i = index;
console.log(`正在导出《${list[i].article_info.title}》${i + 1}/${len}……`);
await getDetail(list[i].article_id);
await sleep(5000);
}
console.log("已全部导出");
} catch (err) {
console.error(err);
config.lastIndex = index;
fs.writeFileSync("config.json", JSON.stringify(config, null, 2));
console.log("导出出错终止,30秒后自动重试");
await sleep(30000);
run();
}
};
这里我们虽然每次爬取都会暂停5秒钟,但还是有可能会被拦截,这里我们做个被拦截后自动重试的机制,那就不用一直守在电脑前看着了。
爬取文章详情
const getDetail = async (id) => {
const response = await axios.get(`https://juejin.cn/post/${id}`);
const html = response.data;
fs.writeFileSync(`html.html`, html);
const $ = cheerio.load(html);
const article = $(
"#juejin > div.view-container > main > div > div.main-area.article-area > article"
).html();
const title = $(
"#juejin > div.view-container > main > div > div.main-area.article-area > article > h1"
)
.text()
.replace(/\n|\t| /g, "");
const content = $("#article-root").html();
fs.writeFileSync(`html/${title}.html`, article);
const markdownContent = htmlToMd(content);
fs.writeFileSync(`md/${title}.md`, markdownContent);
console.log(`已导出文章《${title}》`);
};
这里使用cheerio.load(html)将获取到的 HTML 内容加载到cheerio中,以便像在浏览器中一样使用 jQuery 风格的选择器来操作 HTML。
- 首先,选择特定的 HTML 元素来获取文章内容和标题。选择器
"#juejin > div.view-container > main > div > div.main-area.article-area > article"
用于获取文章的主要内容部分,将其存储在article
变量中。选择器"#juejin > div.view-container > main > div > div.main-area.article-area > article > h1"
用于获取文章的标题,然后使用text()
方法获取文本内容,并通过replace(/\n|\t| /g, "")
去除标题中的换行符、制表符和空格。选择器"#article-root"
用于获取另一个包含文章内容的部分,存储在content
变量中。 - 然后,使用
fs.writeFileSync
将文章内容写入到一个以文章标题命名的 HTML 文件中,文件存储在html/目录下。 - 最后通过
htmlToMd
将爬取到内容从HTML
格式转换为Markdown
格式,并将转换后的内容写入到一个以文章标题命名的Markdown
文件中,文件存储在md/目录下。
源码
该脚本源码已经上传到gitee仓库,地址如下: gitee.com/zheng_yongt…
🌟觉得有帮助的可以点个star~
🖊有什么问题或错误可以指出,欢迎pr~
使用
源码下载
git clone https://gitee.com/zheng_yongtao/one-click-export-juejin-article.git
安装依赖
npm install
修改配置文件
将config.json中的user_id修改为需要导出文章的用户id
运行脚本
node index.js
公众号
关注公众号『前端也能这么有趣
』,获取更多有趣内容。
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣
』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。