科技 · 實用指南
將 Joplin HTML 匯出內容拆成附帶所需附件、可獨立搬移的筆記本
這套經過測試的 Linux 批次流程,可將一份完整的 HTML 目錄匯出內容拆成各自獨立的筆記本封裝,再檢查是否缺少檔案,以及是否仍有連結依賴 Joplin 或其他筆記本。

若要從 Joplin HTML 匯出內容中單獨搬出一本筆記本,必須將它的 HTML 筆記與實際引用的資源一起封裝。HTTrack 可以複製這些相依檔案,並改寫本機連結。以下批次流程從一份完整的匯出內容開始,列出每本頂層筆記本中的所有筆記,再為每本筆記本建立獨立的外層資料夾。
最後的檢查和複製本身一樣重要。在我們的測試中,HTTrack 回報零錯誤,但通往其他筆記本的連結仍指向已停止的本機伺服器。因此,複製後還要核對筆記清單、檢查附件,並產生明確的連結報告,才能判斷這份封裝是否可以獨立閱讀。
先確認是否需要分開封裝
如果你只是想將整份封存搬到另一顆磁碟,保留完整的匯出目錄即可。筆記本資料夾與共用的 _resources 目錄可以一起搬移。如果只有少數幾本筆記本,逐本匯出也可能比較簡單。
當你手上已經有一份大型的 HTML 目錄匯出內容,而且希望各本筆記本可以獨立搬移時,這套批次流程就派得上用場。2026 年的一篇 Joplin 論壇討論提到,使用者有約 100 本筆記本,實際面臨的障礙就是必須逐本重複手動匯出。
本流程保留匯出後的 HTML 閱讀格式。如果你真正的目的是匯入另一款筆記應用程式,請改為考慮該程式支援的格式,或 Jimmy 之類的轉換工具。Joplin 文件將 JEX 說明為可保留筆記中繼資料、無損且能重新匯入的備份格式;請在保留原始匯出內容的同時,也保留一份 JEX。
從完整的桌面版匯出內容開始
透過 Joplin 桌面版應用程式的匯出選單,將需要的筆記本匯出為 HTML 目錄。請選擇會產生個別 HTML 檔案與筆記本資料夾的選項。本指南檢視的 v3.7.21 原始碼顯示,Joplin 命令列應用程式不支援 HTML 匯出。
匯出前,請等待同步與附件下載完成。Joplin 的附件設定分成 Always、Auto 與 Manual:Auto 會在開啟筆記時下載,Manual 則要等到你明確要求下載才會進行。本指南檢視的 HTML 匯出程式會略過尚未下載、由 Joplin 管理的資源。封裝流程無法重建來源中缺少的檔案內容。
在檔案管理員中開啟匯出資料夾。本流程要求它的下一層直接包含各筆記本目錄與共用的 _resources 目錄;pluginAssets 則可能出現在筆記本目錄內。巢狀筆記本會留在所屬頂層筆記本的封裝內。請保留這份匯出內容的完整結構,並將輸出放在它之外的位置。
繼續之前,先開啟幾份有代表性的原始 HTML 筆記及其附件,包括一份位於較深層目錄的筆記,以及一個 PDF 或文件連結。如果某份筆記原本就連到錯誤的位置,必須先修正來源匯出內容。一般檔案系統連結、特殊的自訂 HTML 與外掛產生的內容,可能需要另外處理;本流程適用於 Joplin 一般的匯出目錄配置。
1. 設定四個值,啟動本機伺服器
請使用已安裝 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。
請在同一個 Bash 工作階段中,將每條編號步驟的指令以單一行執行,保留半形直引號、反斜線與分號。第一條指令會建立輸出目錄,並啟動只綁定 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 筆記的網址寫入一份 .urls.txt 檔案。起始網址中的空格與非 ASCII 字元都會經過編碼。它會從筆記清單中排除 pluginAssets,然後為每本筆記本各執行一次 HTTrack。
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 筆記之間不必互相連結,因為所有筆記都已明確列入網址清單。
等待指令完成,並閱讀每本筆記本的起始網址數量與 HTTrack 結束狀態碼。如果出現 Python 錯誤回溯訊息、非零結束狀態碼,或某本筆記本意外未出現在輸出中,請停止操作。正常結束與零錯誤紀錄都是有用的檢查結果,但兩者都無法證明閱讀所需的每個相依項目已經備妥。
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 網址,包括 href 或 src 引用的遠端圖片或樣式表。爬取篩選規則會將這些遠端資源留在封裝之外。如果筆記依賴遠端內容,請檢查那些網址,或在中斷網路連線的情況下開啟搬移後的副本,再判斷相關內容是否可以離線使用。一般的網站連結仍可保留,供連線時使用。

