Post

Medium 文章转 Markdown 工具|免登入自动备份图片与嵌入内容

自动下载 Medium 使用者所有文章并转成 Markdown,支援图片备份、Gist 转码、Twitter 与 YouTube 内容解析,节省手动备份时间,提升内容管理效率。

Medium 文章转 Markdown 工具|免登入自动备份图片与嵌入内容

Click here to view the English version of this article.

點擊這裡查看本文章正體中文版本。

基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。


Converting Medium Posts to Markdown

撰写小工具将 Medium 心血文章备份下来 & 转换成 Markdown 格式

[ZhgChgLi](https://github.com/ZhgChgLi){:target="_blank"} [ZMediumToMarkdown](https://github.com/ZhgChgLi/ZMediumToMarkdown){:target="_blank"}

ZhgChgLi / ZMediumToMarkdown

[EN] ZMediumToMarkdown

I’ve written a project to let you download Medium post and convert it to markdown format easily.

Features

  • Support download post and convert to markdown format

  • Support download all posts and convert to markdown format from any user without login access.

  • Support download paid content

  • Support download all of post’s images to local and convert to local path

  • Support parse Twitter tweet content to blockquote

  • Support download paid content

  • Support command line interface

  • Convert Gist source code to markdown code block

  • Convert youtube link which embed in post to preview image

  • Adjust post’s last modification date from Medium to the local downloaded markdown file

  • Auto skip when post has been downloaded and last modification date from Medium doesn’t changed (convenient for auto-sync or auto-backup service, to save server’s bandwidth and execution time)

  • Support using Github Action as auto sync/backup service

  • Highly optimized markdown format for Medium

  • Native Markdown Style Render Engine (Feel free to contribute if you any optimize idea! MarkupStyleRender.rb )

  • jekyll & social share (og: tag) friendly

  • 100% Ruby @ RubyGem

[CH] ZMediumToMarkdown

可针对 Medium 文章连结、Medium 使用者的所有文章,爬取其内容并转换成 Markdwon 格式连同文章内图片一同下载下来的备份小工具。

[2022/07/18 Update]: 手把手教你无痛转移 Medium 到自架网站

特色功能

  • 免登入、免特殊权限

  • 支援单篇文章、使用者所有文章下载并转换成 Markdown

  • 支援下载备份文章内的所有图片并转换成对应图片路径

  • 支援深度解析内嵌于文章中的 Gist 并转换成相对语言的 Markdown Code Block

  • 支援解析 Twitter 内容并转贴到文章中

  • 支援解析内嵌于文章中的 Youtube 影片,将转换成影片预览图及连结显示于 Markdown

  • 使用者所有文章下载时会去扫描文章内有无嵌入关联文章,有的话会将连结替换为本地

  • 针对 Medium 格式样式特别优化

  • 自动将下载下来文章的最后修改/建立时间,更改为同 Medium 文章发布时间

  • 自动比对下载下来的文章最后修改,如果没有小于 Medium 文章最后修改时间时则自动跳过 (方便大家使用此工具建立自动 Sync/Backup 工具,此机制能节省 server 流量/时间)

  • CLI 操作,支援自动化

本项目及本篇文章仅供技术研究,请勿用于任何商业用途,请勿用于非法用途,如有任何人凭此做何非法事情,均于作者无关,特此声明。

请确认您有文章使用、著作权再行下载备份。

起源

经营 Medium 第三年,已累积发表超过 65 篇文章;所有文章都是我直接使用 Medium 平台撰写,没有其他备份;老实说一直很怕 Medium 平台有状况或是其他因素导致这几年的心血结晶消失。

之前曾经手动备份过,非常无聊且浪费时间,所以一直在找寻一个可以自动把所有文章备份下载下来的工具、最好还能转换成 Markdown 格式。

备份需求

  • Markdown 格式

  • 依照 User 能自动下载该 User 的所有 Medium Posts

  • 文章图片也要能被下载备份下来

  • 要能 Parse Gist 成 Markdown Code Block (我的 Medium 大量使用 gist 嵌入 Source Code 所以这个功能很重要)

备份方案

Medium 官方

官方虽然有提供汇出备份功能,但汇出格式仅能用于汇入 Medum、非 Markdown 或共通格式,而且不会处理 Github Gitst …等等 Embed 的内容。

Medium 提供的 API 没什么在维护且只提供 Create Post 功能。

合理,因为 Medium 官方不希望使用者能轻易地将内容转移至其他平台。

Chrome Extension

有找到试用了几个 Chrome Extension (几乎都被下架了),效果不好,一是要手动一篇文章一篇文章点进去备份、二是 Parse 出来的格式很多错误而且也无法深度 Parse Gist Source Code 出来、也无法备份文章的所有图片下来。

medium-to-markdown command line

某位大神用 js 写的,能达成基本的下载及转换成 Markdown 功能,但一样没图片备份、深度 Parse Gist Source Code。

ZMediumToMarkdown

苦无完美解决方案后,下定决心自行撰写一个备份转换工具;花费了大约三周的下班时间使用 Ruby 完成。

技术细节

如何透过输入使用者名称得到文章列表?

1.取得 UserID:检视使用者主页(https://medium.com/@# {username} ) 原始码可以找到 Username 对应的 UserID 这边要注意因为 Meidum 重新开放自订网域 所以要多处理 30X 转址

2.嗅探网路请求可以发现 Medium 使用 GraphQL 去取得主页的文章列表资讯

3.复制 Query & 替换 UserID 到请求资讯

1
2
HOST: https://medium.com/_/graphql
METHOD: POST

4.取得 Response

每次只能拿 10 笔,要分页拿取。

  • 文章列表:可以在 result[0]->userResult->homepagePostsConnection->posts 中取得

  • homepagePostsFrom 分页资讯 :可以在 result[0]->userResult->homepagePostsConnection->pagingInfo->next 中取得 将 homepagePostsFrom 带入请求即可进行分页存取, nil 时代表已没有下一页

如何剖析文章内容?

检视文章原始码后可发现,Medium 是使用 Apollo Client 服务进行架设;其端 HTML 实际是从 JS 渲染而来;因此可以再检视原始码中的 <script> 区段找到 window.__APOLLO_STATE__ 字段,内容就是整篇文章的段落架构,Medium 会把你整篇文章拆成一句一句的段落,再透过 JS 引擎渲染回 HTML。

我们要做的事也一样,解析这个 JSON,比对 Type 在 Markdown 的样式,组合出 Markdown 格式。

技术难点

这边有一个技术困难点就是在渲染段落文字样式时,Medium 给的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"Paragraph": {
    "text": "code in text, and link in text, and ZhgChgLi, and bold, and I, only i",
    "markups": [
      {
        "type": "CODE",
        "start": 5,
        "end": 7
      },
      {
        "start": 18,
        "end": 22,
        "href": "http://zhgchg.li",
        "type": "LINK"
      },
      {
        "type": "STRONG",
        "start": 50,
        "end": 63
      },
      {
        "type": "EM",
        "start": 55,
        "end": 69
      }
    ]
}

意思是 code in text, and link in text, and ZhgChgLi, and bold, and I, only i 这段文字的:

1
2
3
4
- 第 5 到第 7 字元要标示为 程式码 (用`Text`格式包装)
- 第 18 到第 22 字元要标示为 连结 (用[Text](URL)格式包装)
- 第 50 到第 63 字元要标示为 粗体(用*Text*格式包装)
- 第 55 到第 69 字元要标示为 斜体(用_Text_格式包装)

第 5 到 7 & 18 到 22 在这个例子里好处理,因为没有交错到;但 50–63 & 55–69 会有交错问题,Markdown 无法用以下交错方式表示:

1
code `in` text, and [ink](http://zhgchg.li) in text, and ZhgChgLi, and **bold,_ and I, **only i_

正确的组合结果如下:

1
code `in` text, and [ink](http://zhgchg.li) in text, and ZhgChgLi, and **bold,_ and I, _**_only i_

50–55 STRONG 55–63 STRONG, EM 63–69 EM

另外要需注意:

  • 包装格式的字串头跟尾要能区别,Strong 只是刚好头跟尾都是 ** ,如果是 Link 头会是 [ 尾则是 ](URL)

  • Markdown 符号与字串结合时要注意前后不能有空白,否则会失效

完整问题请看此。

这块研究了好久,目前先使用现成套件解决 reverse_markdown

特别感谢前同事 Nick , Chun-Hsiu Liu ,James 协力研究,之后有时间再自己写改成原生的。

成果

原文 -> 转换后的 Markdown 结果

有任何问题及指教欢迎 与我联络


Buy me a beer

本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。

Improve this page on Github.

This post is licensed under CC BY 4.0 by the author.