WPS官方文档一万两千篇文档,四分钟采完

准备开发WPS的JSA宏和WPSJS加载项的skill,先把文档采集下,之前采集过的文档过时了,结果还不错,尝试用了下WPS灵犀来搞,全程AI帮我做好了,包括以下的文章帮我总结好过程分享。

一万两篇文档,四分钟采完


—— WPS 开放平台文档逆向采集实战手记

从浏览器自动化到 JSON API 逆向,一场「笨办法」到「聪明办法」的进化之旅。

引子:一个「简单」的需求

最近在研究 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 文档中存在大小写不同的同名节点(如 ffiFFI)。两个文件名在 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。学会这个方法,采集效率可以提升一到两个数量级。

WPS二次开发相关
@WPS催化剂-李伟坚
广东省
浏览 711
1
7
分享
7 +1
4
1 +1
全部评论 4
 
Happy凡人
慢慢学
   湖北省
举报
0
0
 
fbfbzz
然后呢
   江苏省
举报
1
0
 
Vicky
Vicky Lv.1 新人创作者

Lv.1新人创作者

   上海
举报
1
0
 
WPS催化剂-李伟坚
文章字数问题未发全,更好的阅读体验可以到公众号里:https://mp.weixin.qq.com/s/7Zmt5Acg0917pSMHqqj7TA
   广东省
举报
1
0