Python

Python ChatGPTを活用してブログを完全自動化:【記事自動作成、自動投稿】④

この記事は、ChatGPTのAPIをPythonから利用し、「ブログ記事を自動で生成させ、更にWordpressへ自動投稿してしまおう!」という、ブログの運営者にとって夢の様なチュートリアルのおまけ編です。
本来は3回で終了の筈でしたが、解説しきれなかった、設定の分離などを解説します。

設定値をファイルへ分離

現在のコードでは設定値が直書きされた状態で、あまり良い状態とは言えません。基本的にどんなアプリケーションでも設定ファイルを別の場所へ分けて保存します。Windows、LinuxなどOS別に解説したいと思います。

Pythonでファイルの読み書き

まず基本として、Pythonからファイルを読み書きする基本を解説します。Pythonではファイルの読み書きをとても簡単に行えます。

標準ライブラリのopen()関数を使用して読み書きを行います。

構文

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
  1. file: ファイルのパスを指定します。
  2. mode: ファイルを開くモードを指定します。デフォルト値は‘r’です。
  3. buffering: バッファリングの設定を指定します。デフォルト値は-1で、システムのデフォルトバッファリングが使用されます。
  4. encoding: ファイルのエンコーディングを指定します。テキストファイルを読み書きする場合にのみ使用します。
  5. errors: エンコーディングエラーの処理方法を指定します。
  6. newline: ファイルを読み書きする際の改行文字を指定します。
  7. closefd: ファイルディスクリプタを閉じるかどうかを指定します。デフォルト値はTrueです。
  8. opener: カスタムなオープン関数を指定します。デフォルト値はNoneです。
パラメータの設定値の説明
  1. ‘r‘: 読み込みモード。ファイルを読み込みます。
  2. ‘w’: 書き込みモード。ファイルに書き込みます。ファイルは上書きされます。
  3. ‘a’: 追記モード。ファイルに書き込みますが、ファイルの末尾に追記されます。
  4. ‘x’: 作成モード。ファイルを作成し、書き込みます。ファイルが存在する場合はエラーが発生します。
  5. ‘b’: バイナリモード。バイナリファイルを読み書きします。
  6. ‘t’: テキストモード。テキストファイルを読み書きします。

例えば、テキストファイルを読み込む場合はmode=’r’、バイナリファイルを読み込む場合はmode=’rb’とします。

open()関数を使用する際には、適切なモードとパラメータを指定してファイルの読み書きを行います。open()関数使用時はwithステートメントを使用してファイルオブジェクトを自動的にクローズすることが推奨されます。

読み書きのサンプル

ファイルの読み込み(Read)
with open(file_path, mode) as file:
    file_contents = file.read()
    # 読み込んだ内容の処理・利用

先程説明したように、withステートメントを使用し、そのブロック内で処理を行います。そうすることで、自動的にファイルを閉じてくれるので、必ずこの様に使用する癖をつけましょう。

読み込んだ内容は、変数fileのread()関数を使用して取得できます。

  1. file_path: ファイルのパスを指定します。
  2. mode: ファイルを開くモードを指定します。主なモードとしては以下があります。
    • ‘r’: 読み込みモード。ファイルを読み込みます(デフォルト)。
    • ‘rb’: バイナリ読み込みモード。バイナリデータとしてファイルを読み込みます。
ファイルの書き込み(Write)
with open(file_path, mode) as file:
    file.write(data)

読み込みとの違いとしては、モードの違いだけです。

ファイルへの書き込みは、変数fileのwrite()関数を使用して行います。

  1. file_path: ファイルのパスを指定します。
  2. mode: ファイルを開くモードを指定します。主なモードとしては以下があります。
    • ‘w’: 書き込みモード。ファイルに書き込みます。既存のファイルは上書きされます。
    • ‘a’: 追記モード。ファイルに書き込みますが、既存のファイルの末尾に追記されます。

設定値をファイルに分離

