/var/www/yatta47.log

/var/www/yatta47.log

やったのログ置場です。スクラップみたいな短編が多いかと。

はてなブログAtomPub: PUTで予約時間を変えたらカテゴリが消えた原因と対策

はてなブログのAtomPub APIで予約投稿の時間だけ変えたら、カテゴリが1つに減って本文も古い版に巻き戻った。原因はPUTが「部分更新」ではなく「全体置換」だから。安全に時間だけ変えるには、GETで最新XMLを取得→<updated>だけ差し替え→PUTする。

何が起きたか

はてなブログの予約投稿、公開タイミングをずらしたくなったんですよ。AtomPub APIで <updated> を書き換えてPUTすればいいかなと。

やってみたら200 OKが返ってきた。成功したと思ってブログを開いたら、カテゴリが3つあったはずなのに1つに減ってる。しかもWebUIで加筆した本文が、ローカルに保存してあった古い版で上書きされてた。

「時間を変えただけ」のつもりが、記事の中身まで巻き戻ってた。

原因

AtomPub(RFC 5023)のPUTは「部分更新」ではなく「リソース全体の置換」です。

RFC 5023でも「クライアントは意図的に変更していないメタデータを保持すべき(SHOULD)」と明記されています(Section 9.3)。

ローカルでXMLを組み立ててPUTすると、こうなります:

ローカルの状態:
  title: "記事タイトル"
  content: ローカルに保存された本文
  category: ["タグA"]         ← 1つだけ
  updated: "2026-03-20T09:00" ← 変更したい値

リモートの状態(WebUIで編集済み):
  title: "記事タイトル(修正済み)"
  content: WebUIで加筆した本文
  category: ["タグA", "タグB", "タグC"]
  updated: "2026-03-18T09:00"

PUT結果: 200 OK ... だがリモートがローカルで上書き
  → カテゴリが3つ→1つに
  → 加筆した本文が消える

200が返ってくるので一見成功に見える。壊れたことに気づくのはブログを開いたとき。

400エラーで止まってくれるならまだいいけど、「成功するけど中身が壊れている」パターンが厄介です。

問題を正しく分解すると:

  1. ローカルで組み立てたXMLが、リモートの最新状態を反映していない
  2. PUTは送信したXML全体でリソースを置き換えるので、古い情報が「正」として上書きされる

「変更手段」の問題ではなく「変更のベースライン」の問題です。

対策: GET→差替→PUTパターン

リモートの最新XMLをGETして、変えたい箇所だけ差し替えて、そのままPUTする。

# 1) リモートの最新Entryを取得
ENTRY_XML=$(curl -s -u "${HATENA_USER}:${HATENA_API_KEY}" \
  "https://blog.hatena.ne.jp/${HATENA_USER}/${HATENA_BLOG_DOMAIN}/atom/entry/${ENTRY_ID}")

# 2) <updated> の値だけ差し替え
NEW_TIME="2026-03-20T09:00:00+09:00"
UPDATED_XML=$(echo "$ENTRY_XML" | sed "s|<updated>[^<]*</updated>|<updated>${NEW_TIME}</updated>|")

# 3) そのままPUT
echo "$UPDATED_XML" | curl -s -X PUT \
  -H "Content-Type: application/atom+xml;type=entry" \
  -u "${HATENA_USER}:${HATENA_API_KEY}" \
  --data-binary @- \
  "https://blog.hatena.ne.jp/${HATENA_USER}/${HATENA_BLOG_DOMAIN}/atom/entry/${ENTRY_ID}"

ポイントは「ローカルのファイルからXMLを組み立てない」こと。リモートから取得したXMLをベースにするから、WebUIで編集した内容もカテゴリも全部保持されます。

なお、GETしたXMLに <hatenablog:scheduled>yes</hatenablog:scheduled> が含まれていない場合(古い記事など)は、PUTするXMLに追加が必要です。

これがないと予約状態が解除される可能性があります。

注意: 公開済み記事をrescheduleしない

もう1つ見落としやすい罠があります。

公開済み記事(app:draft = no)に対して <updated> を未来日時に変えてPUTすると、記事が app:draft = yes に戻ります。公開中の記事が非公開になる。

対策として、GETした時点で app:draft=yes であることを確認してから処理を進めます。

# ガード: 下書き状態でなければ中断
if ! echo "$ENTRY_XML" | grep -qE '<app:draft>\s*yes\s*</app:draft>'; then
  echo "ERROR: この記事は公開済みです。rescheduleすると非公開に戻ります。" >&2
  exit 1
fi

まとめ

AtomPub APIのPUTは全体置換。「1箇所だけ変えたい」ときでも、ベースラインはリモートの最新状態でなければなりません。GET→差替→PUTを定型パターンにしておくと、「時間だけ変えたのに本文が消えた」類の事故は構造的に起きなくなります。

参考