Home Converting Medium Posts to Markdown
Post
Cancel

Converting Medium Posts to Markdown

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 結果

有任何問題及指教歡迎 與我聯絡

===

本文首次發表於 Medium ➡️ 前往查看


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

Design Patterns 的實戰應用紀錄

自行實現 iOS NSAttributedString HTML Render