本記事は生成AIと共同で執筆しています。事実関係は可能な範囲で公式ドキュメント等と照合していますが、誤りが含まれている可能性があります。重要な判断を行う前にご自身でも一次情報をご確認ください。

はじめに(とおことわり)

ROIS-DS 人文学オープンデータ共同利用センター(以下 CODH)のホームページ codh.rois.ac.jp が長期メンテナンスのためサービスを一時停止しています(ROIS-DS による 2026 年 2 月 24 日付公式アナウンス、再開時期未定)。同ホストで公開されていた以下のブラウザベースのツール群を直接埋め込んでいた箇所が動かなくなっています。

  • vdiff.js / vdiff-seq.js
  • IIIF Curation Viewer / Manager / Editor / Player / Board
  • そあん(soan)

別記事 CODH 提供ツールの一時ミラーを Wayback Machine から復元する では、Wayback Machine の id_ フラグで wombat 注入を避けて生バイトを取り出す手順や、各ツールの最小構成の解析方法を扱いました。本記事はその続編として、取り出した一式を独立した GitHub Pages リポジトリ codh-mirror として実運用するまでで踏んだ落とし穴をまとめます。

繰り返しになりますが、本記事の内容は CODH のサービス再開までの暫定対応です。CODH のソフトウェアは MIT ライセンス等で再配布が許諾されていますが、Wayback の特定時点で凍結された状態であり、本家のその後の修正は反映されません。サービス再開後はミラーを順次撤去する前提です。

リポジトリ構成

シンプルに、ツール名のディレクトリを並べるだけの構造にしました。

codh-mirror/
├── README.md
├── .gitignore
├── vdiff/
├── vdiff-seq/
├── iiif-curation-viewer/
├── iiif-curation-manager/
├── iiif-curation-editor/
├── iiif-curation-player/
├── iiif-curation-board/
└── soan/
    ├── index.html
    ├── index.js
    ├── soan/{soan.bundle.css, soan.bundle.min.js}
    ├── dataset/001.json
    ├── dataset/001/<36869 PNG>
    └── kuromoji/dict/<12 .dat.gz>

合計サイズは ワーキングツリーで約 370 MB(うちそあんの古活字 PNG 36,869 枚で 186 MB、.git を除いた配信対象だけなら約 210 MB)。GitHub Pages の 1 GB 上限には十分余裕があり、リポジトリも GitHub の推奨上限(1 GB)に収まります。

GitHub Pages の有効化は CLI から gh api 1 発で済みます。

gh api repos/<owner>/codh-mirror/pages -X POST \
  -F "source[branch]=main" -F "source[path]=/"

ビルドは1〜2分。gh api repos/<owner>/codh-mirror/pages"status":"built" になれば配信開始です。

落とし穴 1: そあんの古活字 PNG 36,869 枚は CODH 絶対 URL で参照されている

そあん(soan)の dataset/001.json は、各文字に対応する古活字 PNG 画像の URL を持っています。CODH デモ版の JSON では、これが 絶対 URLcodh.rois.ac.jp を指していました。

{
  "attribution": "『徒然草』国立国会図書館蔵",
  "data": [
    {"url": "https://codh.rois.ac.jp/soan/dataset/001/001_001_2_01_01_000001.png",
     "char": "つれ〱", "jibo": "徒連〱"},
    ...
  ]
}

データ件数は 36,869 件。CODH 停止中に Wayback Machine からこの数の PNG を取り切るのは現実的ではなく(レート制限が厳しく、そもそもバイナリ画像は網羅的にアーカイブされていない)、別の入手経路が必要でした。

