みなさん、こんにちは。サイオステクノロジー武井です。今回は。表題の通り、生成AIでブログ記事の宣伝(画像付き)を生成してXにPostするOSSを作ったので、それを紹介したいと思います。
どんなもの?
WordPressやQiita、Zennなどのブログからランダムに1件記事を抽出して、その記事に基づいた画像付きTIPSを作成し、XにPostします。以下のリポジトリで公開しています。
https://github.com/noriyukitakei/blog-tips-generator
イメージは以下のとおりです(字がちっちゃいので、クリックして拡大して見て下さい)。
左がブログ、右がXになります。ブログはたくさん記事がありますよね。その記事の中からなにか1件ランダムに記事を抽出して、その記事の中に書かれている色々な情報からTIPSを生成し、そのTIPSに関連した画像を抽出します。そのTIPS、画像に加えて、引用元の記事のURL、ハッシュタグをつけて、XにPostします。
最終的には以下のようなPostになります。
そして、これを実現するためのコアテクノロジーは、今話題の「生成AI」です。
なにがうれしいの?
ブログのPRを効率よく行うことができます。
具体的には以下のようなメリットがあります。
面倒な作業を自動化してくれる
今までは、ブログの宣伝をXに行うためには、PRするブログの記事を選び、ブログの記事を要約して、XにPostするという作業を人手で毎回行っていたと思います。これはとっても手間です。
このOSSは、PRするブログ記事の選択、ブログ記事PR文の作成、XへのPostまですべて自動化してくれます。面倒な作業はすべてこのOSSにおまかせして、ハナキンにベルサッサすることが可能となります。
毎回異なるTIPSを生成してくれる
同じ記事から毎回異なるTIPSを生成してくれます。
一つの記事にはたくさんの役立つ情報(これを以降TIPSと呼びます)が詰まっています。このOSSでは、生成AIのテクノロジーによって、一つの記事の中から毎回異なるTIPSを作ってくれます。そしてさらにそのTIPSの作成元となった記事のURLまでPostしてくれます。
これで何が嬉しいかというと、XにPostされたTIPSは140字以内という短い文章なので、ブログを全部読むよりも素早く情報を取得でき、かつ、TIPS作成元の記事のURLも一緒にPostされているので、もっと詳しく知りたい方をブログ記事に誘導できます。これでブログのPV上がること間違いなしです。
そして、今までは、同じ記事には同じPR文を添えて毎回XにPRしていたかと思います。でも、中にはいろんなよさを持っている記事がありますよね。このOSSを使うと、同じ記事でも毎回異なるTIPSを生成してくれるので、同じ記事でも、色んな角度からPRしてくれます。
記事をランダムに抽出してくれる
どのブログ記事をPRしようかなと選別するのはなかなかに大変な作業です。このOSSはブログ内にある記事からランダムに1件PR対象の記事を抽出してくれるので、頭を悩ませる必要はありません。
また、新しいブログや注目のブログは、1日に何回もPRしたいことがあります。そういう場合は、そのブログ記事のURLを指定することで、特定のブログ記事を毎回PRすることもできます。
抽出してくれるTIPSに画像をつけてくれる
やはりPRはテキストだけだと物足りません。このOSSでは、ブログ記事の中にある画像を抽出しTIPSに添えて、XにPostしてくれます。よって、テキストのみの簡素なPRから、画像を添えることによりわかりみ深く視覚に訴えたPRとなり、よりブログにアクセスしてくれることになるでしょう。
画像が記事内にない場合はテキストのみのPostになります。
どんなしくみ?
Pythonで動いています。環境変数で外部から必要なパラメーターを与えて、Pythonのファイルを実行するだけでOKです。なので必要なのはPythonを実行する環境だけです。物理マシンでもVMでもLambdaでもAzure Functionでも、Pythonさえ動けばOKです。
そして、もう一つ必要なのは「生成AI」になりまして、今のところ対応しているのはAzure OpenAI Serviceのみになります。OpenAIにはいずれ対応したいとは思っております。
上記以外は他に何も必要ないです。ストレージなども用意しなくてOKです。
処理フローは以下のようになります。
ブログ記事を1件ランダムに抽出する
ブログに登録されている記事の中から1件PR対象の記事をランダムに抽出します。
これはブログの種別(WordPressやQiita、Zenn)によって手法は異なります。WordPressやZennはRSSフィードから取得が可能ですが、QiitaはAPIから取得することとなります。
そのあたり、ブログの種別によって、独自にカスタマイズ・拡張できる仕組みになっております。これは後ほど説明いたします。
そして、先程も申し上げましたように、ランダム抽出ではなく、毎回同じ記事をPRすることもできます。注目のブログを注力してPRしたいときに便利な機能です。
生成AIによって、抽出した記事からTIPSを作成する
先程取得した記事を生成AIに読み込ませて、TIPSを作成します。記事の本文を与えてTIPS作成を依頼しているだけなので、毎回異なるTIPSを作成してくれます。
生成AIによって、TIPSに関連した画像を取得する
先程作成したTIPSに関連した画像のURLを本文から取得します。これも生成AIにお願いしています。画像が記事内にない場合は、何もせず、テキストのみのPostになります。
記事のURL、ハッシュタグを添えてXにPostする
記事のURLと、事前に環境変数に定義したハッシュタグを添えて、XにPostします。
どうやって使うの?
本章では実行に必要な環境と使い方を説明します。
実行環境
本OSSの動作に必要なのは、Pythonの実行環境と、Azure OpenAI Serviceのみです。
Python | 3.11 (3.11でしかテストしていないだけで、他のバージョンでも動くとは思います) |
生成AI |
Azure OpenAI Serviceが提供するChat Completion API |
使い方
以下のリポジトリをcloneします。
https://github.com/noriyukitakei/blog-tips-generator
そして、必要な環境変数を定義し、requirements.txtに定義してあるモジュールをインストールして、リポジトリ内のblog_tips_generator.pyを実行します。
環境変数は以下のとおりです。
BLOG_TYPE |
PRしたいブログの種別を定義します。現在対応しているのは、WordPress、Qiita、Zenn、はてなブログの4つです。それぞれ以下の環境変数を定義します。
|
BLOG_URL |
記事一覧を取得するためのURLです。ここで指定したURLから取得される記事からランダムで1件取得し、PRをXにPostします。このURLは、ブログの種別で入れる値は様々で、例えばWordPressの場合はRSSのFeedのURLを設定します。また、一方でQiitaはRSSのフィードからは全記事を取得できないので、APIのエンドポイントを設定します。ここに入れる値は後ほど詳細に説明します。 |
HATENA_ID |
BLOG_TYPEにhatena(はてなブログ)を指定していた場合に設定します。はてなブログに登録した全記事一覧を取得するAPIの認証に使用します。はてなIDを設定します。 |
HATENA_API_KEY |
BLOG_TYPEにhatena(はてなブログ)を指定していた場合に設定します。はてなブログに登録した全記事一覧を取得するAPIの認証に使用します。APIキーを設定します。 |
SPECIFIC_ARTICLE_URL |
BLOG_URLで指定したURLから取得した記事一覧のうち、ここで指定したURLの記事のPRが毎回XにPostされます。ランダムではなく特定の記事をPRし続けたい場合に便利です。指定しない場合は、ランダムに抽出されます。 |
HASH_TAGS |
XにPostされるPRの末尾に付与するハッシュタグを指定します。 |
ADDITIONAL_SYSTEM_ROLE |
OpenAIでいうところの、SystemのRoleになります(詳細はこちら)。AIのキャラ決定に利用するものであり、基本的なキャラ付けはプログラム内でしてあります。ここでは、追加で指定したいキャラを設定します。例えば、萌えキャラな感じでXにPostしてほしいときは「萌え萌えな口調でお願いします。」と設定します。 |
OPENAI_API_TYPE |
生成AIのタイプを指定しますが、今のところAzure OpenAI Serviceにしか対応していないので、「azure」と指定します。 |
OPENAI_API_BASE
|
接続したいAzure OpenAI ServiceのAPI baseを取得します。こちらに記載の「②Language APIs」の値です。 |
OPENAI_API_VERSION
|
APIのバージョンを指定します。バージョンについてはこちらのURLに記載があります。 |
OPENAI_API_KEY |
接続したいAzure OpenAI ServiceのAPIキーを取得します。取得方法については、こちらを参考にして下さい。 |
MAX_TOKENS |
生成AIに利用するモデルが扱うことができる最大トークン数を指定します。ブログ記事から取得した文章はここで指定したトークン以下に分割されます。できるだけ大きい値を指定することが望ましいです。指定しない場合の初期値は、4096となります。 |
DEPLOYMENT_ID
|
Azure OpenAI Serviceのデプロイを識別するIDになります。 |
X_CONSUMER_KEY |
XのAPIを使うために必要なConsumer Keyです。 |
X_CONSUMER_SECRET |
XのAPIを使うために必要なConsumer Secretです。 |
X_ACCESS_TOKEN |
XのAPIを使うために必要なAccess Tokenです。 |
X_ACCESS_TOKEN_SECRET |
XのAPIを使うために必要なAccess Token Secretです。 |
環境変数BLOG_URLについて
環境変数BLOG_URLに指定する値は、ブログの種別によって異なります。
WordPressの場合
WordPressの場合はRSS FeedのURLを指定します。例えば、ブログのURLが「https://tech-lab.sios.jp」だったら、BLOG_URLに指定する値は、「https://tech-lab.sios.jp/feed」になります。
Zennの場合
Zennの場合もWordPress同様、RSS FeedのURLを指定します。例えば、ブログのURLが「https://tech-lab.sios.jp」だったら、BLOG_URLに指定する値は、「https://tech-lab.sios.jp/feed」になります。
ただし、Zennではどうも、画像のリンク付きの本文を返してくれないので、今のところ、TIPSに画像を添付することはできないという制限があります。あしからず。。。
Qiitaの場合
Qiitaの場合は他とは異なります。QiitaはRSSから記事全文の一覧を取得することができず、APIを利用します。よって、環境変数BLOG_URLに指定する値は、APIのエンドポイントを以下の形式で指定します。
https://qiita.com/api/v2/users/[ユーザーID]/items
例えば、ユーザーIDがntakeiの場合は、以下のようになります。
https://qiita.com/api/v2/users/ntakei/items
はてなブログの場合
はてなブログもQiitaと同様、RSSから記事全文の一覧を取得することができないので、APIから取得します。そしてAPIの利用には認証が必要なのも他と異なる点です。なので、はてなブログの場合は、そのはてなIDで登録したブログ記事の一覧しか取得できません。
BLOG_URLに指定するAPIのエンドポイントは以下の形式で指定します。
https://blog.hatena.ne.jp/[はてなID]/[ブログID]/atom/entry
はてなIDがnoriyukitakei、ブログIDがnoriyukitakei.hatenablog.comの場合は、以下のようになります。
https://blog.hatena.ne.jp/noriyukitakei/noriyukitakei.hatenablog.com/atom/entry
独自に拡張したい!!
今のところは、WordPress、Qiita、Zenn、はてなブログの4つに対応していますが、さらに他のブログにも対応したいときや、独自にカスタマイズしたい場合は、既存のソースコードを変更することなく、実現が可能となります。
例えば、ここで新たにQiitaのブログをPR対象のブログに加えたいとします(既にQiitaは対応済みですが、説明をわかりやすくするために対応していないものと仮定します)。まず環境変数BLOG_TYPEに設定する値を決定する必要があります。ここではqiitaとします。
次に、blog_tips_generator.pyと同じ階層に以下の形式の名前のファイルを作成します。
entry_processor_{環境変数BLOG_TYPEの値}.py
つまり今回は以下のファイル名となります。
entry_processor_qiita.py
そして、entry_processor_qiita.pyは以下のように定義します。
import logging
import sys
import requests
# ログの設定
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# StreamHandlerの設定
handler = logging.StreamHandler(stream=sys.stdout)
handler.setLevel(logging.INFO)
# ハンドラをロガーに追加
logger.addHandler(handler)
def get_all_entries(url):
all_articles = []
page = 1
per_page = 100
while True:
params = {'page': page, 'per_page': per_page}
response = requests.get(url, params=params)
if response.status_code != 200:
raise Exception(f'API request failed with status code: {response.status_code}')
articles = response.json()
if not articles:
break
for article in articles:
# feedparserの形式に合わせた辞書を作成
parsed_article = {
'content': article['rendered_body'],
'link': article['url']
}
all_articles.append(parsed_article)
page += 1
return all_articles
def get_system_role_for_extracting_image_url():
return "Qiitaの場合は画像のリンクは、data-canonical-srcから取得して下さい。"
このファイルには、2つの関数「get_all_entries」「get_system_role_for_extracting_image_url」を定義する必要があります。
get_all_entries
get_all_entries関数が返す値は、記事のデータを格納した辞書のリストです。各辞書はブログ記事の2つの主要な情報をキーと値のペアで持っています。’content’キーには、記事内に掲載されている画像のURLを含む記事の本文が文字列で格納され、’link’キーにはその記事のウェブアドレス(URL)が文字列で格納されています。リストには取得した全ての記事が含まれており、各記事は上述の形式の辞書として表されます。
例として、もし関数が3つの記事を取得したとすると、返されるリストは以下のような形をしています。
[
{'content': '記事1の内容', 'link': 'https://example.com/article1'},
{'content': '記事2の内容', 'link': 'https://example.com/article2'},
{'content': '記事3の内容', 'link': 'https://example.com/article3'}
]
get_system_role_for_extracting_image_url
ブログによって微妙に記事内に埋め込んでいる画像のURLの形式が違ったりします。例えば、Qiitaの場合は、imgタグのsrc属性ではなく、data-canonical-src属性から画像のURLを取得しなければなりません。そういったブログの種別ごとの特性をこの関数で吸収しています。
具体的にはこの関数で返す文字列は、OpenAIのsystemのroleに埋め込まれます。つまり、OpenAIに指示する内容をこの関数で返さなければなりません。
よって、Qiitaの場合では、「Qiitaの場合は画像のリンクは、data-canonical-srcから取得して下さい。」という文字列を返すことで、OpenAIのsystemのroleに埋め込み、AIに指示を与えているというわけです。
特に何も指示することがない場合は、空文字を返して下さい。
まとめ
いかがでしたでしょうか?今まで手作業でやっていた作業が自動化されて、そして今まで以上の効果が得られることがご理解いただけたかと思います。このOSSを使って、ブログのPVを向上していきましょう!!