こんにちは、Juntechです。
今回はPythonとWordPress APIを使って、記事を投稿してみたいと思います。
WordPress APIについてはこちら
なお、Pythonはver3.7.7を使います。
それでは、早速やっていきます。
完成イメージ
これを実現するためには、下記を満たす必要があります。
- WordPressにマークダウンテキストを投稿する
- WordPressに画像を登録する
- 投稿にアイキャッチ画像を設定する
- 投稿にリサイズ画像を挿入する
- 投稿にリンクを挿入する
- 投稿にコードブロックを挿入する
設計してみる
ということでまずは実現方法を設計してみます。
処理フロー
こんな感じでやります。
ポイントは先に画像をPOSTして、
POSTした結果で記事内の画像パスを置換するところです。
ファイルツリー
これをやるために、先にファイルツリー(フォルダ構造)を決めておきます。
$ tree .
.
├── eyecatch-image
│ └── test-post.png
├── single
│ └── test-post.md
└── single-image
├── test-1.png
├── test-2.jpg
└── test-3.png
└── post-wp-article.py
eyecatch-imageフォルダではアイキャッチ画像ファイルを、
singleフォルダでは記事ファイルを、
single-imageフォルダでは記事に挿入する画像ファイルを管理します。
画像の拡張子はjpg,pngの2つを想定します。
Pythonでやる理由
APIの実行だけであればShellとかでもいいのですが、
今回は画像のURLをレスポンスから受け取り、
POSTする記事の置換処理を実施する必要があります。
Pythonであればテキスト処理も簡単にできるので、Pythonを使います。
また、今後は記事の投稿をJenkinsジョブ化したいので、
今時点でスクリプト化しておくことで後々の実装も楽になります。
(Jenkinsジョブ化の記事は追って投稿予定です!)
実装してみる
素材となる記事
私は普段マークダウンで記事を作成しているので、
今回もマークダウンで書きます。
# テスト投稿
テスト投稿です。
### テスト投稿
この記事はマークダウンで書かれています。
### 盛り込む要素
#### リンクを入れる
このページの投稿にはWordPress APIを利用しています。
WordPress APIについてはこちら
【投稿自動化】WordPress をAPIで操作する【はじめの一歩】
#### 画像を入れる
画像も投稿します。
![](../single-image/test-1.png)
![](../single-image/test-2.jpg)
![](../single-image/test-3.png)
投稿する画像はリサイズされたものにします。
#### コードブロックを入れる
コードブロックも投稿できるようにします
```sql
SELECT
foo
FROM
bar
;
```
---
おわり。
余談ですが、マークダウンで記事を書くときには、
Visual Studio Code x MarkDown Preview Enhanced x Paste Image
の組み合わせが最強だと思います。
できた
早速ですが、完成版のコードです。
# -*- coding: utf-8 -*-
# コマンド:
# $ python3 post-wp-article.py ${article-name}
import os,sys,re,time,json
import chardet # pip3 install chardet
import requests # pip3 install requests
import glob # pip3 install glob
# エラーを吐いてExitする関数
def exit_with_error(message):
status_sys_error = 1
message_error = 'Error! : {}'
print(message_error.format(message))
sys.exit(status_sys_error)
# API叩いて失敗したらリトライする関数
def post_with_retry(data_name,url,header_obj,data_obj):
max_error_count = 3
sleep_second = 3
post_success_status = 201
response_status_code = None
response = None
error_count = 0
while response_status_code != post_success_status:
response = requests.post(url, headers=header_obj, data=data_obj)
response_status_code = response.status_code
if response_status_code != post_success_status:
print('Error! :' + response.json()["message"])
if error_count == max_error_count:
message = 'Failed post. url={}, data_name={}.'.format(url,data_name)
exit_with_error(message)
else:
error_count += 1
print('Retry : count=' + str(error_count))
time.sleep(sleep_second)
else:
print('Post Succeed: ' + data_name)
response_json = response.json()
return response_json
# ファイルパス・ディレクトリパス
article_name = sys.argv[1]
single_dir = './single'
article_file_path = '{}/{}.md'.format(single_dir, article_name)
eyecatch_dir = './eyecatch-image'
# APIを叩く用の設定
wp_authorization_string = os.environ['WP_AUTHORIZATION_STRING']
url_post_media = 'https://autohacks.net/wp-json/wp/v2/media'
url_post_single = 'https://autohacks.net/wp-json/wp/v2/posts'
# 記事ファイルの存在チェック
if os.path.exists(article_file_path) != True:
message = '{} is not found.'.format(article_file_path)
exit_with_error(message)
# 記事ファイルの文字コードチェック(UTF-8でなければエラー)
with open(article_file_path, mode='rb') as f:
charset = chardet.detect(f.read())['encoding']
if charset != 'utf-8':
message = 'charset of {} must be utf-8, but detected {}.'.format(
article_file_path, charset)
exit_with_error(message)
# 記事ファイル内で画像パスが1行に2件以上存在しないことをチェック
# 問題なければ画像パスのディクショナリを作成
violation_list = []
image_file_dict = {}
with open(article_file_path, mode='r') as f:
article = f.readlines()
for i, line in enumerate(article):
image_file = re.findall(r'!\[\]\(([^\):]+\.(jpg|png))\)', line)
if len(image_file) == 1:
image_file_dict[i] = {'local': image_file[0][0]}
elif len(image_file) > 1:
violation_list.append(i)
if len(violation_list) > 0:
for i in violation_list:
line_num = i + 1
print('line {} has 2 or more image files.'.format(line_num))
message = 'md-article violate writing rule.'
exit_with_error(message)
# 画像ファイル存在チェック
non_existence_list = []
for image_file_index in image_file_dict:
image_file_local = image_file_dict[image_file_index]["local"]
image_file_path = '{}/{}'.format(single_dir, image_file_local)
if os.path.exists(image_file_path) != True:
non_existence_list.append(image_file_path)
if len(non_existence_list) > 0:
for non_existence_file in non_existence_list:
print('{} is not found.'.format(non_existence_file))
message = 'some image files are not found.'
exit_with_error(message)
# アイキャッチ画像ファイル存在チェック
if len(glob.glob('{}/{}'.format(eyecatch_dir, article_name + '.*'))) == 0:
message = 'eycatch image file is not found.'
exit_with_error(message)
# 記事ファイル内の画像をPOST
for image_file_index in image_file_dict:
image_file_local = image_file_dict[image_file_index]["local"]
image_file_path = '{}/{}'.format(single_dir, image_file_local)
file_name = os.path.basename(image_file_path)
with open(image_file_path, mode='rb') as f:
headers = {
'Authorization': 'Basic {}'.format(wp_authorization_string),
'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment; filename="{}"'.format(file_name)
}
image_data = f.read()
response = post_with_retry(image_file_path,url_post_media,headers,image_data)
media_source_path = response["source_url"]
media_medium_path = response["media_details"]["sizes"]["medium"]["source_url"]
image_file_dict[image_file_index]["source"] = media_source_path
image_file_dict[image_file_index]["medium"] = media_medium_path
# アイキャッチ画像をPOST
eyecatch_image_file_path = glob.glob('{}/{}'.format(eyecatch_dir, article_name + '.*'))[0]
eyecatch_image_file_name = os.path.basename(eyecatch_image_file_path)
eyecatch_image_file_id = ''
with open(eyecatch_image_file_path, mode='rb') as f:
headers = {
'Authorization': 'Basic {}'.format(wp_authorization_string),
'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment; filename="{}"'.format(eyecatch_image_file_name)
}
image_data = f.read()
response = post_with_retry(eyecatch_image_file_path,url_post_media,headers,image_data)
eyecatch_image_file_id = response["id"]
# 記事をPOST
with open(article_file_path, mode='r') as f:
article = f.readlines()
# 記事内の画像挿入箇所にWordPress上の画像パスをセット
for image_file_index in image_file_dict:
image_file_path_local = image_file_dict[image_file_index]["local"]
image_file_path_medium = image_file_dict[image_file_index]["medium"]
image_file_path_source = image_file_dict[image_file_index]["source"]
text_before = "![]({})".format(image_file_path_local)
text_after = "[![]({})]({})".format(image_file_path_medium, image_file_path_source)
article[image_file_index] = article[image_file_index].replace(text_before, text_after)
# 記事POST
headers = {
'Authorization': 'Basic {}'.format(wp_authorization_string),
'Content-Type': 'application/json',
}
title = article[0].replace('# ','')
# 記事の1行目(=タイトル行)を削除
del article[0]
content = ""
for line in article:
content += line
body = {
"slug": article_name,
"title": title,
"content": content,
"featured_media" : eyecatch_image_file_id,
"status": "draft"
}
post_with_retry(article_file_path,url_post_single,headers,json.dumps(body))
print('Success!')
コードの解説をしつつ完成させる予定でしたが、
長いので、次回で解説をしたいと思います。
ということで早速実行してみます。
$ python3 post-wp-article.py test-post
Post Succeed: ./single/../single-image/test-1.png
Post Succeed: ./single/../single-image/test-2.jpg
Post Succeed: ./single/../single-image/test-3.png
Post Succeed: ./eyecatch-image/test-post.png
Post Succeed: ./single/test-post.md
Success!
無事正常終了しました。
投稿した記事を管理画面から確認する
管理画面から投稿を確認すると、
先ほどPOSTした記事が下書きで反映されています。
カテゴリーは指定していないので、
デフォルトで設定したものになっています。
投稿された記事の中身です。
テスト投稿です。
### テスト投稿
この記事はマークダウンで書かれています。
### 盛り込む要素
#### リンクを入れる
このページの投稿にはWordPress APIを利用しています。
WordPress APIについてはこちら
【投稿自動化】WordPress をAPIで操作する【はじめの一歩】
#### 画像を入れる
画像も投稿します。
[![](https://autohacks.net/wp-content/uploads/2021/01/test-1-300x300.png)](https://autohacks.net/wp-content/uploads/2021/01/test-1.png)
[![](https://autohacks.net/wp-content/uploads/2021/01/test-2-300x235.png)](https://autohacks.net/wp-content/uploads/2021/01/test-2.png)
[![](https://autohacks.net/wp-content/uploads/2021/01/test-3-300x225.png)](https://autohacks.net/wp-content/uploads/2021/01/test-3.png)
投稿する画像はリサイズされたものにします。
#### コードブロックを入れる
コードブロックも投稿できるようにします
```sql
SELECT
foo
FROM
bar
;
```
---
おわり。
画像パスがWordPress上のパスにできています。
リンクやコードブロックも問題なく挿入できていました。
できてますね!
今回はここまで。
次回はコードの解説をしたいと思います。