ScrapboxのバックアップをMarkdownで残したい
Scrapbox
オンライン上でサクサクとシンプルなメモがとれるツール、Scrapbox。
Scrapbox - A knowledge base built for infinite ideas
Edit seamlessly, together. Everything is set up so you can effortlessly explore ideas. You can edit pages in real time with a team or work asynchronously. Plus, loads of content types auto-embed or are supported like images, videos, maps, quick sketches, code, and math notation.
昨年に出会ってから、1年近くメモをためつづけ、気付けば1000ページを超えていました。
が、最近Notionを使いはじめ、「記事作成のためにはメモもMarkdwonで取っておいた方があとあと使いやすい」ということに気づき、Notionで日々のメモを取り始めました。
そうなると、今までのScrapboxでのメモもNotionにコピーしたくなります。
メモを検索するときも、2つのアプリで探すのは面倒なので、ひとつにまとめたいわけです。
Scrapboxのバックアップ
Scrapboxにはバックアップがあります。
JSON形式ですべてのページをエクスポートできます。
今回は、「JSON形式のScrapbox記法→Markdownへの変換」をPythonで書いてみた話です。
Pythonで変換
コード
早速コードの全体です。
1ページ用ごとに変換
関数部分
# タイトルからjsonの中身を検索
def search_from_title(title):
for page in list_pages:
if page["title"]==title:
# print(page["lines"])
return page["lines"]
else:
pass
# 変換のための関数
def image_(line):
if "[https://gyazo.com/" in line:
image = "![](https://i.gyazo.com/" + line.split("[https://gyazo.com/")[1].strip("]") + ".png)"
else:
image = line
return image
def link_(line):
if "https://youtu.be" in line or "https://www.youtube.com" in line:
link_ = line
elif "[http" in line:
try:
title = line.split(" ", 1)[1].strip("]")
link = line.split(" ", 1)[0].strip("[")
link_ = "[" + title + "](" + link + ")"
except:
# 例外のリンクはそのまま残す
print(line)
link_ = line
else:
link_ = line
return link_
# 変換
def sb2md(page):
list_md_ele = []
for i in range(len(page)):
list_ = []
line = page[i]
# スペース入りの行は飛ばす
if len(line)!=0 and line[0]==" ":
pass
else:
# 変換 lineを更新していく
# 箇条書き
line = line.replace('\t\t\t\t', ' - ')
line = line.replace('\t\t\t', ' - ')
line = line.replace('\t\t', '- ')
# 見だし
line = line.replace('\t', '## ')
# 引用
line = line.replace('>', '> ')
# 画像
line = image_(line)
# リンク
line = link_(line)
list_.append(line)
# コード
if "code:" in line:
list_.remove(line)
line = '```' + line.split(":")[1]
list_.append(line)
j = 1
while True:
code_ = page[i+j]
# コードの中身がスペースのみのとき
if code_==" ":
list_.append('```')
break
elif len(code_)!=0 and code_[0]==" ":
code = code_.lstrip(" ")
list_.append(code)
j += 1
else:
list_.append('```')
break
# tableはそのまま追加する
if "table:" in line:
list_.remove(line)
j = 1
while True:
table_ = page[i+j]
if len(table_)!=0 and table_[0]==" ":
code = table_.lstrip(" ")
list_.append(code)
j += 1
else:
break
# list_の中身を追加する
if len(list_)==1:
list_md_ele.append(list_[0])
else:
for ele in list_:
list_md_ele.append(ele)
# ページのタイトル
title = list_md_ele[0]
# 日付の下に1行空きを入れておく
list_md_ele = list_md_ele[1:]
list_md_ele.insert(1, '')
return title, list_md_ele
# ファイルへ書き出し
def md2file(title , list_):
title = title.replace("/", "")
with open('markdown/{}.md'.format(title), 'w') as f:
for d in list_:
f.write("%s\n" % d)
# print(title)
実行部分
import json
json_open = open('exportしたJSONファイル', 'r')
json_load = json.load(json_open)
list_pages = json_load["pages"]
page = search_from_title("ページのタイトル")
title , list_ = sb2md(page)
md2file(title , list_)
ページのタイトルをinputに、.md形式のファイルの出力まで、行えます。
JSONバックアップからすべてmdに変換
今回やりたかったのはこちら。
import time
import json
json_open = open('json/inaeva7691_20200602.json', 'r')
json_load = json.load(json_open)
list_pages = json_load["pages"]
# 開始時間
start = time.time()
for page in list_pages:
print(page["title"])
title , list_ = sb2md(page["lines"])
md2file(title , list_)
# 終了時間
elapsed_time = time.time() - start
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
参考として、変換にかかる時間を測りました。
1043ページで
elapsed_time:0.7751188278198242[sec]
でした。
実用上まったく問題ない早さだと思います。
対応している変更箇所
今回のコードで、対応している変換箇所についてです。
雑な実装なので、きちんとすべての記法には対応していません。tableなどは崩れてしまいます。
見た目よりもテキストの保存データとして、ある程度整形できたらいいや、の精神で作ったのでご了承ください🙏
(もっときれいに変換できるよ!という方はぜひ教えてもらえたらうれしいです)
このあたりの変換ができます。
個人的な仕様の変換があるので、適宜コードをいじって変換内容を変えてみて下さい。
変換後の見た目の比較
変換前
変換後
同じ内容のページで比較しています。
完全にMarkdownにきれいに変換できるわけではありませんが、自分が満足するくらいのきれいさでOKです。
その他、使えるかもしれないコード
ファイルを100ページごとにフォルダに分ける
バックアップするページが多いと、importの際にも管理が大変です。
例えば、100ページごとにフォルダに分ける場合は、
import os
import glob
import shutil
# #markdownフォルダに移動
os.chdir("markdown/")
list_md = glob.glob("*.md")
# 100個ずつのフォルダに分割
n = 100
list_md_100 = [list_md[idx:idx + n] for idx in range(0,len(list_md), n)]
# 移動
for i in range(len(list_md_100)):
i_ = str(i)
os.mkdir(i_)
for md in list_md_100[i]:
shutil.move(md, "{}/".format(i_))
このような感じでPythonで分割できたりできます。
他のツールで変換する場合
別にPythonで1から作らなくても、すでにある他のツールで変換できたりします。
ですが、大体が「1ページ」ごとの変換だったり、思ったように変換できなかったり…と、
「バックアップ全体を一括でmarkdownに変換」できるものがなかったので、自分で作ってみた次第です。
いくつかツールを調べてみたので、紹介しておきます。
オンライン上で変換
どちらも、scrapbox記法で書いたテキストを貼り付けます。
Online ScrapBox Converter
- scrapbox→markdown
- markdown→scrapbox
の両方ができました。
scrapbox-markdown
https://gyo.gitlab.io/scrapbox-markdown/
React App
Scrapboxのページから
UserScriptなりをつかって、ページ内部から変換できます。
sb2md.sbext.js
Scrapbox -> Markdown (Scrapbox
extension)
Scrapboxの記事をMarkdownとしてコピーできるScrapbox拡張を書いた
userscriptに追加すると、ページの詳細部分から変換できます。
Scrapbox2Markdown
Scrapboxページの文章をMarkdownに変換するBookmarkletを書いた - #daiizメモ
ScrapboxコンテンツをMarkdownに変換するBookmarklet - daiiz
ScrapboxコンテンツをMarkdownに変換するBookmarklet - Scrapboxカスタマイズコレクション
ブックマークレットとして登録することで使えます。
ブックマークからリンクの部分を、
javascript:(function(){var m=function(b){b=b.replace(/&/g,"&");b=b.replace(/</g,"<");b=b.replace(/>/g,">");b=b.replace(/"/g,""");return b=b.replace(/'/g,"'")},n=function(b){b=void 0===b?0:b;for(var a="",g=1;g<b;g++)a+=" ";return a},p=function(b){b=void 0===b?"":b;var a=document.createElement("div");a.innerHTML=b;b=a.querySelectorAll("strong");for(var g=0;g<b.length;g++){var d=b[g],e=+d.className.split("level-")[1],c=d.innerHTML;e=void 0===e?1:e;e=6-e;for(var f="",h=0;h<e;h++)f+="#";d.innerHTML=
f+" "+c}return a.innerHTML},q=function(b){b=void 0===b?"":b;var a=document.createElement("div");a.innerHTML=b;b=a.querySelectorAll("a");for(var g=0;g<b.length;g++){var d=b[g],e=d.innerText.trim(),c=d.href;e="["+e+"]("+c+")";var f=d.querySelector("img");null!==f&&(e="[![Image]("+f.src+")]("+c+")");d.innerText=e}return a.innerText},f=document.querySelector(".lines"),r=f.querySelector(".line-title .text").innerText;f=f.querySelectorAll(".line");pageTexts=[];for(var l=1;l<f.length;l++){for(var c=f[l].querySelector(".text").cloneNode(!0),
h=c.querySelectorAll("span.empty-char-index"),a=0;a<h.length;a++){var k=h[a];k.innerText=""}h=c.querySelectorAll("span.backquote");for(a=0;a<h.length;a++)k=h[a],k.innerText="`";a=c.innerHTML.replace(/<span>/g,"");a=a.replace(/<span.+?>/g,"").replace(/<\/span>/g,"");a=a.replace(/<br.+?>/g,"");a=a.replace(/\n/gi,"").replace(/\t/gi,"").trim();a=p(a);a=q(a);c=c.querySelector(".indent-mark");null!==c&&(k=+c.style.width.split("em")[0]/1.5*2,a=n(k)+"- "+a);null===c&&0<a.length&&"#"!==a[0]&&(a+="<br>");pageTexts.push(a)}(function(b,
a){b=void 0===b?"Title":b;a=void 0===a?[]:a;for(var c="# "+b+"\n",d=0;d<a.length;d++)c+="\n"+a[d];a=window.open();a.document.open();a.document.write("<title>"+b+"</title>");a.document.write("<pre>");a.document.write(m(c));a.document.write("</pre>");a.document.close()})(r,pageTexts)})();
として登録したらOKです。
実行すると、別ページとして開かれます。
sb2md
hitode909/sb2md
コマンドとして変換できるらしいのですが、どうもインストールが上手くいきませんでした。
どこで使う?
今回のコードはどこで使えるのでしょうか?
大きく2つあると思います。
私は、Notionにインポートするために使ってみました。
移行の様子などは次の記事にしようと思います。
記事の感想をリアクションでお願いします!