はじめに(オタク特有の自分語り)
私は毎年推しの遊佐こずえ1ちゃんの誕生日(2月19日)に、「誕生日おめでとう」の意味を込めて創作活動(主に動画)を行っていました。
しかし今年はなんやかんやで忙しく、何も作れていませんでした。
そんな中で迎えた2月12日、あることをひらめきました。
推しが勝手にTLから学んでツイートしてくれるbotなら今からでも間に合うのでは!?!?!?
とはいえ、推しの誕生日まではさほど時間がありません。なるべく簡単にいろんな機能を実装できるのが望ましいです。
以前個人的にpythonを使用したときに大変便利に感じたので、今回もpythonを使用することにしました。
仕事では全く使っていないだけあってほぼ使い方は忘れてますが、今から学んでもまだどうにかなるでしょう。
善2は急げ、です。今から始めればまだ間に合いそうです。早速始めてみます。
この記事が役に立つと思われる方々
・Twitterのbotを作ったことがない
・Pythonをいじったことが(ほぼ)ない
・プログラミングに関するちょっとした知識がある(変数・関数という単語を聞いたことがあるレベル)
※自分のとりあえずの忘備録的なのも兼ねてるので、基本的かつ汎用的な記述がメインです
※pythonの知識やコードの描き方については付け焼き刃レベルなので、「こうしたほうがいい」など指摘がございましたらご指摘お願いいたします。
用意するもの
- pythonが使用できるサーバー
- 私はさくらのレンタルサーバーを契約していたのですが、自動返信の機能がかなりの頻度で落とされることが発覚しました。さくらのレンタルサーバー上で自動返信機能をデーモン化するのは厳しそうだったので、手持ちのラズパイを使用することにしました。
【余談】さくらのレンタルサーバーで、こちらの通りに設定したらいい感じにpython3が動くようになった気がします。 - Twitterアカウント
- Twitterアプリを作るのであれば必要です。既存のアカウントでもいいですし、新たに作成するのもいいでしょう。 ただし、初心者の方は後述するアクセストークンなどを取得するのが大変だと思うので、botとして使用するアカウントでアプリを作るのがいいと思います。 アプリを作るとはいってもそんな大層なものではなく、アプリを識別する暗号的なものを発行するって感じです。
- pythonで開発できる環境
- それなりのスペックのパソコンがあれば問題ないでしょう。私はめんどくさがりなのでAnacondaを使用してpythonをインストールしました。なおこの方法はハードディスクなどの使用容量を抑えたい方にはお勧めできません。私もそうだったのですが、急を要する案件だったので仕方なくそうしたみたいなところはあります。 開発環境と本番環境は同じ端末でもいいですが、別の環境の方がなんやかんやで融通が利くと思います。
-
以下のURLにアクセスし、一番下の「+ Create App」をクリック
https://developer.twitter.com/en/portal/projects-and-apps
-
なんか3つくらいめちゃくちゃな文字列が出てくるので、API keyとAPI secret keyをコピーして、忘れないようにどこかにメモ。
-
名前とかを適当に設定します。
前はなんかTwitter社に「こういうことするんでアプリ作ります!!!」ってメッセージ送らなきゃいけなかったような気がしますが、いまはその必要もないようです。はなくそをほじるのはおろか、煮て味付けしながらでもアプリが作れちゃいますね。
最近またTwitterAPIを使用したアプリを別アカウントで作ったのですが、めちゃくちゃ細かい要件まで聞かれました。少なくともアカウント作成後の初回はいろいろ書くことが求められるようです。(追記:2022年9月27日)
-
「Keys and tokens」をクリックして[Access Token と Access Token Secretをコピーして、忘れないようにどこかにメモ。
-
アイドルマスターシンデレラガールズに登場するアイドルの一人。11歳。いつも眠そうにしている。よくわかんないけど天才。かわいい。
『たかいところから…しゅー……えへへー…。』
— スターライトステージ (@imascg_stage) November 24, 2019
SSレアの遊佐こずえちゃん登場です!https://t.co/mIoEjCBQs4 #デレステ pic.twitter.com/H6O2A54uAT -
とんでもないツイートを拾ってきてしまう前例があるので、善かどうかは人によるような気はしますが……。
……久川凪の………陰毛ちらし寿司🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵
— 学習する佐城雪美bot (@YukimiLearning) December 16, 2020
上記が用意できたら、次はいよいよアプリを作っていきます。
Twitterアプリの作り方
botの機能を決める(TwitterAPI編)
大雑把にまとめると以下に尽きると思います。
・TLからツイートを取得
・Twitterに投稿
・自動でフォロバする
・自動でリプ返する(できればリアルタイムで返信できるのが良い)
今回はPythonを使用することにしました。
なぜなら実装が楽そうだったからです。
Pythonのインストール方法などについてはググってください。
機能の実装
TwitterAPIの使用準備
Twitter関連の操作をするなら必要不可欠なモジュールです。いくつかあるようですが、今回はTweepy(英語版ドキュメント)(日本語版ドキュメント)を使用します。
なんか自分のラズパイ環境では pip install tweepy
と書いてみると、エラーを吐いてくれました(よく覚えてないけど、ERROR: Could not build wheels for Bottleneck which use PEP 517 and cannot be installed directly
的なことが書いてあった気がする)
何度試してもダメだったのですが、ふと思い立って以下のように書いてみると……
$ pip3 install tweepy
無事にできました!python2とpython3が入っている環境だと、3を指定しないとだめなんですね……。初心者なので仕方ないですが、完全に初歩的なミスって感じがします。
インストールできたら、機能を実装していきます。
config.pyには、TwitterAPIを使用するのに必要なAPI KEYなどの情報を書いていきます。
「Twitterアプリの作り方」でメモした値を以下のように記載します。
api_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
api_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
access_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
access_token_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
my_screen_name = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
ツイートの取得と送信
tweetApi.pyには実際の動作を書いていきます。
まずはツイートを一つ取得し、そのまま送信するだけのものを書いてみます。
import tweepy
import config
# Oauth認証
auth = tweepy.OAuthHandler(config.api_key, config.api_secret)
auth.set_access_token(config.access_token, config.access_token_secret)
api = tweepy.API(auth)
# タイムラインからツイートを1個取得(最大200まで指定可能。引数に値がない場合は20個取得)
timeline = api.home_timeline(count=1)
for tweet in timeline:
fetched_tweet = tweet.text
# ツイートを送信する
api.update_status(fetched_tweet)
リプライを受信したときは自動で返信する
次に、自動で返信する動作を書いていきます。
自分の記憶が正しければ、TwitterAPIには自動でリプライを取得する機能はなかったかと思います。しかし、「@Manesuru_Kozue」というツイートを常に検索することは可能なので、ここで引っ掛かったものを対象にリプを送るという強引な手法で自動リプライ機能を実装します。
以下のサンプルは、(=リプライをオウム返しする)プログラムになっています。
このプログラムを起動するだけでは途中で例外が発生してたびたび落ちてしまっていたため、systemdでデーモン化しました。(後述)
なおsys.path.append()
で指定するパスは、ターミナル上でpip3 show <モジュール名>
を実行することで調べられます。
【2022年10月16日追記】
StreamListenerはStreamに統合されたため、autoReplyMain.pyを書き換えました。
import sys
# serviceから起動するとモジュールの場所を認識してくれないので、各モジュールのパスを通すようにする
sys.path.append('/path/to/module')
import tweepy
import config
import datetime
import re
# Oauth認証
auth = tweepy.OAuthHandler(config.api_key, config.api_secret)
auth.set_access_token(config.access_token, config.access_token_secret)
api = tweepy.API(auth)
# ここにストリーミング時の動作を書く
class StreamListener(tweepy.Stream):
def on_status(self, status):
# RTされたときにもリプライを送ってしまうのを防ぐ
if not re.match("RT @" + r'[\w]+', status.text):
# タイムラインからツイートを1個取得(最大200まで指定可能。引数に値がない場合は20個取得)
timeline = api.home_timeline(count=1)
for tweet in timeline:
fetched_tweet = tweet.text
# リプライの実施
tweet = "@"+str(status.user.screen_name)+" "+ fetched_tweet
tweet = '{:.140}'.format(tweet) #140字目より後ろの文字列は捨てる
api.update_status(status = tweet, in_reply_to_status_id = status.id)
# ストリームを取得する
stream = StreamListener(config.api_key, config.api_secret, config.access_token, config.access_token_secret)
# 自分へのリプライを検索
stream.filter(track=['@' + config.my_screen_name])
フォローする
フォローはこんな感じでできます。
import tweepy
import config
#Oauth認証
auth = tweepy.OAuthHandler(config.api_key, config.api_secret)
auth.set_access_token(config.access_token, config.access_token_secret)
api = tweepy.API(auth)
#フォロワーを200人取得
follower_list= api.followers(count=200)
#フォロワーを一人ずつフォローしていく
for follower in follower_list:
if follower.name is not None:
relation = api.show_friendship(source_screen_name = config.my_screen_name, target_screen_name = follower.screen_name)
if relation[0].following or relation[0].following_requested:
#フォロー済みのアカウントはフォローしない。こうしないとフォロー規制にすぐ引っかかってしまう。
pass
else:
#フォローする
api.create_friendship(follower.screen_name)
ツイートをランダムに取得する
取得するツイートをランダムに一つ選ぶ際に、numpyを使用しました。
こちらも先ほどと同じようにインストールしていきます。
pythonいじったことのある方なら大体もう入れている気もしますが……。
$ pip3 install numpy
取得するツイートをランダムに一つ選びます。
ランダムに選ぶついでに、クラスで分割してみました。
(分割するなら最初からやった方がよかったんじゃない?あとコメント書く位置統一しろって感じですねすみません……。)
import tweepy
import config
import numpy as np
class Oauth():
def __init__(self):
# OAuth認証を行う
auth = tweepy.OAuthHandler(config.api_key, config.api_secret)
auth.set_access_token(config.access_token, config.access_token_secret)
self.api = tweepy.API(auth)
return
class ReadTweet(Oauth):
#ツイートを読み込み、ランダム一つ選ぶ
def ReadTweet(self):
#タイムラインからツイートを200個取得(引数に値がない場合は20個取得)
timeline = self.api.home_timeline(count=200)
#timelineからツイートを取り出してリストに格納
original_tweet_list = []
for tweet in timeline:
fetched_tweet = tweet.text
original_tweet_list.append(fetched_tweet) #appendでリストの末尾に要素を追加できる
#取得したツイートの中からランダムに一つ選ぶ
if 0 < len(original_tweet_list):
index = np.random.choice(len(original_tweet_list))
original_tweet = original_tweet_list[index]
else:
print("タイムラインが取得できませんでした")
original_tweet = "ツイートの取得に失敗しました"
print(original_tweet)
return original_tweet
class WriteTweet(Oauth):
#ツイートをする
def WriteTweet(self, tweet):
tweet = '{:.140}'.format(tweet) #140字目より後ろの文字列は捨てる
self.api.update_status(tweet)
return
read = ReadTweet()
write = WriteTweet()
original_tweet = read.ReadTweet() #ツイートの読み込み
write.WriteTweet(original_tweet) #ツイートの送信
その他設定について
自動ツイート・フォロバのためのCRON設定
CRONを使用して自動ツイートの設定をしました。
さくらのレンタルサーバー上で動かしているので、何時何分にツイートさせるかを決めるのは難なくできましたが、「実行コマンド」については手こずりました。
調べてみたところ、以下のように記述する必要がありました。
cd [実行したい.pyファイルのあるディレクトリ] ; [python3の場所] [実行したい.pyファイル]
見てもらえれば分かるかと思いますが、CRONでコマンドを2つ実行したい場合はセミコロンで区切ればよいってことですね。
なお、python3の場所は、ターミナル上で$ which python3
と打つことで確認できます。
さくらのレンタルサーバー上でなければ、以下のように設定します。
4,19,34,49 * * * * cd [実行したい.pyファイルのあるディレクトリ] ; [python3の場所] [実行したい.pyファイル]
自動リプライ機能をデーモン化
自動リプライ機能はその性質上、常に起動している必要があります。
autoReplyMain.pyファイルを実行するだけでは動作が不安定だったため、動いていないことを確認してから再起動する必要があります。
これではさすがに怠すぎるので、プログラムが落ちるのを避けるために、systemdを使用して自動リプライ機能をデーモン化することにしました。
今回は変な場所にautoReplyMain.py
を置いてしまったのですが、本来は/opt/
以下に置くべき(※)とのことなので、次回以降はそうします。
とりあえず以下みたいな感じの/etc/systemd/system/autoReply.service
を作ったら、$ sudo systemctl start autoReplyMain.service
でサービスを起動します。
[Unit]
Description = autoReplyMain daemon
[Service]
ExecStart = /usr/bin/python3 /path/to/python/file/autoReplyMain.py
Restart = always
Type = simple
[Install]
WantedBy = multi-user.target
自動ツイート機能以外はさくらのレンタルサーバー上で動かしているのですが、この機能だけは自宅のラズパイ上で動かしています。私がさくらインターネットと契約すべきだったのは、「さくらのレンタルサーバー」ではなく「さくらのVPS」だったようです。契約した当時はこんなことをやるつもりではなかったので、仕方ない話ではありますが……若干の後悔が残る結果となりました。
【2022年10月16日追記】
ConoHa VPSに移行したので上記は解消されました。
https://qiita.com/4zna3/items/d668f74967ae059927e7
後編に続きます。