0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第2回:WordPress REST APIで記事100本をCSVにエクスポートする|Codex CLIとWordPress REST APIで記事整理自動化

0
Posted at

はじめに

前回は、Codex CLIを使ってWordPress整理用のローカル環境を作りました。

準備したものは、主に以下です。

  • Codex CLI
  • Python仮想環境
  • .env
  • WordPressアプリケーションパスワード
  • AGENTS.md
  • scripts/data/ ディレクトリ

今回は、その続きです。

WordPress REST APIを使って、既存投稿を取得し、CSVとJSONにエクスポートします。

やりたいことはシンプルです。

WordPress管理画面で1記事ずつ見るのではなく、記事100本をCSVとして一覧化したい。

これができると、次のような作業が一気にやりやすくなります。

  • カテゴリー分類
  • タグ整理
  • slugの見直し
  • 記事タイトルの棚卸し
  • 公開済み記事の一覧確認
  • 重複記事や似た記事の確認

WordPressの記事が、管理画面上の「投稿一覧」から、手元で扱える「データ」になります。

今回作るもの

今回作るファイルは、主に2つです。

scripts/
  wp_client.py
  export_posts.py

役割は以下です。

wp_client.py
  WordPress REST APIを叩くための共通クライアント

export_posts.py
  WordPressの投稿を取得してCSV/JSONに出力するスクリプト

出力先は以下です。

data/
  posts_export.csv
  posts_export.json

前提

作業ディレクトリは以下です。

cd ~/wp-codex-tools
source .venv/bin/activate

.env には、以下のような情報が入っています。

WP_BASE_URL=https://www.example.com
WP_USERNAME=your_wordpress_username
WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx

実際には、自分のWordPressサイトのURL、ユーザー名、アプリケーションパスワードに置き換えます。

今回使うWordPress REST APIのベースURLは、以下の形になります。

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

まずCodexに指示する

Codexを起動します。

codex

そして、以下のように指示しました。

このリポジトリで、WordPress REST APIを使って既存投稿を取得するPythonツールを作ってください。

前提:
- 認証情報は .env にあります。
- .env には WP_BASE_URL, WP_USERNAME, WP_APP_PASSWORD があります。
- WordPress REST API Base URL は WP_BASE_URL + /wp-json/wp/v2 です。

まず作るファイル:
- scripts/wp_client.py
- scripts/export_posts.py

実装内容:
1. scripts/wp_client.py
   - .env を読み込む
   - WP_BASE_URL, WP_USERNAME, WP_APP_PASSWORD を取得する
   - Basic認証でWordPress REST APIを叩く
   - 共通の get/post 関数を用意する
   - 認証情報をログに出さない

2. scripts/export_posts.py
   - /wp/v2/posts から投稿を取得する
   - ページネーションに対応する
   - --limit N で取得件数を制限できる
   - --all で全件取得できる
   - data/posts_export.csv と data/posts_export.json に保存する

取得項目:
- post_id
- title
- slug
- date
- status
- link
- excerpt_text
- content_text の先頭2000文字
- categories のID配列
- tags のID配列

注意:
- WordPressへの更新はまだ実装しない
- 削除系APIは実装しない
- .env の中身は絶対に表示しない
- WP_APP_PASSWORDをログに出さない
- APIエラー時はHTTPステータスとエラー概要だけ表示する

ポイントは、最初から「更新」はさせないことです。

今回は取得だけです。

AIエージェントに本番環境を触らせる場合、まずは読み取り専用の処理から始めるのが安全だと思っています。

wp_client.pyの役割

wp_client.py は、WordPress REST APIを叩くための共通部品です。

イメージとしては、以下のような役割です。

import os
import requests
from dotenv import load_dotenv

load_dotenv()

WP_BASE_URL = os.environ["WP_BASE_URL"].rstrip("/")
WP_USERNAME = os.environ["WP_USERNAME"]
WP_APP_PASSWORD = os.environ["WP_APP_PASSWORD"]

API_BASE = f"{WP_BASE_URL}/wp-json/wp/v2"