基本を理解したところで、実際に設定ファイルを作り分けていきましょう。設定を分けて保存しなければいけないのは、下記の3つです。

  1. username = ‘WordPressのユーザー名’
  2. password = ‘アプリケーションパスワード’
  3. api_key = ‘sk-<your secret key>’

ini

makeBlog.pyと同じディレクトリに、config.iniというファイルを新規作成してください。

Windows環境では、一般的にはINI形式の設定ファイル(.ini)を使用します。
ちなみにLinuxやMacの場合、一般的にはテキストファイル(例:config.txt、config.yaml)で設定を管理することが多いです。

下記の内容を記述してください。

[Credentials]
username = WordPressのユーザー名
password = アプリケーションパスワード
api_key = your secret key

この設定ファイルを読み込むには、configparserモジュールを使用して設定ファイルを読み込みます。

import configparser

# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')

# 値の取得
username = config.get('Credentials', 'username')
password = config.get('Credentials', 'password')
api_key = config.get('Credentials', 'api_key')
  1. configparserモジュールをインポートします。
  2. ConfigParserクラスのインスタンスを作成し、設定ファイルを読み込みます。
  3. 設定ファイルから値を取得します。

configparserモジュールをインポートして、ConfigParserクラスのインスタンスを作成し、設定ファイルを読み込みます。

get()メソッドを使用して、設定値を取得します。上記の例では、Credentialsというセクションからusername、password、api_keyというオプションの値を取得しています。

YAML

LinuxなどでYAMLを使用した場合は、下記の様になります。
config.iniの代わりに、config.yamlを作成して下記を書き写してください。

credentials:
  username: WordPressのユーザー名
  password: アプリケーションパスワード
  api_key: your secret key

PythonでYAMLを扱うためにはPyYAMLというパッケージが必要になります。pipでインストールしましょう。

pip install pyyaml

yaml.load関数を使ってYAMLファイルを読み込むことが出来ます。
Pythonに読み込んだときは辞書型として読み込まれます。

import yaml

# 設定ファイルの読み込み
with open('config.yaml') as f:
    config = yaml.safe_load(f)

# 値の取得
username = config['credentials']['username']
password = config['credentials']['password']
apikey = config['credentials']['api_key']
  1. yamlモジュールをインポートします。
  2. open()関数を使用して、設定ファイルを読み込みます。
  3. yaml.safe_load()関数を使用してYAML形式のデータを読み込みます。safe_load()関数は、安全にYAMLデータを読み込むための関数で、悪意のあるコードの実行を防止します。
  4. 設定ファイルから値を取得します。

config変数には、YAMLファイルから読み込まれた設定データが格納されます。credentialsというキーの下にusernamepasswordapi_keyというキーがあり、それぞれの値を取得しています。

実際に分離する

基本をマスターできたと思うので、実際のコードを分けます。YAMLで作成した方は適宜読み替えてください。

import openai
import requests
from requests.auth import HTTPBasicAuth
import configparser

# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')

# 値の取得
username = config.get('Credentials', 'username')
password = config.get('Credentials', 'password')
api_key = config.get('Credentials', 'api_key')

openai.api_key = api_key # 設定から取得したAPIキー

# openAIへAPIリクエストを投げて、レスポンスを返却するクラス
def getOpenAIResponse(question):
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=question,
        max_tokens=1000,
        echo=False
    )
    
    response_text = response['choices'][0]['text'].replace('\n', '')
 
    return response_text

# HTMLでフォーマット
class HtmlFormat:
    # 各プロパティの初期化処理
    def __init__(self, item, request, response, html_format, open_api_flg):
        self.item = item
        self.request = request
        self.response = response
        self.html_format = html_format
        self.open_api_flg = open_api_flg
        
    # HTMLでフォーマット   
    def getFormatContent(self):
        if (self.item != "タイトル"):
            return f"<{self.html_format}>{self.response}</{self.html_format}>"
        else:
            return self.response
        

# main
title = "" # タイトル保存
solution_1 = "" # 方法1を保存
solution_2 = "" # 方法2を保存

