When you want to bulk unpublish old articles after migrating your Hatena Blog articles to another site.

Important Note: You Cannot Revert to Draft

With the Hatena Blog AtomPub API, you cannot revert published articles back to draft. Sending <app:draft>yes</app:draft> via a PUT request results in a 400 Cannot Change into Draft error.

Therefore, there are two approaches:

Method 1: Replace the Article Body with “This Article Has Moved”

You can rewrite the article’s <content> using the AtomPub API’s PUT method.

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">This article has moved to {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)

Method 2: Delete Articles Manually from the Hatena Blog Admin Panel

If the number of articles is small, you can manually delete them from the “Manage Articles” section of the admin panel. However, since there is no bulk selection feature, this is not suitable for a large number of articles.

How to Obtain the API Key

  1. Log in to the Hatena Blog admin panel
  2. Go to “Settings” -> “Advanced Settings”
  3. The API key is displayed in the “AtomPub” section at the bottom of the page

Summary

MethodAdvantageDisadvantage
Replace body textURLs remain, allowing redirection from old linksArticles themselves remain
Delete articlesCompletely removedOld links return 404
Close the blog itselfSimplest approachEverything is deleted

When migrating sites, it is recommended to process the old site after the new site has been indexed by Google.