WordPress REST APIで外部プログラムから記事を自動投稿する方法

WordPress REST APIを利用すると、Pythonなどの外部プログラムから記事の作成・更新・公開を実行できます。社内システムで作成した原稿の登録、定期レポートの投稿、AIで生成した下書きの保存など、管理画面への転記作業を減らしたい場合に便利です。

この記事では、WordPress標準の「アプリケーションパスワード」を使い、Pythonから記事を投稿する手順を解説します。アイキャッチ画像の登録や二重投稿を防ぐ方法も含め、実務で使える形まで組み立てます。

WordPress REST APIによる投稿の仕組み

WordPressの投稿を操作するREST APIのエンドポイントは、通常次のURLです。

https://example.com/wp-json/wp/v2/posts

このURLへHTTPリクエストを送ることで、投稿の取得や作成ができます。主なHTTPメソッドは次のとおりです。

  • GET:投稿を取得する
  • POST:投稿を新規作成する
  • POSTまたはPUT:指定した投稿を更新する
  • DELETE:投稿を削除する

公開済みの記事一覧は認証なしでも取得できることがありますが、記事の作成や更新には認証が必要です。

事前に準備するもの

HTTPSでアクセスできるWordPress

認証情報を送信するため、本番環境ではHTTPSを使用してください。また、次のURLをブラウザで開き、JSON形式の情報が表示されることを確認します。

https://example.com/wp-json/

404エラーやセキュリティプラグインの警告が出る場合は、パーマリンク設定、Basic認証、WAF、REST APIの制限設定を確認します。

投稿権限を持つユーザー

投稿者、編集者、管理者など、記事を作成できる権限を持つユーザーを用意します。自動投稿専用のユーザーを作成し、必要最低限の権限だけを付与すると管理しやすくなります。

アプリケーションパスワードを発行する

WordPress 5.6以降では、外部アプリケーション向けの認証情報を管理画面から発行できます。

  1. WordPress管理画面で「ユーザー」から対象ユーザーを開きます。
  2. 「アプリケーションパスワード」まで移動します。
  3. 「自動投稿ツール」など識別しやすい名前を入力します。
  4. 「新しいアプリケーションパスワードを追加」を押します。
  5. 表示されたパスワードを安全な場所に保存します。

発行されたパスワードは画面を離れると再表示できません。通常のログインパスワードとは別物で、不要になった場合は個別に失効できます。

Pythonから記事を新規投稿する

ここではHTTP通信ライブラリのrequestsを使用します。未導入の場合は次のコマンドでインストールしてください。

pip install requests

最小構成の投稿プログラムは次のとおりです。

import requests

WP_URL = 'https://example.com'
WP_USER = 'api-user'
WP_APP_PASSWORD = 'xxxx xxxx xxxx xxxx xxxx xxxx'

endpoint = f'{WP_URL}/wp-json/wp/v2/posts'

payload = {
    'title': 'REST APIから投稿した記事',
    'content': '<h2>見出し</h2><p>外部プログラムから登録した本文です。</p>',
    'excerpt': 'REST APIによるテスト投稿です。',
    'status': 'draft'
}

response = requests.post(
    endpoint,
    auth=(WP_USER, WP_APP_PASSWORD),
    json=payload,
    timeout=30
)

if response.status_code == 201:
    post = response.json()
    print(f'投稿しました: ID={post['id']}')
    print(post['link'])
else:
    print(f'投稿に失敗しました: {response.status_code}')
    print(response.text)

statusをdraftにすると下書き、publishにすると即時公開になります。最初は下書きで送信し、本文、カテゴリー、画像などを管理画面で確認してから公開する運用が安全です。

よく使う投稿パラメータ

投稿時には、タイトルや本文以外にも次の項目を指定できます。

  • slug:投稿URLに使うスラッグ
  • status:draft、publish、futureなど
  • date:公開日時
  • categories:カテゴリーIDの配列
  • tags:タグIDの配列
  • featured_media:メディアID

カテゴリーとタグは名前ではなくIDを指定します。カテゴリー一覧は/wp-json/wp/v2/categories、タグ一覧は/wp-json/wp/v2/tagsから取得できます。

payload = {
    'title': '制作メモ',
    'content': '<p>記事本文です。</p>',
    'slug': 'production-note-2025',
    'status': 'draft',
    'categories': [3],
    'tags': [8, 12]
}

アイキャッチ画像を登録する

アイキャッチ画像は、先にメディアAPIへ画像をアップロードし、返されたメディアIDを投稿のfeatured_mediaへ指定します。

