将语雀文档迁移到飞书

2023-11-09

前言

我爬虫课程的文字版内容沉淀在语雀的知识库中,一开始感觉很不错,随着课程一直在卖,很快就超过了200人的限制,我已经是个人版中最高级的会员了,但语雀知识库的协作人数依旧限制在200人...即花钱无法解决问题。

先说一下我的需求,我需要一个可以承载一批文档的地方,做到只给固定的一些用户阅读。

我不希望对每篇文章都进行授权处理,希望有类似共享文件夹的效果,只对文件夹进行权限控制,用户可以访问文件夹,便可以访问其中的内容。

我不希望用户可以修改文档内容,但却希望他们可以留下评论,比如写些疑惑,我对疑惑的答疑也会沉淀在文档中。

语雀知识库是我一开始选择的方案,但问题就是人数的限制,官方似乎也没打算解决,当然,我也没想着解决,再复制多个一个知识库,不就又可以承载200人了。

但,一篇内容要在多个知识库中同步,每次人员到达上限时,又要开新的知识库,最终还是让我失去的耐心,所以我打算进行迁移。

先说,迁移的主观感受:巨难受....

我想着,先用腾讯文档,这样用户不需要注册新的应用(使用语雀,需要注册语雀),简单微信授权便可以直接阅读文档了。

但遇到了一些问题,然后我又尝试了石墨、金山、飞书,最终矮个里挑将军,选择了飞书作为新的承接主体。

因为每款文档应用都不希望用户迁移,所以你会遇到一些麻烦....本文便记录一下我遇到的问题和我的解决方案。

语雀的导出限制

当我们希望导出整个知识库时,会发现,你只能导出图片版PDF和语雀特有的lakebook格式。

77e1d85acca9ac6b5128586a9bb41ead.png什么叫图片版PDF?就是PDF文件中的内容其实是图片,即你无法对里面的内容进行修改,但就算是文字版的PDF,修改起来也很麻烦,这是PDF格式限制导致的。

至于lakebook,是语雀特意搞的格式,是混淆的,美其名曰,保护用户数据,但用脚指头想都知道,语雀限制了导出,让你无法轻松将数据迁移走。

这有点像一些数据类Saas服务,导入功能非常完善,你上手起来非常快,当企业感觉不满足需求,想导出数据时,会发现,基本的导出格式,居然没有...

语雀知识库的导出是搞不了了,我可不想硬刚lakebook,但语雀可以将单篇文章导出成markdown或word。

d3f76d576498d847ac4857fce9c5436f.png

考虑多,我有近120篇文档,要是通过手动导出,人会傻掉,所以决定再研究一下,很快,我发现了语雀提供API操作,其中就有获得文档具体内容的API。

关于语雀API的细节,可以看它的对接文档:https://www.yuque.com/yuque/developer/api

比较简单,就是response中的数据格式跟文档表述的格式出入较大,很多字段,其实我也不知道干啥的,通过语雀提供的【获取单篇文档详细信息】的接口,我们可以获得文档的原始markdown。

我对接完多个接口,然后将数据保存到本地,保存方法如下:

def save_docs(namespace):

    save_docs_dir = 'docs'
    if not os.path.exists(save_docs_dir):
        os.makedirs(save_docs_dir)


    with open('docs_list.json', 'r', encoding='utf-8') as f:
        docs_list = f.read()

    docs_list = json.loads(docs_list).get('data', [])
    for d in docs_list:
        slug = d['slug']
        title = d['title']
        url = urljoin(root_url, f'repos/{namespace}/docs/{slug}')
        resp = requests.get(url, headers=headers, params={'raw': 1})
        result = resp.json()
        markdown_content = result.get('data', {}).get('body', '')
         # 正则去除语雀导出的<a>标签
        markdown_content = re.sub("<a name=\".*\"></a>","", markdown_content) 
        title = title.replace('/', ',')
        with open(os.path.join(save_docs_dir, f'{title}.md'), 'w', encoding='utf-8') as f:
            f.write(markdown_content)

完整代码,翻到文末则可获得。

至此,我将知识库中的所有文档都保留了下来。

db2427c649fd200cbc542c2c05fc5f26.png

这里有个无法避免的坑,那便是语雀API返回的数据中,不会包含任何目录结构的数据,即我们同步到其他平台时,需要我们自己手动再整理一次文档的排列....

腾讯、金山、石墨之痛

导出后,我立刻着手弄腾讯文档,我先查了一下腾讯文档的文档,判断一下,它的权限功能是否满足我的需求,完全满足,他可以做到只让文档被微信群中的用户阅读。