# リクエスト配列 - 記事の構成をお好きな形に変更して下さい。
html_format_list = [
    HtmlFormat("タイトル", "ダイエットのブログタイトルを1つ考えて下さい。語尾は「方法」でお願いします。", "", "", True),
    HtmlFormat("リード文のタイトル", "", "はじめに", "h2", False),
    HtmlFormat("リード文", "のリード文を考えて下さい。", "", "p", True),
    HtmlFormat("2つの方法", "", "2つの方法", "h2", False),
    HtmlFormat("方法1", "のやり方を箇条書きで10文字以下で1つ考えて下さい。", "", "h3", True),
    HtmlFormat("方法1詳細", "の詳細を100文字以上で教えて下さい。", "", "p", True),
    HtmlFormat("方法2", "のやり方を箇条書きで10文字以下で1つ考えて下さい。", "", "h3", True),
    HtmlFormat("方法2詳細", "の詳細を100文字以上で教えて下さい。", "", "p", True),
    HtmlFormat("まとめタイトル", "", "まとめ", "h2", False),
    HtmlFormat("まとめ", "の結論を200文字以上で教えて下さい。", "1", "p", True)
]

# 項目ごとにリクエスト内容を変更するので、ラムダを使用して変換
request_dic = {"タイトル": lambda x:x.request, 
               "リード文": lambda x:title + x.request,
               "方法1": lambda x:title + x.request,
               "方法1詳細": lambda x:solution_1 + x.request,
               "方法2": lambda x:solution_1 + "以外で" + title + x.request,
               "方法2詳細":lambda x:solution_2 + x.request,
               "まとめ": lambda x:title + x.request
            }

for html_format in html_format_list:
    while True:
        if html_format.open_api_flg:
            html_format.request = request_dic[html_format.item](html_format) 
            html_format.response = getOpenAIResponse(html_format.request)
        if html_format.item == "タイトル":
            title = html_format.response
        elif html_format.item == "方法1":
            solution_1 = html_format.response
        elif html_format.item == "方法2":
            solution_2 = html_format.response
                
        if html_format.response is None:
            print("リクエストに失敗しました。再実行します。")
        else:
            break
        

contents = ''       
for html_format in html_format_list:
    contents += html_format.getFormatContent()

# WordPressのREST APIのエンドポイント
endpoint = 'https://your-site.com/wp-json/wp/v2/posts'

# 下書きとして投稿するデータ
data = {
    'title': title,
    'content': contents,
    'status': 'draft'  # 下書きステータス
}

# POSTリクエストで下書き投稿
response = requests.post(endpoint, json=data, auth=(username, password))
print(response)

# レスポンスのJSONデータを取得
post = response.json()

# 投稿のIDを表示
print('下書きが作成されました。ID:', post['id'])

上記のように設定ファイルから設定を読み込む形式にして動作することを確認してください。
強いていうと、エンドポイントなんかも設定ファイルに分離しても良いかもしれません。

これで、設定ファイルの分離ができました!!

自動取得する質問を動的に変更する

現在のコードでは毎日実行すると、そのうち内容のバッティングが発生します。また、ダイエットに関する記事しか作成されなくなってしまいます。

防ぐ方法は色々ありますが、今回はエクセルで管理したいと思います。

まず、「questions.xlsx」というファイルをmakeBlog.pyと同じディレクトリに作り「questions」というシートを下記のイメージで作ってください。

A列に質問内容、B列にカテゴリIDを指定します。カテゴリIDなどは投稿時使用する事を考えて入れているだけです。本来はタグなども管理した方が良いかもしれません。

OpenPyXL

OpenPyXLは、PythonでExcelファイルを読み書きするためのライブラリです。こちらはインストールを行う必要があるので、下記のコマンドを実行してください。

pip install openpyxl

インストールの確認の為にPythonプロンプトを立ち上げて、下記を実行してください。