from pathlib import Path
import mimetypes
import requests

def upload_media(image_file):
    image_path = Path(image_file)
    mime_type = mimetypes.guess_type(image_path.name)[0] or 'image/jpeg'

    headers = {
        'Content-Disposition': f'attachment; filename={image_path.name}',
        'Content-Type': mime_type
    }

    with image_path.open('rb') as file:
        response = requests.post(
            f'{WP_URL}/wp-json/wp/v2/media',
            auth=(WP_USER, WP_APP_PASSWORD),
            headers=headers,
            data=file,
            timeout=60
        )

    response.raise_for_status()
    return response.json()['id']

media_id = upload_media('./eyecatch.jpg')

payload = {
    'title': '画像付きの記事',
    'content': '<p>アイキャッチ画像を設定した記事です。</p>',
    'status': 'draft',
    'featured_media': media_id
}

画像の容量上限は、PHPやWebサーバー、WordPress側の設定に影響されます。大きな画像を扱う場合は、アップロード前にリサイズや圧縮を行うと失敗を減らせます。

二重投稿を防いで更新にも対応する

定期実行するプログラムでは、通信後に処理が再実行されるなどの理由で同じ記事が複数作成されることがあります。記事ごとに一意のスラッグを決め、既存記事があれば更新する方式が実用的です。

def find_post_by_slug(slug):
    response = requests.get(
        f'{WP_URL}/wp-json/wp/v2/posts',
        auth=(WP_USER, WP_APP_PASSWORD),
        params={'slug': slug, 'status': 'any'},
        timeout=30
    )
    response.raise_for_status()
    posts = response.json()
    return posts[0] if posts else None

slug = 'daily-report-2025-01-15'
existing_post = find_post_by_slug(slug)

payload = {
    'title': '日次レポート',
    'content': '<p>集計結果を更新しました。</p>',
    'slug': slug,
    'status': 'draft'
}

if existing_post:
    endpoint = f'{WP_URL}/wp-json/wp/v2/posts/{existing_post['id']}'
else:
    endpoint = f'{WP_URL}/wp-json/wp/v2/posts'

response = requests.post(
    endpoint,
    auth=(WP_USER, WP_APP_PASSWORD),
    json=payload,
    timeout=30
)
response.raise_for_status()

より確実に管理する場合は、投稿成功時に返されるWordPressの投稿IDを外部システム側へ保存し、次回からそのIDを使って更新します。

認証情報は環境変数で管理する

ユーザー名やアプリケーションパスワードをソースコードへ直接書くと、Gitリポジトリへの誤登録につながります。本番運用では環境変数やシークレット管理機能を利用してください。

import os

WP_URL = os.environ['WP_URL']
WP_USER = os.environ['WP_USER']
WP_APP_PASSWORD = os.environ['WP_APP_PASSWORD']

GitHub Actionsを使う場合はSecrets、レンタルサーバーのcronで動かす場合はサーバー側の環境変数や、公開ディレクトリ外に置いた設定ファイルを利用します。ログへ認証情報やリクエストヘッダーを出力しないことも重要です。

エラー発生時に確認するポイント

401または403エラー

ユーザー名、アプリケーションパスワード、ユーザー権限を確認します。サーバーやセキュリティプラグインがAuthorizationヘッダーを削除している場合もあります。

404エラー

エンドポイントのURLとパーマリンク設定を確認します。WordPressがサブディレクトリに設置されている場合は、そのディレクトリをURLへ含めます。

400エラー

カテゴリーIDの誤り、公開日時の形式、指定できないステータスなど、送信データに問題がある可能性があります。レスポンス本文にはcodeやmessageが含まれるため、HTTPステータスだけでなく本文も記録してください。

ただし、記事本文や個人情報を含むレスポンスを長期間ログへ保存する場合は、閲覧権限と保存期間にも配慮が必要です。

まとめ

WordPress REST APIとアプリケーションパスワードを使うことで、外部プログラムから記事を作成・更新できます。基本的な流れは、専用ユーザーと認証情報を用意し、/wp-json/wp/v2/postsへJSONデータを送信する形です。

実運用では、最初は下書きとして登録する、スラッグや投稿IDで二重投稿を防ぐ、認証情報を環境変数で管理する、エラー本文を確認できるログを残す、といった設計も加えてください。まずは検証環境で1件の下書き投稿から試し、権限やセキュリティ設定を確認してから自動実行へ進めると安全です。

コメント

タイトルとURLをコピーしました