▒ 目录 ▒
🛫 导读
需求
showdoc是一个API文档、技术文档工具网站,经常能搜到一些很好的文档,但是由于没有权限,往往无法将这些文档下载和修改。(前几年该网站还经常出现崩溃问题,使得文档无法正常查看,让人头痛不已)。
今天我们就通过技术手段,将这些文件下载到本地,方便执行其他操作。
1️⃣ 格式分析
官方下载文件内容
showdoc本身支持下载功能,首先,我们自己生成一个项目,然后查看下官网提供的格式。
从图中,我们可以看出,文件分为三类
prefix_info.json
- 包含了项目的所有内容(文档及目录结构、文档基本信息、文档内容)。
- prefix_readme.md
- 从该文件描述看,它只是保存了文件标题和下载文件名的对应关系。
- prefix_***.md
- 这些文件是每个文档中的内容。
工具apifox可以导入showdoc,就是用到了
prefix_info.json
文件。所以我们继续分析该文件结构
prefix_info.json文件格式
通过工具我们将
prefix_info.json
格式化,可以看到,除了一些基本信息外,核心数据都保存在pages字段(与内部的pages含义不一样)。
内部的pages就是一个数组,包含了文章列表,如下图所示
内部的catalogs字段,结构如下,其中
catalogs
就是个无线套娃的树状结构,我们需要做的就是递归处理该逻辑。
2️⃣ 封包分析
打开浏览器调试界面,我们可以看到,showdoc只有两类请求:
/api/page/info
其中
/api/page/info
内容相对简单,变化的只有page_id
,而所有的page_id
都在/api/item/info
中保存着。
至于该接口的返回结果,我们只需要取出page_content
字段即可。
/api/item/info
/api/item/info
请求也相对简单,需要传递item_id
即可。
其请求结果跟上文分析的prefix_info.json
极为相似。
3️⃣ 编码
逻辑相对简单,函数相关含义都加了注释。打开目标页面的控制台,执行函数即可。
// 获取全部菜单
function getMenu() {
return fetch("https://source.showdoc.com.cn/server/index.php?s=/api/item/info", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh,zh-CN;q=0.9,ja;q=0.8,ko;q=0.7,en;q=0.6,zh-TW;q=0.5",
"content-type": "application/x-www-form-urlencoded",
"sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
"referrer": "https://www.showdoc.com.cn/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": `item_id=${my_item_id}&keyword=&default_page_id=${my_default_page_id}&user_token=${my_user_token}&_item_pwd=null`,
"method": "POST",
"mode": "cors",
"credentials": "omit"
}).then(
res => res.json()
).then(j => {
console.log(j)
// window.my_menu = j.menu
return j.data.menu
})
}
// 获取页面的markdown内容
function getPage(page_id) {
return fetch("https://source.showdoc.com.cn/server/index.php?s=/api/page/info", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh,zh-CN;q=0.9,ja;q=0.8,ko;q=0.7,en;q=0.6,zh-TW;q=0.5",
"content-type": "application/x-www-form-urlencoded",
"sec-ch-ua": "\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
"referrer": "https://www.showdoc.com.cn/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": `page_id=${page_id}&user_token=${my_user_token}&_item_pwd=null`,
"method": "POST",
"mode": "cors",
"credentials": "omit"
}).then(
res => res.json()
).then(j => {
console.log(j)
// window.my_menu = j.menu
return j.data.page_content
})
}
let sleep = function (time) {
return new Promise((resolve) => {
setTimeout(resolve, time)
})
}
async function deal_catalogs(catalogs) {
for (let index = 0; index < catalogs.length; index++) {
const element = catalogs[index];
// 处理目录
await deal_catalogs(element.catalogs)
// 处理页
await deal_pages(element.pages)
}
}
async function deal_pages(pages) {
for (var i = pages.length - 1; i >= 0; i--) {
var page = pages[i]
console.log(page.page_id)
var page_content = await getPage(page.page_id)
page.page_content = page_content
// sleep 1秒
await sleep(1*1000)
}
}
async function main() {
// 设置token等
// my_user_token = '请填写token,也是可以自动获取的,执行一个fetch请求即可;或者localstory中的userinfo获取'
my_user_token = JSON.parse(localStorage.getItem('userinfo')).user_token
// 根据url获取item_id、default_page_id
var paths = location.pathname.split('/')
my_item_id = paths[1]
my_default_page_id = paths[2] || ''
my_menu = {}
// 获取所有menu
getMenu()
.then(async menu => {
my_menu = menu
// 处理目录
await deal_catalogs(menu.catalogs)
// 处理页
await deal_pages(menu.pages)
// 打印信息
var d = {
my_item_id: my_item_id,
my_default_page_id: my_default_page_id,
"item_type": "1",
"item_name": "==",
"item_description": "====",
"password": "***",
"pages": my_menu
}
console.log( JSON.stringify(d) )
})
}
await main()
代码特点
- 所有函数都是同步的,使得逻辑相对简单
- 递归调用,每个函数执行明确的逻辑,保证正常退出循环
- 使用浏览器生成的
fetch
函数,不用纠结请求的参数传递问题
问题
部分文档
生成的json无法直接给apifox使用,需要将其中的中文等特殊字符转换为unicode编码,使用了几个网上的工具,都无法完全解决导入问题。
- https://khz.gitee.io/Ctool/tool.html#/tool/unicode 部分page导入失败
- https://c.runoob.com/front-end/3602/ 导入后的文章会包含
%
编码
📖 参考资料
**ps:**文章中内容仅用于技术交流,请勿用于违规违法行为。