>>> from openpyxl import load_workbook # 1
>>> wb = load_workbook('questions.xlsx') # 2
>>> ws = wb['questions'] # 2
>>> print(f'sheet name: {ws.title}') # 4 
sheet name: questions
>>> ws['a2'].value # 5
'副業に関するブログタイトルを1つ考えて下さい。語尾は「方法」でお願いします。'
>>> ws['b2'].value # 5
1

しっかり読み込めている事が確認できます。詳細な解説はとても長くなりますので省略しますが、簡単に説明すると、

  1. openpyxlモジュールからload_workbook関数をインポートします。
  2. load_workbook関数を使用してExcelファイルを読み込みます。
    load_workbook関数にExcelファイルのパスを指定して、Workbookオブジェクトを作成します。先程作成した、questions.xlsxというExcelファイルを読み込んでいます
  3. Workbookオブジェクトからワークシートを取得します。先程作成したquestionsを指定しています。
  4. タイトルを表示
  5. セルの値を取得しています。

上記の流れをmakeBlogへ組み込みます。また、どの質問を行うかはランダムに決めることにします。

OpenPyXLの使用方法は、今後詳しく解説します。

randomモジュール

Pythonの乱数はrandomモジュールが担っています。それではrandomモジュールの使い方を簡単に説明します。

import random
from random import randint

# 整数乱数の取得
num = randint(2, 8)  # 2~8の範囲の整数値をランダムに取得(エクセルの行数に合わせた。)
print(num)  # 2など

import random

と書いてインポートして使います。また、今回は整数を取得したいのでrandintを別途インポートしています。

randint(a, b)の返す値はa以上b以下の整数です。こちらはbは必ず含まれると決まっています。

randomモジュールの説明も長くなるので、省略します。

勘の鋭い方ならピンときているかもしれませんが、ランダムに行数を指定してエクセルから質問を取得し、チャットGPTからの解答を得て記事にするという方法を今回は使用します。

完全にタイトルの重複を防ぐわけではありませんが、かなり重複は少なくなります。

完全に重複を防ぐ場合、1度作成されたタイトルをエクセルなどに保存し、同一カテゴリIDの場合、過去に利用したタイトルを「、」などで連結し、チャットGPTへの質問時に「連結したタイトル以外でお願いします。」と追記することで実現可能です。

makeBlog.pyの完成

あとは、エクセルの読み込みと質問(prompt)を変更するだけです。下記の様になります。

import openai
import requests
from requests.auth import HTTPBasicAuth
import configparser
import random
from random import randint
from openpyxl import load_workbook

# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')

# 値の取得
username = config.get('Credentials', 'username')
password = config.get('Credentials', 'password')
api_key = config.get('Credentials', 'api_key')

openai.api_key = api_key # 設定から取得したAPIキー

# openAIへAPIリクエストを投げて、レスポンスを返却するクラス
def getOpenAIResponse(question):
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=question,
        max_tokens=1000,
        echo=False
    )
    
    response_text = response['choices'][0]['text'].replace('\n', '')
 
    return response_text

# HTMLでフォーマット
class HtmlFormat:
    # 各プロパティの初期化処理
    def __init__(self, item, request, response, html_format, open_api_flg):
        self.item = item
        self.request = request
        self.response = response
        self.html_format = html_format
        self.open_api_flg = open_api_flg
        
    # HTMLでフォーマット   
    def getFormatContent(self):
        if (self.item != "タイトル"):
            return f"<{self.html_format}>{self.response}</{self.html_format}>"
        else:
            return self.response
        

# main
title = "" # タイトル保存
solution_1 = "" # 方法1を保存
solution_2 = "" # 方法2を保存

# エクセルを読み込み
wb = load_workbook('questions.xlsx')
ws = wb['questions']
num = randint(2, 8)  # 2~8の範囲の整数値をランダムに取得(エクセルの行数に合わせた。)
question = ws['a' + str(num)].value

