WP REST APIを使って手元のMarkdown原稿をWordPressに投稿する (Python)

※当サイトは、アフィリエイト広告を利用しています

手間なく投稿したい

メモ書きは全部Markdownで保存しているので、ブログ記事の下書きもMarkdownで書いている。
日記のうち「これだ!」と思ったものをちょっと書き換えてブログ記事にする感じ。

Markdownで書いたものを投稿画面からそのまま貼り付けることもできるが、
どぉーしても投稿状態をもっと一元化して管理したいっていう欲求と、

  • (例によって)回線が激重すぎて投稿画面になかなか遷移できない
  • 投稿画面の「公開」を押してもずっと固まった状態になることがある
  • アイキャッチやスラッグをいちいち入れるのが面倒

などの面倒があって、WP REST APIを使って投稿することにした。

用意するもの

Application Passwordsから投稿用パスワードを取得

WordPressプラグインのApplication Passwordsを使って投稿用のパスワードを取得する(後で使うのでちゃんと手元にメモしておく)。

こちらの記事を参考にさせていただきました。
【WP REST API】Word Press APIのパスワード設定方法 | もじとばコム

投稿下書きを保存するディレクトリ(フォルダ)構成

まずはMarkdownメモをこんな感じでフォルダに入れる。

. 
├─done   (投稿し終わったファイル)
├─drafts  (下書き)
├─media  (画像とか)
└─stage  (書き終わって投稿前のファイル.このフォルダに後述のpost.pyを入れる)

下書きとなるMarkdown側の設定

ブログ下書きにするMarkdownファイルには下のようなyamlヘッダを先頭に書いておく。

  • テンプレートに入れるか、スニペット(定型文)に登録しておくととても楽。
  • タグIDカテゴリIDを控えておくと楽。

---
title:  # 表題
date:  # 日付
tags: ['0','1']
categories: ['2']
id:  # 初回投稿のときは空でOK
slug:  #パーマリンク
featured_media:  #アイキャッチ画像
# ここらへんに自分用メモ
# ...
---

### 本文
ここらへんに本文
...

Python

比較的簡単なプログラミング言語。今のところこれしか書けないのでこれで書く。
持っていなければここの「Downloads」からダウンロードしてインストールする。

pandoc

多種のドキュメントを形式変換できるプログラム。 持っていなければここからダウンロードしてインストールする。(インストーラを使うのが楽)

投稿用のプログラムを書く

  • 投稿については
    • yamlヘッダ付きのMarkdownファイルをpandocでhtmlに変換して投稿内容として読み込み
    • 元のMarkdownのyamlヘッダから投稿情報を作成
    • 投稿し終わった記事を別フォルダへ移動
  • 画像については
    • フォルダ内にある画像をとりあえず順にアップ

だいたい上のような処理で書いてみた。
この内容をメモ帳にコピペしてpost.pyという名前でstageフォルダに保存する。
【ここを直す】のところは適宜修正要。


import requests, os, shutil, subprocess, yaml, json
from datetime import date, datetime,time

def date_to_datetime(obj):
    #  dateからdatetimeに変換
    if isinstance(obj, (date)):
        obj = datetime.combine(obj, time())
        return obj.isoformat()