然后我着手导入,然后就发现,腾讯文档不支持markdown的导出,是的,如此基础的功能,腾讯文档没有...

那我将markdown转成docx,再用腾讯文档导入不就好了。

简单一搜,便找到了pandoc(https://github.com/jgm/pandoc, 26.6k star)项目,看到这么多star,比较放心,直接下载来用。

嗯,转的效果不太好,导入到腾讯文档中,效果就更不好了,样式完全丢失...接受不了。

8aa01e99a7b7df34895016962d48abc3.png

不死心的研究了半天,发现,腾讯文档只能在编辑在线文档时,开启markdown语法支持,到导入时,真的不支持markdown,吐了。

既然如此,我就立刻换成金山文档,也是个老牌子,这次,我先看是否支持markdown,嗯,金山也不支持...金山的优点是,其在线文档的使用方式与本地使用word软件编辑文档相似,但不是我需要的。

随后,便是石墨,石墨是支持的markdown导入的,但石墨的分享时,会要求你注册石墨账户,权衡了之下,打算先研究一下飞书先。

飞书文档

飞书文档的权限控制也可以满足我的需求,但需要用户加我的好友才行,相比于石墨,麻烦了一点,但考虑到字节收购了石墨,借鉴石墨来搞飞书文档的这一层,打算还是着重用一下飞书文档。

我在飞书文档上,创建了文件夹,然后将markdown批量导入

2a8d7591c8f4611f910faf37f65eded3.png

一个问题是,在飞书文档中直接看markdown样式是比较丑的,而且语雀中的图片在飞书文档的markdown样式中无法正常显示,如下图:

abe1394ef10092d353dd2da024b8a377.png

我们在飞书文档中进入其中一个markdown,发现飞书支持将他转为在线文档,转为在线文档后,样式就美观很多,而且图片等内容也正常了。

那问题又来了,120多个markdown文档,我一个个点显然不合理,很自然的,我去找飞书的API,在飞书开放平台中,可以找云空间、文档的API。

93da1c2638be75583ba646e0a901040f.png

阅读完这些文档后,发现...没有markdown转在线文档的API,这就...难受了。

简单思索一下,决定用爬虫技术解决一下。

如果写selenium自动化,可能会费点时间,还是直接抓一下我在飞书文档中点击转在线文档时,请求的API吧。

当我点击【转为在线文档】时,飞书发送create请求,相关图片如下:

8c37770e5ed7d33808820927eb2fa652.pngf58aa8fc4fdaf7f632a017ac999a380f.png

经过简单分析,发现file_token参数用于指定具体的文档,怎么拿到file_token便是关键。

基于爬虫课里提供的JS Hook,可以快速定位出file_token是什么时候生成的,里面的值又是哪个方法来,但在当前的需求下,这种方式也显得麻烦。

简单搜索分析,发现file_token就在html中,其中一个例子:

9a2c9ecd9d868a90deeefd6321cd0451.png

最直接的想法便是将网页的html内容全部复制下来,然后正则匹配,获得其中的内容就好了。

但飞书只将25个文档的内容放在html中,当你滚动鼠标中,相关div元素会变成其他的内容。

简单而言,你滚动就刷新,新的file_token会覆盖就的file_token。

你可以滚动几次,每次都手动保存一下html,或者利用selenium自动滚动,实时获取,但更好的方式是利用JavaScript的Mutation事件。

利用Mutation事件,你可以实时监控DOM树中某个节点的变化。我们基于Mutation事件写出如下JS代码。

// 展示markdown文档类别的父div
mydocs = document.querySelector("#mainContainer > div.app-main-container.flex.layout-row.explorer-v3 > div > div.sc-llYToB.bepCke > main > div.sc-ewSSRw.fDTLhF > div.sc-eicnZh.bSQUzR > div.sc-fvxABq.eCsope.explorer-file-list-virtualized__container.explorer-file-list-virtualized__container-a > div:nth-child(1) > div")

// 监控配置,属性、子节点、字符数据、子树的变化都监控上
DocumentObserverConfig = {
  attributes: true, 
  childList: true, 
  characterData: true,
  subtree: true
};

hrefs = []
DocumentObserver = new MutationObserver(function() {
  // 每次div变动,都将其下a节点的href属性记录起来
  var items = mydocs.getElementsByTagName('a')
  for (let i of items ){
    hrefs.push(i.href)
  }
});

DocumentObserver.observe(mydocs, DocumentObserverConfig)

在chrome的console中执行上面代码,滚动几次鼠标,hrefs变量中便塞满了href,其中就包含了我们需要的file_token,再利用JS对hrefs做一次去重。

function unique (arr) {
    return Array.from(new Set(arr))
}

result = unique(hrefs)

将获得的结果保存成json文件,如下图:

ddc935567f0db4d60e850f2a92146a8f.png

然后,利用curl to python的技巧(爬虫课中提过该技巧),复制前面的create请求,修改一下代码逻辑,让file_token从feishu_file_tokens.json中获取,然后批量请求,便可以实现markdown文件批量转成飞书在线文档的效果:

fb78da2e41d776e26a9ba62d7aecaf14.png

然后,就是手动整理目录结构了,这个没啥办法,因为在语雀API中没有结构数据,当然,你可以利用OCR的形式,尝试恢复结构,但比较麻烦,就手动弄弄了。

最终效果如下:

2cef28fb68e1d4115e5af7e39f029b05.png

结尾

虽然解决了,但开心不起来,文档类的工具,当你要迁移时,会发现,都不太好用。

本文相关代码我整理弄到了github上:https://github.com/ayuLiao/export-yuque

我是二两,下篇文章见。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将语雀文档迁移到飞书 的相关文章

  • AWS 无法从 START_OBJECT 中反序列化 java.lang.String 实例

    我创建了一个 Lambda 函数 我想在 API 网关的帮助下通过 URL 访问它 我已经把一切都设置好了 我还创建了一个application jsonAPI Gateway 中的正文映射模板如下所示 input input params
  • 有没有办法在 onclick 触发时禁用 iPad/iPhone 上的闪烁/闪烁?

    所以我有一个有 onclick 事件的区域 在常规浏览器上单击时 它不会显示任何视觉变化 但在 iPad iPhone 上单击时 它会闪烁 闪烁 有什么办法可以阻止它在 iPad iPhone 上执行此操作吗 这是一个与我正在做的类似的示例
  • 每个 X 具有多个 Y 值的 Python 散点图

    我正在尝试使用 Python 创建一个散点图 其中包含两个 X 类别 cat1 cat2 每个类别都有多个 Y 值 如果每个 X 值的 Y 值的数量相同 我可以使用以下代码使其工作 import numpy as np import mat
  • Eclipse Java 远程调试器通过 VPN 速度极慢

    我有时被迫离开办公室工作 这意味着我需要通过 VPN 进入我的实验室 我注意到在这种情况下使用 Eclipse 进行远程调试速度非常慢 速度慢到调试器需要 5 7 分钟才能连接到远程 jvm 连接后 每次单步执行断点 行可能需要 20 30
  • Firebase 函数 onWrite 未被调用

    我正在尝试使用 Firebase 函数实现一个触发器 该触发器会复制数据库中的一些数据 我想观看所有添加的内容votes user vote 结构为 我尝试的代码是 const functions require firebase func
  • 为字典中的一个键附加多个值[重复]

    这个问题在这里已经有答案了 我是 python 新手 我有每年的年份和值列表 我想要做的是检查字典中是否已存在该年份 如果存在 则将该值附加到特定键的值列表中 例如 我有一个年份列表 并且每年都有一个值 2010 2 2009 4 1989
  • Google App Engine 如何预编译 Java?

    App Engine 对应用程序的 Java 字节码使用 预编译 过程 以增强应用程序在 Java 运行时环境中的性能 预编译代码的功能与原始字节码相同 有没有详细的信息这是做什么的 我在一个中找到了这个谷歌群组消息 http groups
  • 日期出现奇怪的错误,“未捕获非法访问”

    所以我试图找到最新的DateJavascript 可以处理 我把它减少到 9 月 275760 并增加了我开始捕获未捕获的天数illegal access例外new Date 09 24 275760 to new Date 10 13 2
  • Three.js 各种大小的粒子

    我是 Three js 的新手 正在尝试找出添加 1000 个粒子的最佳方法 每个粒子都有不同的大小和颜色 每个粒子的纹理是通过绘制画布创建的 通过使用粒子系统 所有粒子都具有相同的颜色和大小 为每个粒子创建一个粒子系统是非常低效的 有没有
  • 在mockito中使用when进行模拟ContextLoader.getCurrentWebApplicationContext()调用。我该怎么做?

    我试图在使用 mockito 时模拟 ContextLoader getCurrentWebApplicationContext 调用 但它无法模拟 here is my source code Mock org springframewo
  • 如何计算 pandas 数据帧上的连续有序值

    我试图从给定的数据帧中获取连续 0 值的最大计数 其中包含来自 pandas 数据帧的 id date value 列 如下所示 id date value 354 2019 03 01 0 354 2019 03 02 0 354 201
  • 在 JavaScript 循环之外声明变量可以提高速度和内存?

    C 也有类似的问题 但我们没有看到 JavaScript 的任何问题 在循环内声明变量是否可以接受 假设循环有 200 次迭代 使用样本 2 相对于样本 1 是否有性能要求 内存和速度 我们使用 jQuery 来循环 它提高了我们将 var
  • Scrapy:如何使用元在方法之间传递项目

    我是 scrapy 和 python 的新手 我试图将 parse quotes 中的项目 item author 传递给下一个解析方法 parse bio 我尝试了 request meta 和 response meta 方法 如 sc
  • 带参数的事件监听器

    我想将参数传递给 JavaScript 中的事件侦听器 我已经找到了解决方案 但我无法理解它们为什么或如何工作以及为什么其他解决方案不起作用 我有 C C 背景 但是 Javascript 函数的执行有很大不同 您能否帮助我理解以下示例如何
  • 如何用另一个响应替换窗口的 URL 哈希?

    我正在尝试使用替换方法更改哈希 URL document location hash 但它不起作用 function var anchor document location hash this returns me a string va
  • 当我从 Netbeans 创建 Derby 数据库时,它存储在哪里?

    当我从 netbeans 创建 Derby 数据库时 它存储在哪里 如何将它与项目的其余部分合并到一个文件夹中 右键单击Databases gt JavaDB in the Service查看并选择Properties This will
  • 如何修复 JNLP 应用程序中的“缺少代码库、权限和应用程序名称清单属性”?

    随着最近的 Java 更新 许多人都遇到了缺少 Java Web Start 应用程序的问题Codebase Permissions and Application name体现属性 尽管有资源可以帮助您完成此任务 但我找不到任何资源综合的
  • JavaScript 相对路径

    在第一个 html 文件中 我使用了一个变量类别链接 var categoryLinks Career prospects http localhost Landa DirectManagers 511 HelenaChechik Dim0
  • 导入错误:没有名为 site 的模块 - mac

    我已经有这个问题几个月了 每次我想获取一个新的 python 包并使用它时 我都会在终端中收到此错误 ImportError No module named site 我不知道为什么会出现这个错误 实际上 我无法使用任何新软件包 因为每次我
  • 如何使用 Pycharm 安装 tkinter? [复制]

    这个问题在这里已经有答案了 I used sudo apt get install python3 6 tk而且效果很好 如果我在终端中打开 python Tkinter 就可以工作 但我无法将其安装在我的 Pycharm 项目上 pip

随机推荐

  • go的channel实现归并排序

    func main ch1 create int 1 3 6 待排序的管道1 ch2 create int 2 5 9 待排序的管道2 ch merge ch1 ch2 for c range ch fmt Println c for ru
  • java概述,发展历程

    1 java概述 首先对于刚刚接触java的小白来说 可能连java是什么 为什么叫这么名字都不知道 其实 Java语言是有个曾用名的 叫Oak 而且起这个名字的时候也是很随心的 只是因为看到了窗口外的一颗橡树 歌词 只是因为面向窗外多看了
  • python 合并不同文件夹下名称相同的文件

    转载 https blog csdn net qq 42769683 article details 104565285 utm source app app version 4 10 0 code app 1562916241 uLink
  • 3D 人体姿态估计简述[转]

    转自 3D 人体姿态估计简述 知乎 0 前言 3D Human Pose Estimation 以下简称 3D HPE 的目标是在三维空间中估计人体关键点的位置 3D HPE 的应用非常广泛 包括人机交互 运动分析 康复训练等 它也可以为其
  • 使用Python的Cufflinks库创建三维散点图

    使用Python的Cufflinks库创建三维散点图 在数据可视化中 三维散点图是一种常用的图形展示方式 如果您正在寻找一种方便易用的数据可视化工具来创建三维散点图 那么Cufflinks就是一个不错的选择 Cufflinks是一个基于pl
  • 恕我直言,自从用完Gradle后,有点嫌弃Maven了!速度贼快!

    点击上方蓝色字体 选择 标星公众号 优质文章 第一时间送达 99套Java企业级实战项目 4000G架构师资料 作者 乐百川 点击阅读原文前往 授权转载自 toutiao com i6824937779193971207 相信使用Java的
  • Spring 基于ApplicationEvent、ApplicationEventPublisher、ApplicationListener的事件监听、发布记录

    1 概述 事件发布和订阅具体流程 1 具体要发布的事件 事件中携带发送的数据 2 发送事件 3 监听器 监听发布的事件 获取事件的携带数据 执行业务逻辑 发布 事件作为参数 事件 监听 事件作为参数 例如 使用切面记录系统日志 发送相同类型
  • oracle 9i在线重定义,Oracle 9i中表的在线重定义(转)

    今天遇到要把数据库中的某张表改成分区表 而且该表在别的地方还有其他的注册信息 如果自己手工建一个分区表的替代该表的话 那就得要手工地去执行该表在其他地方的注册 所以不想删除该表再手工创建同名的分区表 想到了Oracle 9i中可以使用在线重
  • gitee上传代码方法(命令)

    第一次上传 创建一个gitee仓库 在 我的电脑 里找到你要上传的文件的位置 在地址栏输入cmd 回车 输入git init 输入git add 输入git commit m 备注信息可更改 输入 git remote add origin
  • C语言中的清屏函数(自己编写)

    在csdn论坛里看到这样一个问题 如何在c语言命令提示下清除屏幕 感兴趣 随查之 有解 include
  • 蓝帽杯半决赛2022

    手机取证 1 iPhone手机的iBoot固件版本号 答案参考格式 iBoot 1 1 1 直接通过盘古石取证 打开 取证大师和火眼不知道为什么都无法提取这个 手机取证 2 该手机制作完备份UTC 8的时间 非提取时间 答案参考格式 200
  • 修炼离线:(五)hbase映射表插入hive

    一 创建hive表 sql drop table if exists ods odsyyy create table if not exists ods odsfff row id string comment 行记录唯一ID 对应ROW
  • Makefile入门二、理解$@、$^和$<

    文章目录 一 理解 lt 的含义 二 举例 三 简提Makefile中打印日志信息 前面简单记录了一下Makefile中helloworld的用法 这次来理解一些 lt 的含义 一 理解 lt 的含义 Makefile中 格式为这样的 ta
  • Spring Roo 实站( 一 )部署安装 & 第一个示例程序

    一 安装 注 可以参与官网spring roo static springsource org spring roo reference html intro html intro exploring sampleROO OPTS http
  • 【Linux 驱动篇(二)】LED 驱动开发

    文章目录 一 Linux 下 LED 灯驱动原理 1 地址映射 1 1 ioremap 函数 1 2 iounmap 函数 2 I O 内存访问函数 2 1 读操作函数 2 2 写操作函数 二 实验程序编写 1 LED 灯驱动程序编写 2
  • 电脑不能开热点的一种可以尝试的解决方法

    1 说明 方法不一定万能 个人情况 win10 以前可以开热点 不知何时起不能再开 会显示 我们无法设置移动热点 2 解决办法 1 管理员方式打开cmd 2 运行命令 netsh int ip reset netsh winsock res
  • 【金融系列】【statsmodels】如何用Python做实证研究?介绍一个功能和STATA很像的Python包,最小二乘,虚拟变量

    博主本科接触的研究主要是公司金融方向的研究 在公司金融的实证研究中 我们的终极目标是建立变量间的因果关系 我们需要识别因果关系 来检验理论 评价政策效果 或作出预测 目前该领域的研究大部分是使用了STATA和R这两种工具来开展研究的 其实作
  • 亲测可用:opencv图片序列转视频

    亲测可用 glob函数可以遍历文件夹下文件 完毕后可在项目目录下生成output avi视频 可以稍作改进 转换的时候显示当前转换图像 include
  • 网络安全专业毕业设计最新最全选题精华汇总-持续更新中

    前言 大家好 这里是海浪学长毕设专题 大四是整个大学期间最忙碌的时光 一边要忙着准备考研 考公 考教资或者实习为毕业后面临的升学就业做准备 一边要为毕业设计耗费大量精力 学长给大家整理了网络安全专业最新精选选题 如遇选题困难或选题有任何疑问
  • 将语雀文档迁移到飞书

    前言 我爬虫课程的文字版内容沉淀在语雀的知识库中 一开始感觉很不错 随着课程一直在卖 很快就超过了200人的限制 我已经是个人版中最高级的会员了 但语雀知识库的协作人数依旧限制在200人 即花钱无法解决问题 先说一下我的需求 我需要一个可以