# リクエスト配列 - 記事の構成をお好きな形に変更して下さい。
html_format_list = [
    HtmlFormat("タイトル", question, "", "", True),
    HtmlFormat("リード文のタイトル", "", "はじめに", "h2", False),
    HtmlFormat("リード文", "のリード文を考えて下さい。", "", "p", True),
    HtmlFormat("2つの方法", "", "2つの方法", "h2", False),
    HtmlFormat("方法1", "のやり方を箇条書きで10文字以下で1つ考えて下さい。", "", "h3", True),
    HtmlFormat("方法1詳細", "の詳細を100文字以上で教えて下さい。", "", "p", True),
    HtmlFormat("方法2", "のやり方を箇条書きで10文字以下で1つ考えて下さい。", "", "h3", True),
    HtmlFormat("方法2詳細", "の詳細を100文字以上で教えて下さい。", "", "p", True),
    HtmlFormat("まとめタイトル", "", "まとめ", "h2", False),
    HtmlFormat("まとめ", "の結論を200文字以上で教えて下さい。", "1", "p", True)
]

# 項目ごとにリクエスト内容を変更するので、ラムダを使用して変換
request_dic = {"タイトル": lambda x:x.request, 
               "リード文": lambda x:title + x.request,
               "方法1": lambda x:title + x.request,
               "方法1詳細": lambda x:solution_1 + x.request,
               "方法2": lambda x:solution_1 + "以外で" + title + x.request,
               "方法2詳細":lambda x:solution_2 + x.request,
               "まとめ": lambda x:title + x.request
            }

for html_format in html_format_list:
    while True:
        if html_format.open_api_flg:
            html_format.request = request_dic[html_format.item](html_format) 
            html_format.response = getOpenAIResponse(html_format.request)
        if html_format.item == "タイトル":
            title = html_format.response
        elif html_format.item == "方法1":
            solution_1 = html_format.response
        elif html_format.item == "方法2":
            solution_2 = html_format.response
                
        if html_format.response is None:
            print("リクエストに失敗しました。再実行します。")
        else:
            break
        

contents = ''       
for html_format in html_format_list:
    contents += html_format.getFormatContent()
    

# WordPressのREST APIのエンドポイント
endpoint = 'https://your-site.com/wp-json/wp/v2/posts'

# 下書きとして投稿するデータ
data = {
    'title': title,
    'content': contents,
    'status': 'draft'  # 下書きステータス
}

# POSTリクエストで下書き投稿
response = requests.post(endpoint, json=data, auth=(username, password))
print(response)

# レスポンスのJSONデータを取得
post = response.json()

# 投稿のIDを表示
print('下書きが作成されました。ID:', post['id'])

エラーが無く動けば完成です。
カテゴリIDもエクセルに管理しているので、併せて投稿することも可能です。生成された記事はリライトなどをした方が良いので、下書きで投稿することをお勧めします。

まとめ

4回のチュートリアルにお付き合いいただき、ありがとうございました!!

ChatGPTは、OpenAIが提供する強力な自然言語処理モデルで、これからますます進化していく事が予想されます。

再度このチュートリアルの手順だけおさらいしておきます。

  1. APIキーの管理: Pythonコード内で使用するAPIキーを設定ファイルに保存し、コードから読み込む。
  2. 外部APIの活用: ChatGPTを使って記事の自動生成を行う。ChatGPTに問いかける形で記事のタイトルや内容をリクエストし、生成された文章を取得。
  3. データのフォーマット: 取得した文章を必要な形式に整形。HTMLのフォーマットや他の要素との組み合わせなど、記事の構成に合わせた処理を行う。
  4. ブログ投稿: WordPressのREST APIを使用して、自動生成された記事をブログに投稿します。認証情報を含めたAPIリクエストを行い、下書きとして記事を作成。
  5. エクセルから読込: エクセルなどから質問を動的に取得し、取得した質問で記事を作成。

以上の手順により、PythonでChatGPTを活用してブログの記事自動作成と自動投稿を実現することができます。この自動化により、記事作成の手間を省きながら、継続的なコンテンツの生成と公開が可能となります。

今後も皆様が楽しめて尚且つ作業が効率化できる様な情報を提供していきます!!