WPS官方文档一万两千篇文档,四分钟采完
准备开发WPS的JSA宏和WPSJS加载项的skill,先把文档采集下,之前采集过的文档过时了,结果还不错,尝试用了下WPS灵犀来搞,全程AI帮我做好了,包括以下的文章帮我总结好过程分享。
一万两篇文档,四分钟采完
—— WPS 开放平台文档逆向采集实战手记
引子:一个「简单」的需求
最近在研究 WPS 的 JSA(JavaScript API)开发,需要频繁查阅 WPS 开放平台的官方文档。文档地址是 open.wps.cn,内容很全——加载项 API、宏编辑器 API、表格 API、文字 API、演示 API……应有尽有。
但问题来了:
每次查资料都要联网,网络不稳定时就抓瞎
文档没有离线版本,想本地全文搜索 impossible
侧边栏导航层层嵌套,找一个具体方法要点好几下
想批量分析 API 命名规律、参数模式,只能手动一页页复制
于是我萌生了一个想法:把整棵文档树的页面全部采集下来,存成本地文件,想怎么搜就怎么搜。
听起来不难,对吧?但实际操作中,我经历了一段从「笨办法」到「聪明办法」的完整进化。今天就把这个过程分享出来,希望能帮到有类似需求的朋友。
一、笨办法:浏览器自动化采集 HTML
最直觉的方案是模拟浏览器操作:打开网页,展开侧边栏导航树,逐个点击叶子节点,提取页面正文内容保存。我用的工具是 CDP(Chrome DevTools Protocol)浏览器自动化。
1.1 打开页面,分析结构
首先导航到文档首页,用 JavaScript 提取侧边栏 DOM 结构。很快发现这是一个 Vue SPA(单页应用),侧边栏导航是动态渲染的。
1.2 展开 12000+ 个折叠节点
真正的噩梦开始了。侧边栏是一棵多级树,默认只展开第一层。每个父节点都需要点击才能展开子节点,而子节点展开后又会冒出新的折叠节点。
我写了个循环:每轮找到所有折叠的三角形图标(CSS class 包含 -rotate-90),全部点击展开,等 0.5 秒渲染完,再找下一批。
执行过程大概是这样的:
轮次 | 点击展开数 | 已发现链接数 |
第 1 轮 | 12 个 | 2 个 |
第 50 轮 | 20 个 | 316 个 |
第 200 轮 | 20 个 | 1,267 个 |
第 400 轮 | 36 个 | 2,284 个 |
…… | …… | …… |
问题在于:每次点击后 Vue 会异步渲染,新展开的节点需要时间才能出现在 DOM 中。而且展开过的节点有时会自动折叠回去(可能是 Vue 的响应式更新导致的状态丢失)。
我跑了 200 多轮循环,花了十几分钟,才展开到 2000 多个链接。而根据估算,整棵树有超过 12000 个叶子节点。照这个速度,光展开导航树就要一个小时。
1.3 提取到的链接也不完整
更让人崩溃的是,即使节点展开了,由于 Vue 的虚拟 DOM 机制,折叠状态下的子节点链接根本不在 DOM 中——它们是懒加载的,只有展开时才会创建。
这意味着:不把所有节点展开到叶子层级,就拿不到完整的链接列表。而展开所有节点又极其耗时且不稳定。
到了这一步,我意识到:浏览器自动化方案行不通。 不是为了采集内容,光是把导航树完整展开就要花费不可接受的时间。
二、转折:一条 Network 请求改变了一切
正当我准备放弃的时候,灵光一闪:既然页面是通过 AJAX 请求加载数据的,那为什么不直接看它请求了什么?
我用 Performance API 列出了页面加载时的所有网络请求,过滤掉 JS、CSS、图片等静态资源后,发现了两个关键的 JSON API 请求:
请求 URL | 作用 |
/docs/api/collections/client?lang=zh | 返回完整侧边栏导航树 |
/docs/api/doc/{doc_id}?lang=zh&source=local | 返回单个页面的 Markdown 原文 |
用浏览器直接访问第一个 API,返回了一个 8MB 的 JSON。里面就是整棵导航树——所有节点、所有层级、所有链接,一次性全部返回。
那一刻,我的心情可以用四个字形容:豁然开朗。
2.1 导航树 API
请求地址:
GET https://open.wps.cn/docs/api/collections/client?lang=zh
返回的是一个嵌套的 JSON 树结构,每个节点包含:
name:节点显示名称(如"表格 API 参考")
type:节点类型(folder 或 file)
path:页面路径
children:子节点数组(folder 类型才有)
docInfo:文档元信息(file 类型才有),其中 id 字段是内容 API 的关键参数
整棵树长这样(简化版):
{
"name": "WPS Office 基础接口",
"type": "folder",
"children": [
{
"name": "表格 API 参考",
"type": "folder",
"children": [
{
"name": "Workbook 对象",
"type": "file",
"docInfo": {
"id": "app-integration-dev_..._Workbook_obj",
"breadcrumb": ["WPS客户端二次开发", "WPS Office 基础接口", "表格 API 参考", "Workbook 对象"]
}
}
]
}
]
}
2.2 内容 API
拿到每个叶子节点的 docInfo.id 后,就可以请求内容了:
GET https://open.wps.cn/docs/api/doc/{doc_id}?lang=zh&source=local
返回内容更让人惊喜:data.content 字段直接就是 Markdown 原文!不需要解析 HTML,不需要正则提取正文,拿到的就是干净的 Markdown。
这意味着:我最初计划先采集 HTML 再转换格式的方案完全是多此一举。API 返回的就是最原始的 Markdown,直接保存即可。
三、秒杀:从一小时到四分钟
有了两个 API,采集逻辑变得极其简洁:
1. 调用导航树 API,递归遍历 JSON 树,提取所有 type=file 的叶子节点
2. 对每个叶子节点的 docInfo.id,调用内容 API 获取 Markdown
3. 按导航树层级创建文件夹,保存 .md 文件
3.1 多线程并发下载
单线程逐个请求太慢(12000 个页面,每个请求约 0.2 秒,串行需要 40 分钟)。用 Python 的 ThreadPoolExecutor 开 20 个线程并发请求,整体耗时降到 4 分钟左右。
核心代码逻辑(简化版):
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=20) as executor:
futures = {executor.submit(fetch_and_save, doc_id): doc_id
for doc_id in all_doc_ids}
for future in as_completed(futures):
doc_id, saved, errors = future.result()
total_success += saved
3.2 文件命名与目录结构
为了让采集结果方便检索,我按导航树的第二级节点创建子文件夹,文件名包含完整树路径。
例如面包屑为 ["WPS客户端二次开发", "WPS Office 基础接口", "加载项 API 参考", "自定义功能区", "自定义功能区概述"] 的页面,保存为:
WPS Office 基础接口/加载项 API 参考/加载项 API 参考_自定义功能区_自定义功能区概述.md
这样想找表格相关的内容,直接进"表格 API 参考"文件夹即可。
3.3 Windows 文件名大小写陷阱
采集过程中遇到一个小坑:Windows 文件系统不区分大小写,而 WPS 文档中存在大小写不同的同名节点(如 ffi 和 FFI)。两个文件名在 Linux 上是不同的文件,在 Windows 上会冲突覆盖。
解决方案很简单:检测到文件名(转小写后)重复时,自动在文件名后加序号后缀。
3.4 最终成果
采集结果汇总:
顶级节点 | 文件数 | 占比 |
WPS Office 集成模式 | 43 | 0.4% |
WPS Office 基础接口 | 12,140 | 99.6% |
合计 | 12,183 | 100% |
"WPS Office 基础接口"下的各子模块分布:
子模块 | 文件数 | 占比 |
文字 API 参考 | 4,267 | 35.2% |
表格 API 参考 | 4,095 | 33.8% |
演示 API 参考 | 1,761 | 14.5% |
通用 API 参考 | 861 | 7.1% |
宏编辑器控件 API 参考 | 397 | 3.3% |
PDF API 参考 | 275 | 2.3% |
加载项 API 参考 | 267 | 2.2% |
宏编辑器 API 参考 | 144 | 1.2% |
WPS 扩展 API | 69 | 0.6% |
其他 | 104 | 0.9% |
全部 12,183 个文件,总大小约 70MB,采集耗时 216 秒,0 错误。
四、原理:为什么 API 比浏览器自动化快 100 倍
4.1 浏览器自动化的瓶颈
浏览器自动化(Selenium、Puppeteer、CDP 等)的本质是模拟人类操作:点击、等待渲染、读取 DOM。每一次操作都有固定开销:
渲染延迟:Vue/React 等框架收到状态变更后,需要异步执行虚拟 DOM diff 和 patch,通常需要 100-300ms
网络往返:每次导航到新页面都要重新加载 HTML、CSS、JS,执行框架初始化
状态管理:SPA 的导航状态(展开/折叠)保存在内存中,操作过快会导致状态丢失或回滚
DOM 查询:在复杂页面中通过 CSS 选择器定位元素也有性能成本
对于几十个页面来说,这些开销可以忽略。但当页面数量达到上万级别时,每个页面多花 1 秒,总量就是好几个小时。
4.2 API 直调的优势
API 直调绕过了所有中间环节:
导航树 API 一次返回全部 12000+ 个节点的完整结构,不需要逐层展开
内容 API 直接返回 Markdown 原文,不需要渲染 HTML 再解析提取
HTTP 请求可以轻松并发(20 线程),而浏览器自动化受限于单线程操作序列
没有渲染开销,没有状态管理问题,纯粹的请求-响应模式
打个比方:浏览器自动化像是从北京坐绿皮火车去广州,每站都停;API 直调像是坐直达高铁。目的地一样,但效率天差地别。
4.3 如何发现 API
很多人觉得「逆向 API」听起来很高深,其实方法非常朴素:打开浏览器 DevTools,看 Network 面板。
具体步骤:
1. 用 Chrome 打开目标页面,按 F12 打开 DevTools
2. 切换到 Network 面板,勾选 Fetch/XHR 过滤器
3. 刷新页面,观察加载时的请求列表
4. 在侧边栏点击任意导航项,观察新触发的请求
5. 寻找返回 JSON 且包含文档内容的请求
6. 用 Python requests 直接调用该 API 验证
90% 的现代 Web 应用都有类似的 JSON API。学会这个方法,采集效率可以提升一到两个数量级。


Lv.1新人创作者