class WPPost:
  def __init__(self):
    # WP投稿に必要な情報
    self.user_id_ = "【ここを直す】" # ユーザーID
    self.password_ = 'xxxx xxxx xxxx xxxx xxxx xxxx' # 【ここを直す】APIパスワード
    self.baseurl_ = "http://【ここを直す】.com/" # 投稿先サイトアドレス
    # ローカル環境では"http://localhost:8000/"

  def post(self,filename):
    # 記事idが付与されていれば更新、そうでなければ新規post
    end_point_url = self.baseurl_ + "wp-json/wp/v2/posts"

    # 本文を指定(htmlから)
    with open (os.path.splitext(filename)[0] + ".html", encoding='utf-8') as f:
      content = f.read()

    # 基本パラメータ設定
    payload = {
      'status' : 'draft', #下書き
      'comment_status': 'closed', #コメント欄非表示
      'content' : content,
    }
    # 本文以外(mdのyamlヘッダから)
    with open(os.path.splitext(filename)[0] + ".md", encoding='utf-8') as file:
      objs = yaml.safe_load_all(file)

      for obj in objs:
        # 本当は最初のstreamだけ番号で取り出したかったができなかった
        print(obj)
        info = obj
        break

    # パラメータをまとめる
    payload.update(info)

    # featured_mediaが数字で指定されていなければidを探す
    if payload['featured_media'].isdecimal() == False:
      r = self.get_media(1,os.path.basename(os.path.splitext(payload['featured_media'])[0]))
      if r.status_code == requests.codes.ok:
        get_id  =r.json()
        payload['featured_media'] = get_id[0]['id']
        print("media hit:{}".format(payload['featured_media']))
      else:
        # 上がってなかったらアップロード
        r = self.upload_media(payload['featured_media'])
        if r.status_code == requests.codes.ok:
          # print(get_id)
          get_id = r.json()
          payload['featured_media'] = get_id['id']
          print("media not found:{}".format(payload['featured_media']))
        else:
          print("media err:{}".format(payload['featured_media']))
          payload.pop('featured_media')
   
    # 記事の投稿
    try:
      if info['id'] > 0: #既にIDのついている記事は上書き
        end_point_url += '/' +str(info['id'])

    finally:
      # 投稿
      headers_ = {'content-type': "Application/json"}
      r = requests.post( end_point_url, data=json.dumps(payload, default=date_to_datetime) , headers=headers_, auth=(self.user_id_, self.password_) )

      # 確認
      print(r.status_code) # 201:成功
      return r

  def upload_media(self, mediapath):
    end_point_url =self.baseurl_ + "wp-json/wp/v2/media"
    headers_ = {
        "Content-Disposition": "attachment; filename=" + os.path.basename(mediapath) ,
        "Content-Type": "application/octet-stream"}
   
    with open(mediapath, 'rb') as f:
        payload = f.read()
   
    r = requests.post(end_point_url, data=payload, headers=headers_, auth=(self.user_id_, self.password_))
    return r

  def get_post(self,page,keyword):
    # 新しい投稿のidを表示
    end_point_url =self.baseurl_ + "wp-json/wp/v2/posts"
    r = requests.get(end_point_url,params={'page': page,'search': keyword})
    return r

  def get_media(self,page,keyword):
    # 既にアップされたメディアを取得(name検索)
    end_point_url =self.baseurl_ + "wp-json/wp/v2/media"
    r = requests.get(end_point_url,params={'page': page,'search': keyword})
    return r

def Main():
  # 実行フォルダをこのファイルがあるフォルダに変更
  os.chdir(os.path.dirname(os.path.abspath(__file__)))

  wp_post = WPPost()

  for filename in os.listdir():
    print(os.path.join(os.path.abspath(__file__), filename))
    if filename.endswith(".md") :
      # pandocでmdからhtmlへ変換
      pandoc_path = r"C:\Users\【ここを直す】\AppData\Local\Pandoc\pandoc.exe" # pandocの場所
     
      subprocess.run([pandoc_path,"-f", "markdown","-t","html", filename, "-o",os.path.splitext(filename)[0] + ".html"])
     
      # 投稿処理
      result = wp_post.post(filename)
     
      if result.status_code == requests.codes.ok:
        # 最新postのidを取得
        newpost = result.json()
        print("newpost id:{0}, featured_media:{1}".format(newpost['id'],newpost['featured_media']))

        # 投稿完了ファイルをdoneに移動
        shutil.move(filename, '../done/')

        # htmlは不要なので削除
        os.remove(os.path.splitext(filename)[0] + ".html")

    elif (filename.endswith(".png")) or (filename.endswith(".jpg")):
        # 画像の投稿
        result = wp_post.upload_media(filename)
        if result.status_code == requests.codes.ok:
        # idを取得
          newmedia = result.json()
          print("newmedia id:{}".format(newmedia['id']))

          # 投稿完了ファイルをmediaに移動
          shutil.move(filename, '../media/')
    else:
        continue

if __name__ == '__main__':
    Main()

投稿手順

  1. 書き終わった記事のMarkdownと、アップする画像をstageフォルダに移動する。
  2. stageフォルダのpost.pyを実行する。
    Visual Studio Codeから実行することもできる。 アップした記事のidなどが標準出力に出てくるはず。
  3. 上記のプログラムだと下書き状態で投稿されるので、公開前にブラウザで内容確認。
    もし公開ボタンを押せなくても、内容が問題なければ記事一覧に戻って「クイック編集」からタグをつけたり公開したりすることができる。

まとめ

もうちょっと洗練させたいのですが、完璧を目指してたら進まないので、今はこんなもんかな……

投稿画面が開くまで待たなくていいし、
記事を投稿したのかどうなのかフォルダの状態でわかるので管理も楽です。

参考リンク

こちらの記事を参考にさせていただきました。

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