session = requests.Session()
session.auth = (WP_USERNAME, WP_APP_PASSWORD)

def get(path, params=None):
    url = f"{API_BASE}{path}"
    response = session.get(url, params=params, timeout=30)
    response.raise_for_status()
    return response

def post(path, json=None):
    url = f"{API_BASE}{path}"
    response = session.post(url, json=json, timeout=30)
    response.raise_for_status()
    return response

実際には、Codexにエラーハンドリングも含めて実装してもらいました。

ここで重要なのは、.env の値をログに出さないことです。

特に WP_APP_PASSWORD は絶対に表示しないようにします。

export_posts.pyで投稿を取得する

次に、export_posts.py で投稿一覧を取得します。

WordPress REST APIでは、投稿一覧は以下のエンドポイントから取得できます。

/wp-json/wp/v2/posts

ただし、投稿数が多い場合、一度に全部は取れません。

そのため、ページネーション対応が必要です。

イメージとしては、以下のようなパラメータを使います。

per_page=100
page=1

1ページ目、2ページ目、3ページ目……と順番に取得していき、記事がなくなるまで繰り返します。

Codexには、このページネーション処理も含めて実装してもらいました。

まず3件だけ取得する

いきなり全件取得する前に、まず3件だけ取得します。

python scripts/export_posts.py --limit 3

成功すると、以下のような出力になります。

Connected to WordPress.
Fetched 3 posts.
Saved: data/posts_export.csv
Saved: data/posts_export.json

この時点では、WordPress側には何も変更を加えていません。

取得して、手元に保存しているだけです。

出力ファイルを確認する

ファイルができたか確認します。

ls -lh data/posts_export.csv data/posts_export.json

CSVの先頭を見ます。

head -n 5 data/posts_export.csv

または、Pythonで中身を軽く確認します。

python - <<'PY'
import pandas as pd

df = pd.read_csv("data/posts_export.csv")
print(df.head())
print(f"件数: {len(df)}")
print(df.columns.tolist())
PY

想定している列は以下です。

post_id
title
slug
date
status
link
excerpt_text
content_text
categories
tags

これで、WordPressの記事がCSVとして手元に出てきました。

全件取得する

3件取得できたら、全件取得します。

python scripts/export_posts.py --all

100記事程度であれば、それほど時間はかかりません。

取得後に件数を確認します。

python - <<'PY'
import pandas as pd

df = pd.read_csv("data/posts_export.csv")
print(f"取得件数: {len(df)}")
print(df[["post_id", "title", "slug", "status"]].head(10))
PY

記事数が想定と合っていればOKです。

JSONも出力する理由

今回はCSVだけでなく、JSONも出力しました。

data/posts_export.csv
data/posts_export.json

CSVは、人間が見たり、Excelやスプレッドシートで確認したりするのに向いています。

一方、JSONは、後続のスクリプトで扱いやすいです。

たとえば、後でカテゴリー分類やslug整理をするときに、JSONの方が扱いやすい場面があります。

今回は両方出しておくことにしました。

HTML本文をテキスト化する

WordPress REST APIで取得した本文は、HTMLを含んでいます。

そのままだと、カテゴリー分類やタグ推定には少し扱いづらいです。

そこで、beautifulsoup4 を使ってHTMLからテキストを取り出します。

イメージは以下です。

from bs4 import BeautifulSoup

def html_to_text(html):
    soup = BeautifulSoup(html or "", "html.parser")
    return soup.get_text("\n", strip=True)

今回のCSVでは、本文すべてではなく、先頭2000文字程度を content_text として保存することにしました。

理由は、カテゴリー分類の材料としては、記事本文の冒頭がかなり重要だからです。

また、全本文をCSVに入れると、ファイルが重くなったり見づらくなったりします。

取得する項目

今回取得した項目は以下です。

post_id

WordPress上の投稿IDです。

後でカテゴリーやタグを反映するときに使います。

title

記事タイトルです。

分類やslug作成では、タイトルをかなり重視します。

slug

現在のslugです。

