あとは、今月書いたブログを1つ移行してみる。

とりあえず、引数でURLを渡すとキャプチャするシェルスクリプトをAIに書いてもらう。
% vi fetch_fanbox.sh
#!/bin/zsh
set -euo pipefail
if [ $# -lt 1 ]; then
echo "使い方: $0 URL" >&2
exit 1
fi
URL="$1"
BASE_DIR="blog_temp"
mkdir -p "$BASE_DIR"
# URL末尾から簡単なスラッグを作る(例: .../10806321 → 10806321)
LAST_PART="${URL%%\?*}"
LAST_PART="${LAST_PART%/}"
LAST_PART="${LAST_PART##*/}"
SLUG=$(echo "$LAST_PART" | tr -cd '[:alnum:]_-')
[ -z "$SLUG" ] && SLUG="page"
POST_DIR="${BASE_DIR}/${SLUG}"
HTML_FILE="${POST_DIR}/page.html"
IMG_DIR="${POST_DIR}/images"
mkdir -p "$POST_DIR" "$IMG_DIR"
echo "Fetching HTML from $URL"
curl -sSL "$URL" -o "$HTML_FILE"
# ベースURL(スキーム+ホスト)
SCHEME_HOST=$(printf '%s\n' "$URL" | sed -E 's|(https?://[^/]+).*|\1|')
# ディレクトリ部分(相対パス用)
BASE_PATH="${URL%%\?*}"
BASE_PATH="${BASE_PATH%/*}"
echo "Extracting image URLs..."
TMP_IMG_LIST=$(mktemp)
# <img ... src="..."> から src だけ抜き出し、重複排除
grep -oiE '<img[^>]+src="[^"]+"' "$HTML_FILE" \
| sed -E 's/.*src="([^"]+)".*/\1/' \
| sort -u > "$TMP_IMG_LIST"
if ! [ -s "$TMP_IMG_LIST" ]; then
echo "No <img> tags found."
echo "HTML: $HTML_FILE"
exit 0
fi
i=1
while IFS= read -r SRC; do
# data: や空などはスキップ
if [ -z "$SRC" ] || [[ "$SRC" == data:* ]]; then
echo "Skip data URI image"
continue
fi
# フルURL組み立て
case "$SRC" in
http://*|https://*)
FULL="$SRC"
;;
//* )
# //example.com/path のような形式
FULL="https:${SRC}"
;;
/*)
# /path のような絶対パス
FULL="${SCHEME_HOST}${SRC}"
;;
*)
# 相対パス
FULL="${BASE_PATH}/${SRC}"
;;
esac
FNAME=$(basename "${FULL%%\?*}")
OUT_FILE="${IMG_DIR}/$(printf '%03d-%s' "$i" "$FNAME")"
i=$((i+1))
echo "Downloading image: $FULL -> $OUT_FILE"
curl -sSL "$FULL" -o "$OUT_FILE" || echo " (failed)"
done < "$TMP_IMG_LIST"
rm -f "$TMP_IMG_LIST"
echo "Done."
echo "HTML: $HTML_FILE"
echo "Images: $IMG_DIR"
試す。
% chmod +x fetch_fanbox.sh
% ./fetch_fanbox.sh https://www.fanbox.cc/@kinneko/posts/10806321
Fetching HTML from https://www.fanbox.cc/@kinneko/posts/10806321
Extracting image URLs...
画像がないよ。というか、page.htmlが404画面だよ。
% find blog_temp
blog_temp
blog_temp/10806321
blog_temp/10806321/page.html
blog_temp/10806321/images
こっちなら404ではないものが取れた。
% ./fetch_fanbox.sh https://kinneko.fanbox.cc/posts/10891201
けど、取得したのは、ヘッダ情報くらいで、コンテンツの内容はapi.fanbox.ccを叩いて呼び出す方式のようだ。これはcurlでは無理ね。 本文テキストや本文中の画像は、api.fanbox.cc/post.info?postId=... から JSON で返ってくる。 APIを叩くにはFANBOXSESSIDクッキーが必要で、ブラウザからクッキーを抜いて curl -b 'FANBOXSESSID=...' するような仕掛けが要る。
シェル芸でやると、ゴリゴリになってしまうので、面倒なので、Pythonでやることにする。
% mkdir fetchFanbox
% cd fetchFanbox
% uv init
Initialized project `fetchfanbox`
% uv add requests
Using CPython 3.13.7
Creating virtual environment at: .venv
Resolved 6 packages in 291ms
Prepared 5 packages in 149ms
Installed 5 packages in 7ms
+ certifi==2025.11.12
+ charset-normalizer==3.4.4
+ idna==3.11
+ requests==2.32.5
+ urllib3==2.5.0
idを渡して取得する。
% vi fanbox_post_info.py
#!/usr/bin/env python3
import os
import sys
import json
import requests
def get_fanbox_session() -> str:
"""
環境変数 FANBOXSESSID からセッションIDを取得。
"""
sess = os.environ.get("FANBOXSESSID")
if not sess:
raise RuntimeError(
"環境変数 FANBOXSESSID が設定されていません。\n"
"ブラウザのCookieから FANBOXSESSID の値をコピーして、\n"
" export FANBOXSESSID='...'\n"
"のように設定してください。"
)
return sess
def fetch_post_info(post_id: str, session_id: str) -> dict:
"""
FANBOX API post.info から JSON を取得する。
"""
url = "https://api.fanbox.cc/post.info"
params = {"postId": post_id}
headers = {
"Origin": "https://www.fanbox.cc",
"Referer": "https://www.fanbox.cc/",
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) "
"Gecko/20100101 Firefox/123.0"
),
"Accept": "application/json, text/plain, */*",
}
cookies = {
"FANBOXSESSID": session_id,
}
resp = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=15)
print(f"HTTP {resp.status_code} {resp.reason}", file=sys.stderr)
if resp.status_code != 200:
raise RuntimeError(
f"API呼び出しに失敗しました: {resp.status_code}\n{resp.text[:500]}"
)
return resp.json()
def main():
if len(sys.argv) != 2:
print("使い方: uv run fanbox_post_info.py POST_ID", file=sys.stderr)
print("例: uv run fanbox_post_info.py 10891201", file=sys.stderr)
sys.exit(1)
post_id = sys.argv[1].strip()
if not post_id.isdigit():
print(f"postId が数値ではありません: {post_id}", file=sys.stderr)
sys.exit(1)
print(f"postId = {post_id}", file=sys.stderr)
session_id = get_fanbox_session()
data = fetch_post_info(post_id, session_id)
out_name = f"post_{post_id}.json"
with open(out_name, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"JSONを書き出しました: {out_name}")
print(json.dumps(data, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
FANBOXSESSIDクッキーは、firefoxのGUIでは表示できないみたいだな。開発ツールのストレージからコピーする。
% export FANBOXSESSID='31349 227_JkwgeqEDQ1CSUhaAzBAWiPQ8fWP6Cm5c'
実行。エラーだな...
% uv run fanbox_post_info.py 10806321
postId = 10806321
HTTP 403 Forbidden
Traceback (most recent call last):
File "/Users/kinneko/Documents/fanbox-zola/fetchFanbox/fanbox_post_info.py", line 80, in <module>
main()
~~~~^^
File "/Users/kinneko/Documents/fanbox-zola/fetchFanbox/fanbox_post_info.py", line 69, in main
data = fetch_post_info(post_id, session_id)
File "/Users/kinneko/Documents/fanbox-zola/fetchFanbox/fanbox_post_info.py", line 48, in fetch_post_info
raise RuntimeError(
f"API呼び出しに失敗しました: {resp.status_code}\n{resp.text[:500]}"
)
RuntimeError: API呼び出しに失敗しました: 403
<html><head><title>FANBOX</title><meta charset="UTF-8"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta name="robots" content="noindex, nofollow"><meta name="viewport" content="width=device-width,initial-scale=1"><style>@charset "UTF-8";
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monosp
ブラウザからでないと厳しいようだ。自動取得できないか...
残念だけど、手動コピーをボチボチやっていくか...
リンク先が別タブで開かないのは不便。[markdown]に追記する。
% vi config.toml
[markdown]
external_links_target_blank = true
external_links_no_follow = true
external_links_no_referrer = true
こんな風にパースされる。
<a href="https://example.com/"
target="_blank"
rel="nofollow noreferrer noopener">
no_followは、SEO的に「被リンクとして評価してほしくない」リンクに付ける属性なので、今回はいらない。外しておくか。 no_referrerは、リンク先サイトに「どのページから来たのか(Referer)」を送らないようブラウザに指示するもの。これは付いていてもいいかな。
別タブで開くようになった。
画像は、imagesとかstaticの下に入れるみたいだけど、整理に困るので、contentに日付のディレクトリを掘って置きたい。しかし、そういう構成だと画像がZolaに認識されない感じだな... 画像はstatic以下で扱うしかないのか...
というわけで、こんな感じになった。

descriptionの中で改行したい。 brタグ入れたり、これやってみたけど、ダメだった。無理っぽいね。
description = """
THINKLETやUSB HUB経由で使うために、
短いType-Cケーブルを探したメモです。
いろいろ買って試した結果を書いています。
"""
定義はこれっぽい。
% grep -R post-description ./*
./themes/andromeda/templates/section.html: <span class="post-description">{{ page.description | safe }}</span>
post-description-multilineを追加する。
% vi ./themes/andromeda/templates/section.html:
<p class="post-description post-description-multiline">
{{ page.description }}
</p>
cssはこのあたり。publicはserveが作っているテンポラリなので、次にビルドすると消える。
% find ./* | grep ".css"
./public/main.css
./themes/andromeda/sass/_index.scss
./themes/andromeda/sass/_colors.scss
./themes/andromeda/sass/main.scss
./themes/andromeda/sass/_footer.scss
./themes/andromeda/sass/_sitewide.scss
./themes/andromeda/sass/_page.scss
_custom.scss を新規作成する。ついでに、本文のフォントをもう少し大きくしたいので、その設定も入れる。
% vi ./themes/andromeda/sass/_custom.scss
// themes/andromeda/sass/_custom.scss
.post-description-multiline {
white-space: pre-line;
}
article {
font-size: 1.1rem;
line-height: 1.9;
}
main.scssに読み込むように設定する。
% vi ./themes/andromeda/sass/main.scss
@import "custom";
serveを再起動してみる。反映されない...
brを強制するのもダメだわ。
% vi ./themes/andromeda/templates/section.html
<span class="post-description post-description-multiline">{{ page.description | replace(from="\n", to="<br>") | safe }}</span>
本文のフォントサイズのほうは、body-containerのほうらしい。 こちらはこれで反映された。
% vi ./themes/andromeda/sass/_custom.scss
.body-container p {
font-size: 1.3rem;
line-height: 1.9;
}

HTMLを見てみたけど、descriptionには、改行入っているけど、brはないね。
<span class="page-description lozad" data-loaded="true">THINKLETやUSB HUB経由で使うために、
短いType-Cケーブルを探したメモです。
いろいろ買って試した結果を書いています。
</span>
どうやら、white-space: normalが有効で、改行できていないようだ。
% vi ./themes/andromeda/sass/_custom.scss
.page-description {
white-space: pre-line;
}
できたできた。

自動でできないのはつらいな...
オリジナル投稿: Zola使ってみる -3-|kinneko|pixivFANBOX
https://www.fanbox.cc/@kinneko/posts/10950121