將一份封裝移出批次資料夾,再驗證一次
從 OUT-moved 中選一本筆記本的外層資料夾,將整個資料夾複製到另一個位置,例如另一顆磁碟上的暫存資料夾。HTTrack 會在其中建立以主機名稱命名的目錄,例如 127.0.0.1_8765,裡面包含筆記本與它專用的 _resources。請讓這個以主機名稱命名的目錄及其所有內容保持在一起。
將為這本筆記本產生的 .urls.txt 清單,逐一對照鏡像筆記本目錄樹中的 HTML 檔案。請檢查檔名與巢狀路徑,不要只看總數,因為總數可能包含 HTTrack 自己產生的索引頁。每份預期的筆記都應該存在,包括沒有任何連結指向的筆記。請為每份打算保留的封裝重複核對清單。
在本機伺服器停止的情況下,從單獨搬移的外層資料夾開啟筆記。開啟 PDF 或文件附件、查看圖片,並檢查巢狀筆記與使用外掛資源的格式。若是重要的二進位附件,請將副本的檢查碼與原始 _resources 目錄中對應檔案的檢查碼比對;不要將套用附件檔名的錯誤頁面視為複製成功。
每份引用共用附件的筆記本封裝,都應該在內部各自保存一份該附件。只有另一本筆記本使用的檔案,不應只因兩本筆記本來自同一份匯出內容,就也出現在這份封裝內。當筆記清單、必要附件與連結限制都已逐項確認,這份封裝才算是一份獨立的閱讀副本。請繼續保留原始完整匯出內容與 JEX 備份。
實際測試的內容,以及仍需自行確認的部分
我們在 Linux 上使用 Bash、Python 3 與 HTTrack 3.50-5 進行測試,合成測試資料的目錄結構以 Joplin v3.7.21 的 HTML 匯出程式為依據。這不是從頭到尾操作 Joplin 桌面介面的測試,也沒有測試每個匯出版本與外掛。
最後的批次測試包含兩本頂層筆記本,其中一本的名稱含有空格與中文字元。五份預期的 HTML 筆記全數複製成功,包括巢狀筆記與沒有其他筆記連入的筆記。第一份封裝包含預期的 10 個全域資源與四個外掛資源;第二份包含預期的四個全域資源與一個外掛資源。共用 PDF 與文件副本的位元組內容均與來源一致,而且全部 24 個來源檔案都未改動。
停止伺服器並搬移輸出後,測試資料的驗證程式確認了 21 個有效的本機 HTML 與 CSS 參照。上方提供給讀者的指令則另外檢查了 18 個 HTML href/src 參照,並正確回報兩個跨筆記本的本機 HTTP 連結與兩個 joplin:// 連結。兩份 HTTrack 紀錄仍然都回報零錯誤。
較早的一版篩選規則納入了編碼後的筆記本名稱,結果漏掉了那本名稱含空格與中文字元的筆記本所需的樣式表。最後的流程改為追蹤標準資源目錄,並明確列出所有筆記作為起點。因此,篩選規則必須搭配後續檢查:複製結果、執行紀錄,以及成功開啟第一份筆記,各自都只能回答可否獨立搬移這個問題的一部分。