Mediumの投稿をMarkdownに変換する方法
Medium の大切な記事をバックアップして Markdown 形式に変換する小ツールを作成する

[EN] ZMediumToMarkdown
Mediumの投稿を簡単にダウンロードしてMarkdown形式に変換できるプロジェクトを書きました。
特徴
-
投稿のダウンロードとMarkdown形式への変換をサポート
-
ログイン不要で、任意のユーザーのすべての投稿をダウンロードし、Markdown形式に変換することをサポートします。
-
有料コンテンツのダウンロードをサポート
-
投稿のすべての画像をローカルにダウンロードし、ローカルパスに変換をサポートする
-
Twitterのツイート内容をblockquoteに解析して対応するサポート
-
有料コンテンツのダウンロードをサポート
-
コマンドラインインターフェースをサポートしています
-
Gist のソースコードを Markdown のコードブロックに変換する
-
投稿に埋め込まれたYouTubeリンクをプレビュー画像に変換する
-
MediumからローカルにダウンロードしたMarkdownファイルへ投稿の最終更新日時を調整する
-
投稿が既にダウンロードされており、Mediumの最終更新日が変わっていない場合は自動スキップ(自動同期や自動バックアップサービスに便利で、サーバーの帯域幅と実行時間を節約)
-
Medium向けに高度に最適化されたMarkdown形式
-
ネイティブMarkdownスタイルレンダーエンジン(最適化のアイデアがあればぜひご協力ください!
MarkupStyleRender.rb) -
jekyll とソーシャルシェア(og: タグ)対応
-
100% Ruby @ RubyGem
[CH] ZMediumToMarkdown
Medium の記事リンクや Medium ユーザーのすべての記事を対象に、その内容をクロールして Markdown 形式に変換し、記事内の画像とともにダウンロードするバックアップツール。
[2022/07/18 更新]: 手取り足取り教える Medium から自分のサイトへの無痛移行
特徴機能
-
ログイン不要、特別な権限不要
-
単一記事およびユーザーのすべての記事のダウンロードとMarkdownへの変換をサポート
-
記事内のすべての画像をダウンロードして対応する画像パスに変換することをサポートしています
-
記事内に埋め込まれた Gist を深く解析し、該当言語の Markdown コードブロックに変換します
-
Twitter のコンテンツを解析して記事に貼り付けることをサポートしています
-
記事内に埋め込まれたYouTube動画を解析し、動画のサムネイルとリンクをMarkdownに表示することをサポートしています
-
ユーザーの全記事をダウンロードする際、記事内に関連記事の埋め込みがあるかをスキャンし、あればリンクをローカルに置き換えます。
-
Medium フォーマットのスタイルに特別に最適化
-
ダウンロードした記事の最終更新日/作成日をMediumの記事公開日に自動で変更する
-
自動でダウンロードした記事の最終更新日時を比較し、Mediumの記事の最終更新日時より古い場合は自動でスキップします
(この機能により、自動同期やバックアップツールの構築が容易になり、サーバーの通信量や時間を節約できます) -
CLI 操作、自動化対応
本プロジェクトおよび本記事は技術研究のみを目的としており、商業利用や違法行為には使用しないでください。もし本内容を使って違法行為が行われた場合、作者は一切関与しません。ここに声明します。
記事の使用権および著作権を確認のうえ、ダウンロードとバックアップを行ってください。
起源
Medium を始めて3年目で、すでに65本以上の記事を投稿しています。すべての記事は私が直接 Medium プラットフォームで執筆しており、他のバックアップはありません。正直なところ、Medium プラットフォームに問題が起きたり、何らかの理由でこれまでの努力が消えてしまうのではないかとずっと不安に感じていました。
以前は手動でバックアップしていましたが、とても退屈で時間がかかりました。そこで、すべての記事を自動でバックアップしてダウンロードできるツール、できればMarkdown形式に変換できるものを探していました。
バックアップの要件
-
Markdown フォーマット
-
User のすべての Medium 投稿を自動的にダウンロードする
-
記事内の画像もダウンロードしてバックアップ可能
-
Gist を Markdown のコードブロックにパースできる
(私の Medium では大量に gist を使ってソースコードを埋め込んでいるため、この機能は非常に重要です)
バックアップ方法
Medium 公式
公式にはエクスポート機能が提供されていますが、エクスポート形式は Medium へのインポート専用であり、Markdown や共通フォーマットには対応していません。また、Github Gist などの埋め込みコンテンツは処理されません。
Medium が提供する API はほとんどメンテナンスされておらず、Create Post 機能のみを提供しています。
妥当です。なぜなら Medium 公式はユーザーがコンテンツを簡単に他のプラットフォームに移行することを望んでいないからです。
Chrome 拡張機能
いくつかの Chrome 拡張機能を試しました(ほとんどが削除されていました)が、効果は良くありませんでした。まず、記事を一つずつ手動で開いてバックアップする必要があり、次に解析されたフォーマットに多くの誤りがあり、Gist のソースコードを深く解析できず、記事内のすべての画像をバックアップすることもできませんでした。
medium-to-markdown コマンドライン
ある有名な方が js で書いたもので、基本的なダウンロードと Markdown への変換は可能ですが、画像のバックアップや Gist ソースコードの深い解析はできません。
ZMediumToMarkdown
完璧な解決策が見つからなかったため、自分でバックアップ変換ツールを作成することに決めました;約3週間の仕事後の時間を使ってRubyで完成させました。
技術的詳細情報
ユーザー名を入力して記事リストを取得する方法は?
-
UserIDの取得:ユーザーのホームページ(https://medium.com/@#{username}) のソースコードを確認すると、
Usernameに対応するUserIDが見つかります。
ここで注意が必要なのは、Mediumがカスタムドメインを再び許可したため、30Xリダイレクトの処理を追加する必要があります。 -
ネットワークリクエストを解析すると、Medium が GraphQL を使ってホームページの投稿リスト情報を取得していることがわかります
-
Query をコピーして UserID をリクエスト情報に置き換える
HOST: https://medium.com/_/graphql
METHOD: POST
query = [{
"operationName": "UserProfileQuery",
"variables": {
"homepagePostsFrom": homepagePostsFrom,
"includeDistributedResponses": true,
"id": userID,
"homepagePostsLimit": 10
},
"query": "query Us...."
}]
4.レスポンスの取得
一度に取得できるのは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 が提供する構造が以下のようになっていることです:
code in text, and link, and ZhgChgLi, and bold, and I, only i
意味は code in text, and link in text, and ZhgChgLi, and bold, and I, only i このテキストの:
- 5文字目から7文字目まではコードとして表示する(`Text`形式で囲む)
- 18文字目から22文字目まではリンクとして表示する([Text](URL)形式で囲む)
- 50文字目から63文字目までは太字にする(*Text*形式で囲む)
- 55文字目から69文字目までは斜体にする(_Text_形式で囲む)
第 5 から 7 と 18 から 22 はこの例では扱いやすいです。なぜなら交差していないからです。しかし、50–63 と 55–69 は交差の問題があり、Markdown では以下のような交差を表現できません:
code `in` text, and [ink](http://zhgchg.li) in text, and ZhgChgLi, and **bold,_ and I, **only i_
正しい組み合わせ結果は以下の通りです:
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 の協力研究に感謝します。時間があれば後で自分でネイティブに書き直します。
成果
Post は ZMediumToMarkdown によって Medium から変換されました。



コメント