• 将语雀文档迁移到飞书


    前言

    爬虫课程的文字版内容沉淀在语雀的知识库中,一开始感觉很不错,随着课程一直在卖,很快就超过了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。

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

    1. def save_docs(namespace):
    2.     save_docs_dir = 'docs'
    3.     if not os.path.exists(save_docs_dir):
    4.         os.makedirs(save_docs_dir)
    5.     with open('docs_list.json''r', encoding='utf-8') as f:
    6.         docs_list = f.read()
    7.     docs_list = json.loads(docs_list).get('data', [])
    8.     for d in docs_list:
    9.         slug = d['slug']
    10.         title = d['title']
    11.         url = urljoin(root_url, f'repos/{namespace}/docs/{slug}')
    12.         resp = requests.get(url, headers=headers, params={'raw'1})
    13.         result = resp.json()
    14.         markdown_content = result.get('data', {}).get('body''')
    15.          # 正则去除语雀导出的标签
    16.         title = title.replace('/'',')
    17.         with open(os.path.join(save_docs_dir, f'{title}.md'), 'w', encoding='utf-8') as f:
    18.             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代码。

    1. // 展示markdown文档类别的父div
    2. 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")
    3. // 监控配置,属性、子节点、字符数据、子树的变化都监控上
    4. DocumentObserverConfig = {
    5.   attributes: true
    6.   childList: true
    7.   characterData: true,
    8.   subtree: true
    9. };
    10. hrefs = []
    11. DocumentObserver = new MutationObserver(function() {
    12.   // 每次div变动,都将其下a节点的href属性记录起来
    13.   var items = mydocs.getElementsByTagName('a')
    14.   for (let i of items ){
    15.     hrefs.push(i.href)
    16.   }
    17. });
    18. DocumentObserver.observe(mydocs, DocumentObserverConfig)

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

    1. function unique (arr) {
    2.     return Array.from(new Set(arr))
    3. }
    4. 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

    我是二两,下篇文章见。

  • 相关阅读:
    王道数据结构编程题 链表
    mysql分表之后怎么平滑上线?
    解析后人类时代类人机器人的优越性
    Django day08
    好好回答下 TCP 和 UDP 的区别
    SQLite 入门教程
    AlphaGo & Model-Based RL
    【Kubernetes】资源指标管道——部署Metrics Server
    pyyaml操作yaml配置文件基于python
    Datax安装
  • 原文地址:https://blog.csdn.net/weixin_30230009/article/details/127130975