後で長すぎるslugや日本語slugを見直すために使います。

date

投稿日です。

記事の時系列を確認するために使います。

status

公開状態です。

publishdraft などが入ります。

今回は基本的に公開済み記事を対象にしていますが、状態を確認できるようにしておきます。

link

現在のURLです。

slug変更時に、旧URLと新URLを対応させるためにも使えます。

excerpt_text

抜粋です。

カテゴリー分類の材料になります。

content_text

本文テキストの先頭部分です。

タイトルだけでは判断できない記事を分類するために使います。

categories

現在ついているカテゴリーIDです。

既存分類を参考にするために保存します。

tags

現在ついているタグIDです。

既存タグも参考情報として保存します。

実際にCSV化して感じたこと

WordPress管理画面で記事を見るのと、CSVで記事一覧を見るのでは、かなり感覚が違いました。

管理画面では、どうしても1記事ずつ見ることになります。

でもCSVにすると、記事群全体を一覧できます。

たとえば、以下のようなことが見えてきます。

  • slugがやたら長い記事
  • 同じようなテーマの記事
  • カテゴリーが未整理の記事
  • タイトルの粒度がそろっていない記事
  • 旅行記なのかノウハウなのか曖昧な記事

WordPressの記事が、急に「分析対象のデータ」になった感じがありました。

これはかなり大きいです。

管理画面で100記事をポチポチするのは大変ですが、CSVなら一気に俯瞰できます。

よくありそうなエラー

401 Unauthorized

認証に失敗しています。

確認することは以下です。

- WP_USERNAME が正しいか
- WP_APP_PASSWORD が正しいか
- アプリケーションパスワードを使っているか
- 通常のログインパスワードを入れていないか

WordPressのユーザー名がメールアドレスではない場合もあります。

その場合は、WordPress管理画面で実際のユーザー名を確認します。

403 Forbidden

ユーザー権限が足りない可能性があります。

投稿を取得するだけなら問題ないことも多いですが、下書きや非公開記事まで扱う場合は権限が必要です。

404 Not Found

WP_BASE_URL が間違っている可能性があります。

以下のURLにブラウザでアクセスして、REST APIが見えるか確認します。

https://www.example.com/wp-json/wp/v2/posts?per_page=1

公開投稿がJSONで返ってくれば、REST API自体は動いています。

CSVが文字化けする

日本語を扱う場合、CSVの文字コードに注意が必要です。

Excelで開くことも考えて、CSVは utf-8-sig で出力するようにしました。

df.to_csv(output_path, index=False, encoding="utf-8-sig")

この段階ではまだ更新しない

今回の記事でやったのは、あくまで取得だけです。

WordPressへの更新はしていません。

これは意図的です。

いきなりカテゴリーやタグを反映するのではなく、まず記事一覧を手元に持ってきます。

その上で、

  1. CSVを眺める
  2. 分類案を作る
  3. 人間が確認する
  4. dry-runする
  5. 少数だけ反映する
  6. 問題なければ全件反映する

という順番にします。

本番環境に対してAIエージェントを使うなら、この段階分けはかなり重要だと思います。

今回できたこと

今回できたことは以下です。

  • WordPress REST APIで投稿一覧を取得した
  • ページネーションに対応した
  • 3件だけ取得して疎通確認した
  • 全件取得してCSV/JSONに出力した
  • タイトル、slug、本文、カテゴリー、タグを一覧化した
  • WordPressの記事100本を「データ」として扱えるようにした

ここまでできると、次の作業が一気にやりやすくなります。

次回

次回は、この posts_export.csv を使って、記事100本のカテゴリー分類案を作ります。

やることは以下です。

  • カテゴリーを7つに整理する
  • classify_posts.py を作る
  • 記事ごとに main_category を決める
  • タグ候補を3〜8個出す
  • confidence を付ける
  • categorized_posts.csv を出力する
  • 人間は confidence=low を中心に確認する

WordPressの記事がCSVになったことで、次は「100記事をどういう本棚に並べるか」を考えられるようになります。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?