テクノロジー · 実用ガイド
Joplin の HTML エクスポートを、添付ファイル込みで持ち運べるノートブックに分割する
検証済みの Linux 一括処理で、完全な HTML ディレクトリエクスポートをノートブック別のパッケージに分けます。さらに、不足ファイルや、Joplin または別のノートブックに依存したままのリンクを確認します。

Joplin の HTML エクスポートからノートブックを切り出すには、そのノートブックの HTML ノートと、実際に参照しているリソースを一緒にまとめます。HTTrack はこうした依存ファイルをコピーし、ローカルリンクを書き換えられます。以下のバッチ処理は、構成を崩していない一式のエクスポートを出発点に、最上位のノートブックごとにすべてのノートを列挙し、それぞれのパッケージを収める外側のフォルダーを作ります。
最後の確認は、コピー処理と同じくらい大切です。今回のテストでは、別のノートブックへのリンクが停止済みのローカルサーバーを指したままでも、HTTrack の報告はエラー 0 件でした。パッケージを単独で閲覧できるものとみなす前に、コピー後のノート一覧との照合、添付ファイルの確認、リンクレポートの確認まで行ってください。
個別パッケージが必要かを判断する
アーカイブ全体を別のドライブに移したいだけなら、エクスポートしたディレクトリ全体を一緒に移動してください。ノートブックのフォルダーと共有の _resources ディレクトリを一式として持ち運べます。ノートブックが少数なら、それぞれを個別にエクスポートするほうが簡単な場合もあります。
この一括処理が役立つのは、すでに大きな HTML ディレクトリエクスポートが一つあり、各ノートブックを個別に持ち運びたい場合です。2026 年の Joplin Forum の議論では、約 100 個のノートブックを持つライブラリについて、ノートブックごとに手動エクスポートを繰り返すことが実務上の障害になっていました。
この手順では、エクスポート済みの HTML という閲覧形式を維持します。本来の目的が別のノートアプリへのインポートなら、移行先のアプリが対応する形式や、Jimmy などの変換ツールを検討してください。Joplin の説明では、JEX はノートのメタデータを保持する、情報を失わず再インポートできるバックアップ形式です。元のエクスポートと併せて保管してください。
完全なデスクトップ版のエクスポートを用意する
Joplin デスクトップアプリのエクスポートメニューから、必要なノートブックを HTML ディレクトリ形式でエクスポートします。個別の HTML ファイルとノートブックのフォルダーを生成する選択肢を使ってください。本ガイドで確認した v3.7.21 のソースでは、Joplin のコマンドライン版は HTML エクスポートに対応していません。
エクスポート前に、同期と添付ファイルのダウンロードを完了させてください。Joplin の添付ファイル設定には Always、Auto、Manual というダウンロード動作の違いがあります。Auto はノートを開いたときにダウンロードし、Manual は利用者が明示的にダウンロードするまで待ちます。確認した HTML エクスポーターは、ダウンロードされていない管理対象のリソースをスキップします。パッケージ化しても、元のエクスポートにないデータは復元できません。
ファイルマネージャーでエクスポート先のフォルダーを開きます。この手順が想定する構成では、その直下にノートブックのディレクトリと共有の _resources ディレクトリがあります。pluginAssets はノートブックのディレクトリ内に現れることがあります。入れ子のノートブックは、最上位の親ノートブックのパッケージに含めます。このエクスポートの構成を崩さず、出力先はその外にしてください。
先に、元の HTML ノートと添付ファイルをいくつか開いて確認します。深い階層のノートと、PDF または文書へのリンクも含めてください。リンク先がすでに誤っているノートは、まず元のエクスポートの問題を修正する必要があります。通常のファイルシステムへのリンク、特殊な独自 HTML、プラグインが生成する内容には、別の対応が必要な場合があります。この手順は Joplin の通常のエクスポート構成を対象としています。
1. 4 つの値を設定し、ローカルサーバーを起動する
Python 3 と HTTrack のコマンドラインプログラムをインストールした Linux の Bash ターミナルを使います。検証した HTTrack のバージョンは 3.50-5 です。プログラムはプロジェクトの公式ダウンロードページ https://www.httrack.com/page/2/en/index.html から入手し、この手順を使う前に実行ファイルのバージョンを確認してください。以下のコマンドは Bash 構文であり、Windows のコマンドプロンプト、PowerShell、macOS では検証していません。
次のコマンドでは、EXPORT の値を構成を崩していないエクスポートの絶対パスに、HTTRACK の値を実行ファイルの絶対パスに置き換えます。OUT はエクスポートの外にある、まだ存在しない新しいディレクトリのパスにしてください。その親ディレクトリは既存のものを使います。OUT に指定したパスの末尾に -moved を付けたパスも、まだ存在しないものにします。ローカルで使われていないポートを選んでください。例では 8765 を使います。
番号付きのコマンドは、それぞれ改行を挟まない 1 行のコマンドとして、同じ Bash セッションで実行してください。ASCII の引用符、バックスラッシュ、セミコロンをそのまま保ちます。最初のコマンドは出力ディレクトリを作り、127.0.0.1 のみにバインドしたサーバーを起動します。ノートをウェブサイトにアップロードする処理ではありません。
EXPORT="/absolute/path/to/html-export"; OUT="/absolute/path/to/new-notebook-bundles"; PORT=8765; HTTRACK="/absolute/path/to/httrack"; mkdir "$OUT" && { python3 -m http.server "$PORT" --bind 127.0.0.1 --directory "$EXPORT" >"$OUT/server.log" 2>&1 & HTTRACK_SERVER_PID=$!; printf "%s\n" "$HTTRACK_SERVER_PID" >"$OUT/server.pid"; sleep 1; kill -0 "$HTTRACK_SERVER_PID"; }
このコマンドでエラーが出たら、バッチ処理に進む前に停止してください。OUT/server.log を確認し、パスまたはポートの問題を解消してから、新しい出力ディレクトリを使います。ターミナルのセッションは開いたままにしてください。後続のコマンドは、このセッションの変数と、記録したサーバープロセス ID を使います。
2. 最上位のノートブックごとに個別パッケージを作る
このコマンドは各ノートブックを再帰的に調べ、すべての HTML ノートの URL を含む .urls.txt ファイルを書き出します。開始 URL 内の空白と ASCII 以外の文字はエンコードされます。pluginAssets はノート一覧から除外し、その後、ノートブックごとに HTTrack を 1 回実行します。
RESULT=0; for BOOK in "$EXPORT"/*/; do NAME=${BOOK%/}; NAME=${NAME##*/}; [ "$NAME" = "_resources" ] && continue; LIST="$OUT/$NAME.urls.txt"; python3 -c 'import pathlib,sys;from urllib.parse import quote;s=pathlib.Path(sys.argv[1]).resolve();b=pathlib.Path(sys.argv[2]).resolve();sys.stdout.write("".join("http://127.0.0.1:"+sys.argv[3]+"/"+quote(p.relative_to(s).as_posix(),safe="/")+"\n" for p in sorted(b.rglob("*.html")) if "pluginAssets" not in p.relative_to(b).parts))' "$EXPORT" "$BOOK" "$PORT" >"$LIST"; [ -s "$LIST" ] || continue; "$HTTRACK" --list "$LIST" -B -N0 -K0 -q "-*" "+127.0.0.1:$PORT/_resources/*" "+127.0.0.1:$PORT/*/pluginAssets/*" -O "$OUT/$NAME"; STATUS=$?; printf "%s: %s HTML seeds; HTTrack exit %s\n" "$NAME" "$(wc -l <"$LIST")" "$STATUS"; [ "$STATUS" -eq 0 ] || { RESULT=1; break; }; done; test "$RESULT" -eq 0
フィルターは一般的なクロールを拒否したうえで、_resources と pluginAssets 内の参照されているファイルへのアクセスを許可します。共有の _resources ディレクトリ自体は開始ページに使わないため、そのディレクトリ一覧を意図的に丸ごと取得する処理ではありません。HTML ノート同士がリンクされている必要はありません。すべてのノートを URL 一覧で明示的に指定しているためです。
コマンドが完了するまで待ち、ノートブックごとの開始 URL 件数と HTTrack の終了ステータスを確認します。Python の traceback が出る、終了ステータスがゼロ以外になる、出力にあるはずのノートブックが見当たらない、といった場合は停止してください。正常終了とエラー 0 件のログは有用な確認材料ですが、どちらも閲覧に必要な依存ファイルがすべてそろったことを証明するものではありません。
3. サーバーを停止し、出力を移動して未解決のリンクを報告する
次のコマンドは、このセッションのローカルサーバーを停止し、出力ディレクトリの名前の末尾に -moved を付けます。その後、ミラーされた HTML ファイルを走査し、名前を変えた出力ディレクトリの直下に link-report.json を書き出します。この名前変更で元のエクスポートが変更されることはありません。
kill "$HTTRACK_SERVER_PID" && mv "$OUT" "$OUT-moved" && python3 -c 'exec("import pathlib,sys,json\nfrom html.parser import HTMLParser\nfrom urllib.parse import urlsplit,unquote\nr=pathlib.Path(sys.argv[1]).resolve()\nrows=[]\nchecked=0\nfor m in sorted(r.glob(\"*/127.0.0.1*\")):\n for f in sorted(m.rglob(\"*.html\")):\n links=[]\n p=HTMLParser()\n p.handle_starttag=lambda tag,attrs: links.extend(v for k,v in attrs if k in (\"href\",\"src\") and v)\n p.feed(f.read_text(encoding=\"utf-8\",errors=\"replace\"))\n for v in links:\n u=urlsplit(v)\n if u.scheme==\"joplin\" or u.hostname==\"127.0.0.1\":\n rows.append({\"file\":str(f.relative_to(r)),\"kind\":\"joplin\" if u.scheme==\"joplin\" else \"local-http\",\"url\":v})\n elif not u.scheme and not u.netloc and u.path:\n checked+=1\n t=(f.parent/unquote(u.path)).resolve()\n if not t.is_file() or m.resolve() not in t.parents:\n rows.append({\"file\":str(f.relative_to(r)),\"kind\":\"missing-or-outside-local\",\"url\":v})\nresult={\"checked_local_html_references\":checked,\"review\":rows}\ntext=json.dumps(result,ensure_ascii=False,indent=2)\n(r/\"link-report.json\").write_text(text,encoding=\"utf-8\")\nprint(text)\n")' "$OUT-moved"
レポートには、確認が必要な各項目の HTML ファイル、リンク、理由が列挙されます。checked_local_html_references の数値は、検査したローカルの HTML href/src 参照の件数です。ノート数や添付ファイル数、すべての依存関係の数ではありません。review 一覧が空であることは有用な確認材料ですが、それだけでアーカイブ全体の検証が完了したことにはなりません。
サーバーの停止や出力の移動に失敗した場合、後続の走査が実行されたと思い込まないでください。先にその失敗を解消します。新たに実行する際は以前の出力先を使い回さず、新しい OUT と OUT-moved のパスを選んでください。以前のミラーが残っていると、今回の出力の不足を見落とす原因になります。
レポートを読んでからパッケージを使う
local-http の項目は、まだ 127.0.0.1 を指しています。意図的にこのパッケージの範囲外とした別のノートブックへのリンクか、取得できなかったリソースの可能性があります。表示された元のノートを開き、リンク先を調べてください。必要な画像、スタイルシート、添付ファイルがまだローカルサーバーを参照しているなら、パッケージは不完全です。
joplin の項目はアプリケーションへのリンクです。ノートをコピーしただけでは、持ち運べる通常の HTML リンクには変わりません。missing-or-outside-local の項目は、存在しないローカルファイル、またはそのパッケージのミラー先ディレクトリ外にあるローカルファイルを指しています。コピーを頼りにする前に、これらの項目を調べてください。
確認対象のリンクがノートブック間の移動用リンクだけなら、その制限を記録したうえで閲覧用コピーとして役立つかを判断してください。そうしたリンクも機能させたい場合は、元の完全なエクスポートを一式のまま保持します。この手順は、ノートブック間のリンクを書き換えて新しい統合ライブラリを作ったり、既存の joplin:// リンクを修復したりするものではありません。
自動走査の対象は HTML 内の href と src です。CSS の url() による依存ファイル、スクリプトが生成するリクエスト、ページ内アンカーの参照先、組み込まれたアプリケーションの動作は調べません。そのほかの HTML 属性を網羅しているわけでもありません。実際に表示したノートと添付ファイルも確認してください。
レポートは、通常の外部 HTTP / HTTPS URL を確認対象として列挙しません。href や src から参照される外部の画像やスタイルシートも対象外です。クロールのフィルターにより、こうした外部リソースはパッケージに取り込まれません。ノートが外部コンテンツに依存している場合は、オフラインで利用できると判断する前に、その URL を確認するか、ネットワークを切断した状態で移動後のコピーを開いてください。通常のウェブサイトへのリンクは、オンラインで使うリンクとして残しておけます。

