はじめに
あるクライアントのブログ運用を自動化するため、Pythonスクリプトで「記事生成 → WordPress自動投稿」のパイプラインを構築しました。しかし最初のテスト投稿で本文が文字化けし、さらにレイアウトが崩れるという二重のトラブルに遭遇。原因はJSONのエンコードミスと、Markdown/HTMLの混在でした。
本記事では、再現コードと修正後のコードを並べて、同じ轍を踏まないためのポイントをまとめます。
環境
| 項目 | バージョン |
|---|---|
| Python | 3.12 |
| requests | 2.32 |
| markdown | 3.6 |
| WordPress | 6.5(REST API v2) |
| 認証 | Application Passwords(WP標準機能) |
パイプラインの全体構成
記事生成(Markdownで出力)
↓
Markdown → HTML 変換
↓
WordPress REST API(POST /wp-json/wp/v2/posts)
↓
下書き保存 → 人間が最終確認して公開
ポイントは「いきなり公開しない」こと。status: draft で投稿し、人間の目を1回挟む設計にしています。
ハマりポイント1:日本語が文字化けする
最初に書いた失敗コード
当初はシェルスクリプトから curl で叩いていました。
curl -X POST "https://example.com/wp-json/wp/v2/posts" \
-u "admin:xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d "{\"title\": \"$TITLE\", \"content\": \"$CONTENT\", \"status\": \"draft\"}"
これで投稿すると、本文の日本語が ã�ã�®è¨ のような文字化けに。原因は2つありました。
-
シェル変数展開時にエスケープが壊れる:本文に
"や改行が含まれると、JSONとして不正な文字列になる - エンコーディングが暗黙依存:ロケール設定によってはUTF-8として送信されない
requestsライブラリで明示的にUTF-8指定
Pythonの requests に置き換え、json= 引数を使うことで解決しました。
import requests
WP_URL = "https://example.com/wp-json/wp/v2/posts"
AUTH = ("admin", "xxxx xxxx xxxx xxxx") # Application Password
payload = {
"title": "自動投稿テスト",
"content": html_body,
"status": "draft",
}
res = requests.post(
WP_URL,
auth=AUTH,
json=payload, # ← ここが重要。自動でUTF-8のJSONにシリアライズされる
headers={"Content-Type": "application/json; charset=utf-8"},
timeout=30,
)
res.raise_for_status()
print(res.json()["link"])
json=payload を使うと、requestsが内部で json.dumps() してUTF-8でエンコードしてくれるため、エスケープ漏れも文字化けも起きません。data=json.dumps(payload) と自前でやる場合は ensure_ascii=False の指定とエンコードを自分で管理する必要があり、ミスの温床になります。
curl と requests の比較
| 観点 | curl + シェル変数 | requests + json= |
|---|---|---|
| エスケープ処理 | 自前(壊れやすい) | 自動 |
| 文字コード | ロケール依存 | UTF-8固定 |
| エラーハンドリング | 終了コードのみ | 例外+レスポンス解析 |
| リトライ実装 | 困難 |
urllib3.Retryで容易 |
ハマりポイント2:レイアウトが崩れる
原因:MarkdownとHTMLタグの混在
記事生成の出力はMarkdownなのに、一部にHTMLタグが混ざった状態でそのまま content に渡していました。WordPressのブロックエディタはこれを正しく解釈できず、見出しが平文になったり、リストが崩れたりします。
対策:投稿前にMarkdown→HTML変換を挟む
import markdown
def md_to_html(md_text: str) -> str:
return markdown.markdown(
md_text,
extensions=["extra", "codehilite", "tables"],
)
html_body = md_to_html(generated_markdown)
extra 拡張でテーブルや脚注に対応、codehilite でコードブロックのシンタックスハイライト用クラスが付与されます。変換後のHTMLを渡すようにしたところ、レイアウト崩れは完全に解消しました。
つまづきがちなFAQ
Q. Application Passwordsで401が返る
A. .htaccess で Authorization ヘッダーが落とされているケースが多いです。RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] を追加してください。
Q. 画像はどうする?
A. POST /wp-json/wp/v2/media で先にアップロードし、返ってきたIDを featured_media に指定します。
Q. 投稿が重複する
A. リトライ時の二重投稿対策として、タイトルやスラッグで既存記事を検索してから投稿する冪等チェックを入れると安全です。
まとめ
-
curl+シェル変数のJSON組み立ては壊れやすい。requestsのjson=引数でUTF-8シリアライズを任せる - Markdownのまま投稿せず、必ずHTML変換レイヤーを挟む
- いきなり公開せず
draftで止め、人間の確認を1回挟む
自動化は「最初から完璧に動かす」のではなく、動かしてからログとレスポンスを見て直すのが鉄則だと改めて実感しました。皆さんが自動化で最初にハマったポイントもぜひコメントで教えてください。
この記事を書いた人
BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。
GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。
👉 業務自動化サービス — 詳細・お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中