幸い、そあんの core contributor である @2SC1815J 氏が個人で運用している「そあん(soan)プロフェッショナル版」(https://dev.2sc1815j.net/soan/) のホストが生きていて、ここに同じ古活字 PNG が同じファイル名で置かれていました(パスは相対 dataset/001/...png)。画像のバイナリだけこちらから 36,869 枚をダウンロードして流用しました。

# JSON から画像 URL リストを生成 (絶対→相対に書き換えつつ)
grep -oE '"url":"dataset/001/[^"]+\.png"' dataset/001.json \
  | sed 's|"url":"|https://dev.2sc1815j.net/soan/|;s|"$||' \
  > /tmp/soan_image_urls.txt

# xargs -P 5 で並列ダウンロード
cat /tmp/soan_image_urls.txt | xargs -P 5 -I {} bash -c '
  out="dataset/001/$(basename "$1")"
  [[ -s "$out" ]] && exit 0
  curl -sL --compressed --max-time 30 "$1" -o "$out"
' _ {}

5 並列で 30 分前後で 36,869 枚 (約 186 MB) を取得できました。Wayback ではなく生きている第三者ミラーを経由する判断は、対象の規模次第で割り切る必要があります。

なお、JSON 中の画像 URL は元の絶対 URL のままにすると CODH を引きに行ってしまうので、ホスト名 https://codh.rois.ac.jp/soan/ を取り除いて相対化しておきます。

import json, re
with open('demo_001.json') as f:
    d = json.load(f)
for item in d['data']:
    item['url'] = re.sub(r'^https://codh\.rois\.ac\.jp/soan/', '', item['url'])
with open('codh-mirror/soan/dataset/001.json', 'w') as f:
    json.dump(d, f, ensure_ascii=False, separators=(',', ':'))

これで JSON は dataset/001/xxx.png の相対パス参照になり、自サイト (GitHub Pages) の同パスから配信できます。

落とし穴 2: HTML から見えない実行時依存(kuromoji 辞書)でローディングが止まらない

UI / バンドル / dataset / 画像を全部揃えてデプロイしたあと、ブラウザでアクセスすると 画面のローディングスピナーが止まらない という症状に遭遇しました。

curlindex.htmlsoan.bundle.min.jsdataset/001.json も全部 200 OK が返り、JSON も valid。それでも JS 側の初期化が終わらない。index.html を読み直しても <script> <link> に怪しい参照は無く、依存関係は満たしているように見えます。

正解は DevTools の Network パネルでした。kuromoji/dict/base.dat.gz ほか複数のリクエストが 404 で並んでいて、それが原因と一目で分かります。バンドルを覗くと:

let e;
if (isNode) e = i;
else try {
  e = new URL("../kuromoji/dict/", SoanSrc).href;
} catch (e) { v(e); ... }
e && (f.fn.spin && h && f(h).spin(),
  r.builder({ dicPath: e }).build(function(e, n) {
    f.fn.spin && h && f(h).spin(!1);
    ...
  }))

そあんは内部で kuromoji.js(純 JS の日本語形態素解析器)を使っており、バンドルに同梱されている kuromoji.builder({dicPath}).build(...)辞書ファイル群を ../kuromoji/dict/ から動的に取りに行くつくりになっています。$(h).spin() は辞書ロード完了で止まるので、辞書が 404 だと完了せず永久に回り続けます。

教訓は単純で、HTML / <script> / <link> だけ見ていても気付けない実行時依存があるということ。CSS が url() で画像を引いたり、JS が動的 URL でデータを取ったりと、依存解析を HTML だけで完結させると漏れます。Network パネルで 404 / pending を見るのが最短ルートです。

辞書ファイル本体(kuromoji-js 標準の 12 個、計 17.8 MB)は、CODH オリジナルサイトでは https://codh.rois.ac.jp/soan/kuromoji/dict/ 配下に置かれていましたが、Wayback はバイナリ .dat.gz を網羅的にはアーカイブしておらず CDX 検索でゼロ件でした。これも 36k PNG と同じく dev.2sc1815j.net の同名パスから取得して凌ぎました。

落とし穴 3: CSS の url(...) で参照される画像・フォントが大量に欠落

特に IIIF Curation Viewer 系のように、Bootstrap / jQuery UI / Leaflet / Leaflet.draw / Leaflet.fullscreen / FontAwesome / 等を組み合わせているツールでは、CSS の url(...) で参照される画像・フォントファイルが大量にあります。index.html(href|src)="..." を起点にした単純な依存解決では拾い切れません。

/* bootstrap.min.css */
@font-face {
  ...
  src: url(../fonts/glyphicons-halflings-regular.eot);
  src: url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),
       url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'), ...;
}
/* jquery-ui.css */
.ui-state-default { ... background-image: url("images/ui-icons_555555_256x240.png"); ... }
/* leaflet.css */
.leaflet-control-layers-toggle { background-image: url(images/layers.png); ... }

