项目背景
在日常的文档管理中,我们经常会使用语雀等在线文档平台来协作编写和管理文档。但有时候我们需要将这些文档导出到本地,进行备份或者进一步处理。虽然语雀提供了 API 接口,但手动调用和处理还是比较繁琐的。
于是我开发了这个简单而实用的工具,可以自动从语雀 API 获取文档树形结构,批量下载文档内容和图片,并保存为本地的 Markdown 文件。
技术栈
这个项目使用了以下技术:
- Node.js: JavaScript 运行环境
- ES6+ 模块化 : 使用
import/export语法 - Axios: 强大的 HTTP 客户端,用于请求语雀 API
- 文件系统操作 : Node.js 原生的
fs模块 - 路径处理 : Node.js 原生的
path模块
核心功能
1. 获取文档树形结构
首先通过语雀的 TOC API 获取知识库的完整文档树:
javascript
const TreeData = axios.get(
"https://www.yuque.com/api/v2/repos/namespace/toc",
// www可替换公司内语雀域名
// namespace 替换为语雀知识库的命名空间
{
headers: {
"Content-Type": "application/json",
"X-Auth-Token": "your-token-here",
},
},
);
这个 API 会返回知识库中所有文档和目录的层级结构,包括:
- 文档类型(DOC/TITLE)
- 文档标题
- 文档 slug(用于访问具体文档)
- 层级关系(父子、兄弟节点)
2. 图片下载和本地化
文档中的图片通常存储在远程服务器上,为了让 Markdown 文件可以离线使用,需要把图片下载到本地:
javascript
async function downloadImage(imageUrl, imageDirPath, fileName) {
try {
const response = await axios.get(imageUrl, {
responseType: "arraybuffer",
timeout: 10000,
});
const imagePath = path.join(imageDirPath, fileName);
fs.writeFileSync(imagePath, response.data);
console.log(`🖼️ 图片已下载: ${fileName}`);
return imagePath;
} catch (err) {
console.error(`❌ 图片下载失败: ${imageUrl} - ${err.message}`);
return null;
}
}
关键点:
- 使用
responseType: "arraybuffer"获取二进制图片数据 - 设置 10 秒超时,避免因网络问题导致程序卡死
- 错误处理:下载失败时记录日志但不中断整个流程
3. Markdown 图片路径替换
下载图片后,需要替换 Markdown 中的图片链接:
javascript
async function processImages(content, imageDirPath) {
// 确保 image 目录存在
if (!fs.existsSync(imageDirPath)) {
fs.mkdirSync(imageDirPath, { recursive: true });
}
let updatedContent = content;
let imageIndex = 0;
// 匹配 markdown 图片格式: 
const markdownImageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
const matches = [...content.matchAll(markdownImageRegex)];
for (const match of matches) {
const imageUrl = match[2];
const alt = match[1];
// 跳过本地路径
if (imageUrl.startsWith("file://") || imageUrl.startsWith("/")) {
continue;
}
// 生成本地图片名称
const urlParts = imageUrl.split("?")[0]; // 去掉查询参数
const ext = urlParts.split(".").pop() || "png";
const fileName = `image_${imageIndex++}.${ext}`;
// 下载图片
const imagePath = await downloadImage(imageUrl, imageDirPath, fileName);
if (imagePath) {
// 替换为相对路径
const absolutePath = `/mkdir/image/${fileName}`;
updatedContent = updatedContent.replace(
``,
``,
);
}
}
return updatedContent;
}
这个函数的亮点:
- 使用正则表达式
matchAll找出所有图片链接 - 跳过已经是本地路径的图片
- 自动提取图片扩展名
- 按顺序命名图片文件(image_0.png, image_1.jpg...)
- 替换原始 URL 为本地路径
4. 批量处理文档
主流程中遍历所有文档项,批量下载和保存:
javascript
TreeData.then(async (res) => {
const data = res.data.data;
// 用于保存所有项的分层信息
const structureInfo = [];
// 遍历所有项
for (const item of data) {
// 保存分层信息到数组
structureInfo.push({
uuid: item.uuid,
type: item.type,
title: item.title,
slug: item.slug,
id: item.id,
level: item.level,
depth: item.depth,
parent_uuid: item.parent_uuid,
child_uuid: item.child_uuid,
sibling_uuid: item.sibling_uuid,
});
// 如果是文档类型,获取内容并保存
if (item.type === "DOC") {
try {
const docRes = await axios.get(
`https://easedata.yuque.com/api/v2/repos/wt15ay/wwuyoq/docs/${item.slug}?raw=1`,
{
headers: {
"X-Auth-Token": "your-token-here",
},
},
);
let content = docRes.data.data?.body || "";
// 处理图片下载和替换
content = await processImages(content, imageDir);
const filePath = path.join(mdDir, `${item.slug}.md`);
fs.writeFileSync(filePath, content, "utf-8");
console.log(`📄 创建文件: ${filePath}`);
} catch (err) {
console.error(`❌ 获取文档失败 ${item.title}:`, err.message);
}
} else if (item.type === "TITLE") {
console.log(`📁 目录项: ${item.title}`);
}
}
// 保存分层结构到 JSON 文件
const structureFilePath = path.join(mdDir, "structure.json");
fs.writeFileSync(
structureFilePath,
JSON.stringify(structureInfo, null, 2),
"utf-8",
);
console.log(`\n📋 分层结构已保存: ${structureFilePath}`);
console.log("✅ 所有文档已生成完毕!");
}).catch((err) => {
console.error("❌ 获取树形结构失败:", err.message);
});
处理逻辑:
- 收集所有文档的结构信息
- 针对 DOC 类型的文档,获取完整内容
- 使用
?raw=1参数获取 Markdown 原始内容 - 处理图片下载和路径替换
- 保存为
.md文件 - 最后导出完整的结构信息为 JSON 文件
项目结构
umi-APi/
├── index.js # 主程序入口
├── package.json # 项目配置文件
├── pnpm-lock.yaml # 依赖锁定文件
├── node_modules/ # 依赖包
└── mkdir/ # 导出的文档目录
├── image/ # 下载的图片
├── *.md # 导出的 Markdown 文件
└── structure.json # 文档结构信息
使用方法
1. 安装依赖
bash
pnpm install
2. 配置 Token
在代码中替换你的语雀 API Token:
javascript
headers: {
"X-Auth-Token": "your-yuque-token-here",
}
3. 运行程序
bash
node index.js
运行后,程序会:
- 创建
mkdir和mkdir/image目录 - 下载所有文档到
mkdir目录 - 下载所有图片到
mkdir/image目录 - 生成
structure.json文件记录文档结构
技术亮点
1. ES6 模块化
项目使用了 ES6 的 import/export 语法,在 package.json 中设置了 "type": "module",让 Node.js 支持原生 ES 模块。
2. 异步处理
大量使用 async/await 来处理异步操作,代码清晰易读,避免了回调地狱。
3. 正则表达式处理
使用正则表达式和 matchAll 方法,精准匹配和替换 Markdown 中的图片链接。
4. 错误处理
在关键的网络请求处理中都加入了 try-catch,确保单个文档或图片失败不会影响整体流程。
5. 文件系统操作
灵活使用 fs 模块的同步和异步方法,合理处理目录创建、文件读写等操作。
可能的改进方向
- 配置文件化: 将 Token、知识库 ID 等配置项提取到配置文件中
- 命令行参数: 支持通过命令行参数指定知识库 ID 和输出目录
- 并发控制: 对于大量文档和图片的下载,可以添加并发控制,提高效率
- 增量更新: 支持检测已下载的文档,只更新有变化的部分
- 进度显示: 添加进度条,让用户了解下载进度
- 目录结构还原: 根据文档的层级关系创建对应的文件夹结构
- 重试机制: 对于失败的请求添加自动重试
总结
这个工具虽然代码量不多,但实现了一个完整的文档导出功能。通过学习这个项目,你可以掌握:
- 如何使用 Node.js 进行文件系统操作
- 如何调用第三方 API
- 如何处理异步流程
- 如何进行字符串正则匹配和替换
- 如何下载和处理二进制文件
希望这个小工具能对你的文档管理工作有所帮助!如果你有任何改进建议,欢迎交流讨论。
参考资料
本文档由作者原创,转载请注明出处。