はてなブログの記事を別サイトに移行した後、旧記事を一括で非公開にしたいケースがあります。

注意点:下書きには戻せない

はてなブログのAtomPub APIでは、公開済みの記事を下書き(draft)に戻すことはできません。PUTリクエストで <app:draft>yes</app:draft> を送ると 400 Cannot Change into Draft エラーになります。

そのため、以下の2つの方法があります。

方法1:記事本文を「移転しました」に書き換える

AtomPub APIのPUTで記事の <content> を書き換えることは可能です。

import requests
import xml.etree.ElementTree as ET
import time

HATENA_ID = "your_hatena_id"
BLOG_ID = "your_blog_id.hatenablog.com"
API_KEY = "your_api_key"
NEW_SITE_URL = "https://your-new-site.com"

ATOM_NS = "http://www.w3.org/2005/Atom"

def fetch_all_entries():
    entries = []
    url = f"https://blog.hatena.ne.jp/{HATENA_ID}/{BLOG_ID}/atom/entry"
    while url:
        resp = requests.get(url, auth=(HATENA_ID, API_KEY), timeout=30)
        resp.raise_for_status()
        root = ET.fromstring(resp.text)
        for entry in root.findall(f"{{{ATOM_NS}}}entry"):
            title_el = entry.find(f"{{{ATOM_NS}}}title")
            title = title_el.text or "" if title_el is not None else ""
            edit_link = entry.find(f"{{{ATOM_NS}}}link[@rel='edit']")
            edit_url = edit_link.get("href") if edit_link is not None else None
            if edit_url:
                entries.append({"title": title, "edit_url": edit_url})
        next_el = root.find(f"{{{ATOM_NS}}}link[@rel='next']")
        url = next_el.get("href") if next_el is not None else None
    return entries

def replace_content(entry):
    title = entry["title"]
    update_xml = f"""<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:app="http://www.w3.org/2007/app">
  <title>{title}</title>
  <content type="text/plain">この記事は {NEW_SITE_URL} に移転しました。</content>
</entry>"""
    resp = requests.put(
        entry["edit_url"],
        auth=(HATENA_ID, API_KEY),
        data=update_xml.encode("utf-8"),
        headers={"Content-Type": "application/atom+xml; charset=utf-8"},
        timeout=30,
    )
    return resp.status_code

entries = fetch_all_entries()
print(f"Found {len(entries)} entries")

for i, e in enumerate(entries):
    status = replace_content(e)
    print(f"[{i+1}/{len(entries)}] {status}: {e['title'][:50]}")
    time.sleep(0.5)

方法2:はてなブログの管理画面から一括削除

記事数が少なければ、管理画面の「記事の管理」から手動で削除する方法もあります。ただし一括選択機能がないため、大量の記事には向きません。

APIキーの取得方法

  1. はてなブログの管理画面にログイン
  2. 「設定」→「詳細設定」
  3. ページ下部の「AtomPub」セクションにAPIキーが表示されています

まとめ

方法メリットデメリット
本文書き換えURLが残るので旧リンクからの誘導が可能記事自体は残る
記事削除完全に消える旧リンクが404になる
ブログ自体を閉じる一番シンプル全てが消える

サイト移行時は、新サイトがGoogleにインデックスされてから旧サイトを処理するのがおすすめです。