開発記録(第10回)。AI に日本株の売買判断をさせる投資システム investTool を個人で開発しています。全体像は目次記事にまとめています。

- WordPress の REST API とアプリケーションパスワードで、Python から記事を下書きで投稿しています(プラグインは不要)
- 投稿に使うユーザーは、管理者ではなく編集者の権限にしています
- 画像を送ると5枚中1枚だけサーバーの WAF に 403 で弾かれ、multipart/form-data で送ることで通りました
動作環境
| 項目 | 内容 |
|---|---|
| サーバー | ConoHa WING(共用のレンタルサーバー) |
| WordPress | アプリケーションパスワードは WordPress 5.6 以降の標準機能 |
| 送る側 | Python 3.12。確認用のスクリプトは標準ライブラリだけで書いた |
やりたかったこと
前回書いたとおり、このブログは AI が書いた記事を API で下書きとして投稿し、人が確認して公開します。そのために、Python から次のことができる必要がありました。
| やりたいこと | エンドポイント |
|---|---|
| 記事を下書きで作る・更新する | POST /wp-json/wp/v2/posts |
| 画像をアップロードする | POST /wp-json/wp/v2/media |
| アイキャッチを付ける | 記事の featured_media に画像の ID を入れる |
手順
1. 先に6段階で疎通を確かめる
記事を作る仕組みを作り込む前に、サーバーで API が本当に使えるかを確かめるスクリプトを書きました。共用のサーバーでは、認証の情報がサーバーの手前で落とされたり、セキュリティの設定で API が止められたりすることがあるためです。

失敗した段階ごとに、考えられる原因と対処を表示するようにしました。たとえば、パーマリンクの設定が「基本」だと /wp-json/ が使えないため、その場合は設定の変更を案内します。
環境によっては、認証の情報(Authorization ヘッダー)がサーバーで落とされ、.htaccess に転送の設定を足す必要があります。ConoHa WING では、設定を足さずに6段階すべてが通りました。
2. 投稿用のユーザーは編集者にする
アプリケーションパスワードは、ユーザーごとに発行します。管理者のユーザーで発行すると、そのパスワードが漏れたときにサイトを乗っ取られます(プラグインの追加やユーザーの作成ができるため)。
そこで、投稿専用のユーザーを「編集者」の権限で作り、そのユーザーでパスワードを発行しました。編集者は記事と画像を扱えますが、サイトの設定は変えられません。投稿者(author)ではなく編集者にしたのは、タグを新しく作るのに編集者の権限が必要なためです。
パスワードはコードに書かず、.env から読みます。
3. 記事を下書きで投稿する
data = {
"title": title,
"content": html,
"slug": slug, # 必ず指定する
"status": "draft", # 自動では公開しない
"categories": [category_id],
"excerpt": excerpt,
"featured_media": eyecatch_id,
}
slug を指定しないと、WordPress はタイトルから日本語の URL を作ります。日本語の URL は、共有したときに %E5%B8%82… のような長い文字列になって読めなくなります。
すでに公開した記事を API で更新するとき(前後の回へのリンクを足すときなど)に status: draft を送ると、公開中の記事が下書きに戻ります。更新では status を送らないようにしています。
つまずいた点と解決
画像だけ、5枚中1枚が 403 で弾かれた
- 症状
- 画像を5枚アップロードしたところ、3枚目だけが HTTP 403 になった
- 原因
- サーバーの WAF(不正なアクセスを防ぐ仕組み)が、画像のバイト列を攻撃のパターンと誤って判定したとみられる
- 対処
- 本文にバイナリをそのまま載せる送り方をやめ、管理画面と同じ multipart/form-data で送る
疎通の確認では画像のアップロードも通っていたので、問題ないと考えていました。ところが本番の記事の図を5枚送ると、1・2枚目は通ったのに、3枚目だけが 403 になりました。
返ってきたのは WordPress の JSON ではなく、次のような HTML でした。
閲覧できません (Forbidden access)
……
Powered by (WAF の製品名)
WordPress のプラグインではなく、サーバー側の WAF が止めていました。送った画像は図の PNG で、コードも文字列も含んでいません。画像のバイト列が、たまたま攻撃のパターンに一致したと考えられます。

WordPress の REST API は、画像のバイナリを本文にそのまま載せ、ファイル名を Content-Disposition で渡す送り方を受け付けます。疎通の確認はこの方法でした。これを、ブラウザの管理画面と同じ multipart/form-data に変えたところ、同じ画像が通りました。
boundary = "----investToolBoundary"
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="file"; filename="{name}"\r\n'
f"Content-Type: image/png\r\n\r\n"
).encode() + png_bytes + f"\r\n--{boundary}--\r\n".encode()
headers = {"Content-Type": f"multipart/form-data; boundary={boundary}"}
画像しだいで起きる、たまにしか出ない種類の失敗でした。記事を自動で作るようになると、図は毎回変わります。確認で1回通ったことを頼りにせず、WAF に止められにくい送り方にそろえておくことにしました。
実際にどう使っているか
この連載の記事は、すべてこの方法で下書きとして投稿しています。記事の元は Markdown の文書で、投稿のときに HTML への変換、図のアップロード、アイキャッチの設定までをまとめて行います。
REST API での投稿そのものは、プラグインなしで簡単に動きました。つまずいたのは、サーバーの手前にある WAF と、公開済みの記事を下書きに戻さないといった運用の細部でした。
この記事の内容に近い書籍
※ Amazon のアソシエイトリンクを含みます。
- 『いちばんやさしいWordPressの教本 第7版』(石川栄和、大串肇、星野邦敏、Kindle版)
WordPress のサイト構築の入門書です。この記事で扱った投稿やユーザーの権限の、基本の部分と近い内容です
前回:記事を自動投稿するなら、WordPress のテーマ選びの基準が変わる|無料の Cocoon にした理由
次回:WordPress を立ち上げたら、ログイン名が外から見えていた|見えていた4つの経路と、確かめ方の落とし穴
当サイトは情報提供を目的としており、投資助言を行うものではありません。掲載している売買記録は仮想売買(ペーパートレード)であり、実際の取引の結果ではありません。投資判断はご自身の責任で行ってください。詳しくは免責事項をご覧ください。