これらは CSS ファイルの相対位置を起点に解決する必要があるため、CSS ごとに url() を抽出 → CSS ファイルからの相対パスを正規化 → ダウンロード、というスクリプトを書きました。

# CSS から url() を抽出して、CSS ファイルの位置を起点に解決
find . -name "*.css" -print0 | while IFS= read -r -d '' css; do
  css_dir=$(dirname "$css" | sed 's|^\./||')
  grep -oE "url\([^)]+\)" "$css" | while read line; do
    raw=$(echo "$line" | sed -E "s|^url\(['\"]?||;s|['\"]?\)$||")
    case "$raw" in
      data:*|http://*|https://*|\#*) continue ;;
    esac
    raw_clean="${raw%%\?*}"; raw_clean="${raw_clean%%#*}"
    target="${css_dir:+$css_dir/}$raw_clean"
    target=$(python3 -c "import os.path,sys; print(os.path.normpath(sys.argv[1]))" "$target")
    [[ "$target" == /* || "$target" == ../* ]] && continue
    echo "$target"
  done
done | sort -u

このリストに対して Wayback id_ で再フェッチするのですが、.eot .woff 等のフォントは Wayback がアーカイブしていないことも多く、404 が頻出しました。代替策として:

  • Bootstrap glyphiconsLeaflet draw spritesheet など複数ツール共通のものは、すでに取得できているツール(IIIF Curation Viewer 等)の方からコピー
  • FontAwesome 5 webfontsLeaflet 1.6.0 markers など、配布物が広く CDN で生きているものは jsDelivr / unpkg から取得

を組み合わせました。

# Bootstrap fonts: ICV からコピー
cp -n iiif-curation-viewer/bootstrap/bootstrap/fonts/* iiif-curation-manager/bootstrap/bootstrap/fonts/

# FontAwesome 5: jsDelivr から
for f in fa-{brands-400,regular-400,solid-900}.{eot,svg,ttf,woff,woff2}; do
  curl -sL --compressed -o iiif-curation-board/fontawesome/webfonts/$f \
    "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/webfonts/$f"
done

# Leaflet 1.6.0 markers: unpkg から
for f in layers.png layers-2x.png marker-icon.png marker-icon-2x.png marker-shadow.png; do
  curl -sL --compressed -o iiif-curation-board/leaflet/leaflet-1.6.0/images/$f \
    "https://unpkg.com/leaflet@1.6.0/dist/images/$f"
done

落とし穴 4: Wayback の 000 連発(レート制限)

ツールあたり数十〜100 ファイル弱(IIIF Curation Viewer は約 86 ファイル)を順次取得していると、途中から HTTP 000(接続失敗)を返されるようになります。Wayback Machine 側のレート制限と思われます。

最初は sleep 無しで回していたら、ツール 1 つで 19 / 66 件しか取れず(途中で全部 000)、リトライしても直りませんでした。リクエスト間に 2〜3 秒のスリープを入れるだけで、ほぼ取りこぼし無しに完走できるようになります。

for asset in "${ASSETS[@]}"; do
  mkdir -p "$(dirname "$asset")"
  http_code=$(curl -sL --compressed --max-time 90 -w "%{http_code}" \
    "${BASE}/${asset}" -o "${asset}")
  if [[ "$http_code" != "200" ]]; then
    echo "FAIL ($http_code): $asset"
    rm -f "${asset}"
  fi
  sleep 3   # ← これがないと中盤から全部 000 になる
done

例外として、そあんの古活字 PNG 36,869 枚については個人サーバ(dev.2sc1815j.net)からの取得だったので、xargs -P 5 で並列ダウンロードを行いました(5 並列、max-time 30、合計 30 分前後)。利用先の都合だけで決めず、相手サーバの規模・レート制限にも合わせて並列度を選ぶのが大事です。

落とし穴 5: 認証・保存バックエンドは結局動かない

IIIF Curation Viewer / Manager / Editor / Board は、内部で Firebase 認証JSONkeeper(CODH 側が運用していたキュレーション JSON 保存用 Flask アプリ)に依存しています。本ミラーはあくまで静的アセットの暫定配信なので、以下は動きません。

  • Firebase 認証: バンドルに CODH の Firebase プロジェクト ID codh-81041、authDomain codh-81041.firebaseapp.com がハードコードされている。Firebase の認可済みドメインに nakamura196.github.io は登録されていないため、別ドメインからの sign-in ポップアップは auth/unauthorized-domain 系のエラーで閉じる
  • 保存(JSONkeeper): 認証が通っても、保存先 API のホストが CODH 配下で停止中

「では自分で Firebase プロジェクトを立てて authFirebase.js を差し替えればよいのでは?」という発想は技術的には正しいのですが、

  • ログインだけ通っても JSONkeeper を立てない限り保存できない(rois-codh/JSONkeeper を自前 VM / Cloud Run などにデプロイ+ Firebase の Admin SDK で連携する必要がある)
  • 暫定ミラーの趣旨を超えて運用コストが大きくなる

ため、本ミラーでは認証・保存周りはあえて整備していません。

逆に 読み取り専用の利用は問題なく動きます

# 既存の curation JSON を渡して Viewer で閲覧(認証不要)
https://nakamura196.github.io/codh-mirror/iiif-curation-viewer/?curation=<curation_json_url>

# Manifest と矩形領域を指定してハイライト表示(認証不要)
https://nakamura196.github.io/codh-mirror/iiif-curation-viewer/?manifest=<manifest_url>&canvas=<canvas_id>&xywh=<x,y,w,h>&xywh_highlight=border

このため、利用先サイトから「保存しない閲覧用」として参照する分には十分機能します。書き込み系の機能を残したい場合は、本ミラーではなく CODH のサービス再開を待つか、自分で完全な代替バックエンドを立てるか、というトレードオフになります。

README の方にも同等の注意書きを記載しています。

ライセンスと出典表示

ミラーに含まれる成果物の権利関係は一様ではないので、構成要素ごとに分けて表示しています。各ファイルの先頭コメントに含まれているライセンス・著作権表示はそのまま残しました。

構成要素由来ライセンス
vdiff / vdiff-seq / IIIF Curation 各種、そあんの UI・バンドル・index.js・dataset/001.jsonCODH 提供のソフトウェアMIT Copyright CODH(core contributor: Jun HOMMA(@2SC1815J))
そあんの古活字 PNG 36,869 枚(dataset/001/*.pngCODH「古活字データセット」(原資料は『徒然草』国立国会図書館蔵)データセットのライセンス(詳細ページは CODH 停止中で参照不可、サービス再開時に再確認)
そあんの kuromoji 辞書 12 ファイル(kuromoji/dict/*.dat.gzkuromoji.js プロジェクト同梱の標準辞書(原典は mecab-ipadic)Apache 2.0(kuromoji.js) / NAIST 独自ライセンス + ICOT Free Software 条項(mecab-ipadic 本体)

そあんの画像バイナリと kuromoji 辞書は、CODH 停止中の代替策として @2SC1815J 氏個人運用の dev.2sc1815j.net から取得しました。同氏のホストは中継配信であり、原権利者は CODH(画像)/ kuromoji.js プロジェクト(辞書)です。

なお、dev.2sc1815j.net で公開されている Soan Pro バンドル(Copyright 2023 Jun HOMMA)には明示的な再配布条件が見当たらないため、Pro バンドル自体は本ミラーに含めていません。CODH デモ版の MIT ライセンスのバンドルだけを使う構成にしています。

おわりに

配信対象だけで 200 MB 強のミラーリポジトリを GitHub Pages に置くだけ、と書くと簡単に聞こえますが、実際にはツール毎に依存関係の解析が必要で、HTML だけ見ていても気付けない依存(kuromoji 辞書のような実行時動的ロードや、CSS の url() の連鎖参照)でハマる箇所が点々とありました。CODH のサービス再開後はリポジトリをアーカイブする予定です。本リポジトリは Issue / PR を募集する性質のものではありませんが、同様のミラー作業の参考になれば幸いです。

最後になりますが、本ミラーは、CODH の長年のオープン公開活動と、core contributor の Jun HOMMA(@2SC1815J)氏が個人ホストでも関連リソースを生かし続けてくださっていることがあって初めて成立しています。改めて感謝申し上げます。

参考