パッケージを一つ、バッチ出力フォルダーの外で検証する
OUT-moved の下にあるノートブックの外側のフォルダーを一つ選び、別のドライブの一時フォルダーなど、別の場所へ丸ごとコピーします。その中には、HTTrack が作成する 127.0.0.1_8765 のようなホスト名付きディレクトリがあり、ノートブックと専用の _resources が収まっています。このホスト名付きディレクトリとその中身は、必ず一緒に保持してください。
生成されたノートブックの .urls.txt 一覧を、そのノートブックのミラー内にある HTML ファイルと照合します。HTTrack 自身の index ページが含まれる可能性のある合計件数だけでなく、ファイル名と階層内のパスを確認してください。ほかからリンクされていないノートも含め、想定したすべてのノートが存在する必要があります。保管する予定のパッケージごとに、この一覧照合を繰り返します。
ローカルサーバーを停止した状態で、個別に移動した外側のフォルダーからノートを開きます。PDF または文書の添付ファイルを開き、画像、入れ子のノート、プラグインアセットを使う書式も確認してください。重要なバイナリ添付ファイルは、コピー先のチェックサムを元の _resources ディレクトリ内の対応ファイルと比較します。添付ファイルの名前が付いたエラーページを、コピー成功とみなさないでください。
共有の添付ファイルは、それを参照する各ノートブックのパッケージ内に存在する必要があります。別のノートブックでしか使われないファイルは、同じエクスポートに由来するという理由だけで含まれるべきではありません。ノートの一覧、必要な添付ファイル、リンクの制限をそれぞれ確認できたら、そのパッケージは個別の閲覧用コピーになります。元の完全なエクスポートと JEX バックアップは引き続き保管してください。
今回検証したことと、利用者による確認が必要なこと
Linux 上で Bash、Python 3、HTTrack 3.50-5 を使い、Joplin v3.7.21 の HTML エクスポーターのディレクトリ構造に基づいて作成した合成テスト用データで検証しました。Joplin デスクトップアプリの画面操作を含む一連の手順を最初から最後まで実測したものではなく、すべてのエクスポートのバージョンやプラグインを検証したものでもありません。
最終バッチには最上位のノートブックが二つあり、一つの名前には空白と中国語の文字を含めました。入れ子のノートと、ほかからリンクされていないノートも含め、想定した 5 個の HTML ノートをすべてコピーできました。最初のパッケージには _resources 内の想定した 10 個のリソースと 4 個のプラグインアセットがあり、二つ目には想定した 4 個のリソースと 1 個のプラグインアセットがありました。共有 PDF と文書ファイルのコピーは元データとバイト単位で一致し、元の 24 ファイルはすべて変更されていませんでした。
サーバーを停止して出力を移動した後、テスト用データの検証ツールは、HTML と CSS に含まれる有効なローカル参照を 21 件確認しました。これとは別に、上記の読者向けコマンドは HTML の href/src 参照を 18 件検査し、ノートブック間のローカル HTTP リンク 2 件と joplin:// リンク 2 件を正しく報告しました。それでも両方の HTTrack ログはエラー 0 件を報告していました。
初期のフィルターでは、エンコードしたノートブック名を条件に含めたところ、空白と中国語の文字を含む名前のノートブックでスタイルシートを取得できませんでした。最終手順では、代わりに標準のリソースディレクトリをたどり、すべてのノートを開始 URL として明示的に指定します。フィルターと処理後の確認を組み合わせるのはこのためです。コピー結果、ログ、最初のノートが開けたことは、それぞれ持ち運んだ後も読めるかという問いの一部にしか答えていません。