Watch at Ease세상을 읽고, 일상의 답을 찾다.

기술 · 실용 가이드

Joplin에서 내보낸 HTML을 첨부 파일과 함께 옮길 수 있는 노트북별 패키지로 나누기

테스트한 Linux 일괄 처리 절차로 전체 HTML 디렉터리 내보내기 결과를 노트북별 패키지로 나눈 뒤, 빠진 파일과 여전히 Joplin 또는 다른 노트북에 의존하는 링크를 확인합니다.

Joplin HTML 내보내기 하나가 노트북 패키지 두 개로 나뉘고, 각 패키지에 필요한 공유 첨부 파일의 자체 복사본이 들어가는 과정을 보여주는 자체 제작 일러스트.
자체 제작 편집용 일러스트: 각 노트북의 바깥 패키지 폴더에는 필요한 리소스가 따로 들어갑니다. 두 노트북이 함께 사용하는 첨부 파일은 양쪽 패키지에 모두 복사됩니다. Watch at Ease

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 세션에서 실행하세요. 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을 한 번씩 실행합니다.

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이 아닌 종료 상태, 예상과 달리 출력에서 빠진 노트북이 있으면 멈추세요. 정상 종료와 오류 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 속성을 검사하지는 않습니다. 실제로 표시되는 노트와 첨부 파일도 확인하세요.

보고서는 href나 src에서 참조하는 원격 이미지 또는 스타일시트를 포함해, 일반적인 외부 HTTP 또는 HTTPS URL을 표시하지 않습니다. 크롤링 필터도 이러한 원격 리소스를 패키지에 포함하지 않습니다. 노트가 원격 콘텐츠에 의존한다면, 해당 URL을 살펴보거나 네트워크 연결을 끊은 상태에서 옮긴 사본을 열어, 해당 콘텐츠를 오프라인에서 사용할 수 있는지 확인하세요. 일반적인 웹사이트 링크는 유용한 온라인 링크로 남겨둘 수 있습니다.

로컬 서버를 중지하고 노트북 패키지 하나를 옮긴 뒤의 확인 절차를 보여주며, 다른 노트북으로 연결되는 링크와 Joplin 앱 링크를 검토 대상으로 표시한 자체 제작 일러스트.
자체 제작 편집용 일러스트: 서버를 중지하고 패키지 전체를 옮긴 뒤 노트와 첨부 파일을 확인합니다. HTTrack이 오류 0건을 보고해도 다른 노트북으로 연결되는 링크와 joplin:// 링크는 검토해야 합니다. Watch at Ease

일괄 출력 폴더 바깥에서 패키지 하나 검증하기

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개와 플러그인 리소스 4개가 있었고, 두 번째에는 예상한 전역 리소스 4개와 플러그인 리소스 1개가 있었습니다. 공유 PDF와 문서의 복사본은 원본과 바이트가 일치했으며, 원본 파일 24개는 모두 변경되지 않았습니다.

서버를 중지하고 출력을 옮긴 뒤, 테스트 데이터 검증기는 유효한 로컬 HTML 및 CSS 참조 21개를 확인했습니다. 독자에게 제공하는 위 명령은 이와 별도로 HTML href/src 참조 18개를 검사해, 다른 노트북으로 연결되는 로컬 HTTP 링크 두 개와 joplin:// 링크 두 개를 정확히 보고했습니다. 그런데도 두 HTTrack 로그는 모두 오류 0건으로 표시되었습니다.

앞선 필터에는 인코딩된 노트북 이름이 들어 있었는데, 공백과 중국어 문자가 있는 노트북의 스타일시트 하나를 놓쳤습니다. 최종 절차는 그 대신 표준 리소스 디렉터리를 따라가고 모든 노트를 시작 URL로 명시합니다. 필터와 후속 확인이 함께 필요한 이유입니다. 복사 결과, 로그, 첫 번째 노트가 잘 열렸다는 사실은 각각 패키지를 따로 옮겨 읽을 수 있는지에 대한 답의 일부만 보여줍니다.