0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python×WordPress REST APIでブログ自動投稿パイプラインを組んだら文字化けした話と対処法

0
Posted at

はじめに

あるクライアントのブログ運用を自動化するため、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つありました。

  1. シェル変数展開時にエスケープが壊れる:本文に " や改行が含まれると、JSONとして不正な文字列になる
  2. エンコーディングが暗黙依存:ロケール設定によっては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. .htaccessAuthorization ヘッダーが落とされているケースが多いです。RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] を追加してください。

Q. 画像はどうする?
A. POST /wp-json/wp/v2/media で先にアップロードし、返ってきたIDを featured_media に指定します。

Q. 投稿が重複する
A. リトライ時の二重投稿対策として、タイトルやスラッグで既存記事を検索してから投稿する冪等チェックを入れると安全です。

まとめ

  • curl+シェル変数のJSON組み立ては壊れやすい。requestsjson= 引数でUTF-8シリアライズを任せる
  • Markdownのまま投稿せず、必ずHTML変換レイヤーを挟む
  • いきなり公開せず draft で止め、人間の確認を1回挟む

自動化は「最初から完璧に動かす」のではなく、動かしてからログとレスポンスを見て直すのが鉄則だと改めて実感しました。皆さんが自動化で最初にハマったポイントもぜひコメントで教えてください。


この記事を書いた人

BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。

GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。

👉 業務自動化サービス — 詳細・お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?