統計学、機械学習などを使って身近な世界を分析したりするブログです

新年の抱負を立てるかわりにTwitter APIでbotを作った話

新年あけましておめでとうございます。本年もよろしくお願いいたします。

昨年はこちらのブログには一度も投稿しませんでしたね。noteのほうではいくつか記事を書いたのですが。そちらもよければご覧ください。リンクはこの記事の下のほうに貼っておきます。

さて、タイトルにあるとおり、新年は明けましたが、僕は抱負を立てませんでした。そのかわり、Twitter APIを使ってbotを作ったので、なぜそうしたのか、またその実装について書いていきます。

目次

1. なぜTwitter APIでbotを作ったのか

新年が明けたら、一年の抱負を立てる。これは極めて一般的な行為でしょう。多くの人が抱負を立て、それを公開する人もたくさんいます。

抱負を立てるというのは、素晴らしいことです。目標を持って、それに向かって進むわけですから、目標がなく、ただ闇雲に毎日を過ごすよりも、断然、理想の自分に近づける可能性は高まります。

でも僕は新年の抱負を立てませんでした。

1-1. なぜ新年の抱負を立てないか

なぜ新年の抱負を立てなかったか。なぜなら、それは、これまで立てた抱負を思い出したことがないからです。年始に抱負を立てて、年末には翌年の抱負を考える。


それただの抱負立てるマン!


抱負を立てたなら、年末には、その抱負に対してどのくらい達成できたのか、できなかったのなら、来年はどうしていくべきなのか、検証しなければ意味がありません。PDCAサイクルを回さなければ、ダメなのです。

じゃあ抱負を忘れないようにすればいいじゃん、という声が聞こえました。


それも、もうやった


それはすでに、僕にはワークしないことが判明しています。2019年の始めに、抱負を立てました。抱負を立てたことは覚えています。そして、立てた抱負を忘れないようにしたことも覚えています。でも、覚えていないのです。

かの有名な大前研一氏の名言があります。

人間が変わる方法は3つしかない。
1番目は時間配分を変える。
2番目は住む場所を変える。
3番目はつきあう人を変える。
この3つの要素でしか人間は変わらない。
最も無意味なのは、『決意を新たにする』ことだ。

そう、「決意を新たにする」ことは無意味なのです。

そこで、僕は自分の決意には頼らず、なにか仕組みを作ることにしました。理想の自分に近づくための仕組み。

1-2. なぜTwitterか

どんな仕組みを作ろうかなぁと考えていたとき、僕はいつものようにTwitterのTLを眺めていました。そこでは、たくさんの人が新年の抱負を公開しており、なにか参考になりそうなものはないかなぁと考えていたのです。

そこで、ふと思ったのです。


Twitter見すぎじゃね?

と。

iPhoneには便利な機能があり、自分がどのアプリをどのくらい使っているか見ることができます。その結果がこれです。

まずは、アプリを使っている時間。

f:id:shokosaka:20200103232647j:plain
Screen Time

そして、iPhoneを起動して最初に開くアプリ。

f:id:shokosaka:20200103232656j:plain
Pickups

Twitter、圧倒的です。TwitterのためにiPhoneを使っていると言っても過言ではない。いやむしろ、Twitterのために起きていると言っても言い過ぎではないくらい、Twitterを使っています。

しかし、Twitterを使うことをやめようとは思いません。いや、やめようと思ったことは何回もあるのです。しかし、前述のとおり、決意は屁の突っ張りにもならないので、ここに仕組みを取り入れることにしました。

これだけ多くの時間を費やしているTwitterの利用を仕組みでコントロールできれば、きっと理想の自分に近づけます。

1-3. 何をやりたいか

実は、Twitterの利用に関して、日頃から課題に感じていることが2つありました。

1-3-1. 凝り固まったTLをほぐしたい

先述のとおり、長い時間Twitterを使っていますから、そのTLには、多大な影響を与えられているはずです。ましてや、ずっと同じ人たちをフォローしていると、その人たちの意見や考えに自分が染まっていってしまうのではないか、という怖さがあります。

凝り固まったTLに影響されて、自分の思考回路まで凝り固まってしまわないようにしなければいけません。

なぜ同じ人をフォローし続けてしまうかというと、いろいろな原因が考えられます。

  • 新たにフォローする人を探す時間がない
  • 新たにフォローする人を探す方法が分からない
  • リアルな知り合いをアンフォローしづらい
  • ずっと前からTLでは見かけてたけど、フォローするタイミングを失った
  • ずっとつぶやいてない人(アンフォローすべき人)にそもそも気づかない
  • フォロワー数に対してフォロー数が増えるとダサいかもしれない
  • ツイートはあんまり好きじゃないのに、有名人だからフォローし続けてしまう

根深い問題です。

1-3-2. フォロワーと積極的にコミュニケーションをとりたい

2つ目の課題は、フォロワーとのコミュニケーションです。僕は個人的に、あまりフォロワーとコミュニケーションを取らないほうなのですよね。それが良くないとは思いつつも。

コミュニケーションといっても、リプを送り合う必要はなく、ただlikeしたり、RTしたりするだけでも、コミュニケーションが生まれると思っているのですが、なんとなくめんどくさかったり、likeのリストを後で見返したいツイート集として使っているというのもあり、あまりフォロワーのツイートに反応しないというのが現状です。

これを変えたい。

なぜなら、Twitterを通して社会とつながること、それ自体に意味がありますし、そのためにフォロワーとのコミュニケーションは欠かせません。また、自分がブログを書いたときや求人系のツイートをしたときにはRTやlikeがほしいのですが、普段自分がフォロワーのツイートに反応しないくせにそれを期待するなんて、我ながら虫が良すぎるとも思うのです。


これら2つの課題を解決するbotを作りました。

2. どんなbotを作ったのか

一言で言うと、自分のlike履歴に基づいて自動でフォロー/アンフォローするbotです。

もう少し詳しく言うと、ある一定期間、自分からのlikeがなかった人のうち数人をランダムにアンフォローし、自分がフォローし続けている人がフォローしている人のうち数人をランダムにフォローしてくれます。

このbotを導入することにより、botにアンフォローされたくない人のツイートを積極的にlikeするようになり、自分がlikeしていない人が自動でアンフォローされ、意識しなくても新たなフォローが増え、TLがほぐされていきます。完璧に先程の課題を解決する流れができあがりました。

決意に頼らず、botに頼る。

具体的な実装は以下のとおりです。

2-1. 全体像

まずは全体像をご覧ください。かっこよく見せるために英語で作った図です。

f:id:shokosaka:20200103223853j:plain
Twitter 自動フォロー/アンフォローbot の全体像

細かい部分は後述しますが、大きく4つのパートに分かれています。APIインスタンスを作成するスクリプト(get_api.py)、自分のlike履歴を自動収集するスクリプト(auto_fav_collect.py)、自動でアンフォローするスクリプト(auto_unfollow.py)、自動でフォローするスクリプト(auto_follow.py)、の4つです。これらをタスクスケジューラを使って定期実行させることでbot化させています。

2-2. APIインスタンス作成(get_api.py)

まずはAPIインスタンスの作成パートです。TwitterのAPIを触ったことがある方なら見慣れた内容かもしれませんが、keysにご自身のアカウント名やキー、トークンを設定してください。

Twitter APIはけっこうデータ取得制限が厳しいので、エラーをしょっちゅう吐きます。ので、そのあたりを処理してくれるようにパラメータをこんな感じで設定しておくと吉です。

ちなみにanalyze_worldは僕のTwitterアカウントのscreen_nameです。

ぜひフォローしてください。

import tweepy

def get_api():
    keys = dict(
        screen_name = 'analyze_world',
        consumer_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        consumer_secret = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        access_token =  'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        access_token_secret = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    )

    SCREEN_NAME = keys['screen_name']
    CONSUMER_KEY = keys['consumer_key']
    CONSUMER_SECRET = keys['consumer_secret']
    ACCESS_TOKEN = keys['access_token']
    ACCESS_TOKEN_SECRET = keys['access_token_secret']

    auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
    auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
    api = tweepy.API(auth,
                    retry_count=5,
                    retry_delay=10,
                    retry_errors=set([401, 404, 500, 503]),
                    wait_on_rate_limit=True)
    return api, SCREEN_NAME

Twitter APIはこの記事を書いたときに使ったのですが、新たにアカウントを作り直しました。

www.analyze-world.com

久しぶりにアカウントを発行したので、このサイトを参考にさせていただきました。

www.torikun.com

2-3. 自分のlike自動収集(auto_fav_collect.py)

さて、自分のlike履歴を自動収集するスクリプトです。これに関しては、APIの制限がとりわけ厳しいため、小分けにして収集が必要です。フォロー、アンフォローは1週間に1回で設定しますが、1週間分のlikeを取得しようとすると制限に引っかかるので、このスクリプトのみ毎日走らせることにしています。

全履歴を毎回取得するのは無駄の極みなので、前回の履歴からの差分だけを取得するようにしましょう。

あと、このスクリプトは制限がきつくてエラーになりやすいので、ログを貯めるようにしています。

import tweepy
import time
import csv
import pandas as pd
import datetime
from decimal import Decimal
import get_api

def myfavorites(api, my_userid):
    last_tweet_data = pd.read_csv('my_fav.csv')
    latest_id = max(last_tweet_data.id)
    
    try:
        tweet_data = []
        for tweet in tweepy.Cursor(api.favorites,
                                    screen_name = my_userid,
                                    count = 200,
                                    since_id = latest_id).items():
            tweet_data.append([Decimal(tweet.id),
                                Decimal(tweet.user.id),
                                tweet.created_at  + datetime.timedelta(hours=9)])
        print('succeed')
        #csv出力
        with open('my_fav.csv', 'a', newline='', encoding='utf-8') as f:
            writer = csv.writer(f, lineterminator='\n')
            writer.writerows(tweet_data)
        with open('error_log.csv', 'a', newline='', encoding='utf-8') as f:
            writer = csv.writer(f, lineterminator='\n')
            writer.writerow(list([pyfilename, 'succeed', datetime.datetime.now().isoformat()]))
        pass
    except:
        with open('error_log.csv', 'a', newline='', encoding='utf-8') as f:
            writer = csv.writer(f, lineterminator='\n')
            writer.writerow(list([pyfilename, 'error', datetime.datetime.now().isoformat()]))
        pass
        print('error')

if __name__ == "__main__":
    my_userid = 'analyze_world'
    pyfilename = 'auto_fav_collect.py'
    api, SCREEN_NAME = get_api.get_api()
    myfavorites(api, my_userid)

2-4. 自動アンフォロー(auto_unfollow.py)

パラメータは変更可能ですが、「直近90日間で自分からのlikeがなかった人のうち、ランダムに20人を毎週アンフォローする」仕様にしました。

自動フォローも毎週20人にしているため、全体のフォロー数には影響がありません。20人としたのは、今のフォロー数が400強なので、5%くらいは入れ替わらないと意味がないかなと思ったためです。

ここでは、先程の自動like収集の結果と、現時点での自分のフォローリストから、アンフォローの対象となる(直近90日間likeなし)人をリストアップし、その中から完全にランダムで20人を選び出します。

自分でアンフォローするときは胸が痛む僕でも、botがやってくれるなら安心です。

また、この仕組みを導入することで、likeは「この人のツイートは見続けたいときに押すもの」という確固たる定義が自分の中でできたのも収穫です。

import tweepy
import time
import csv
import pandas as pd
import datetime
import random
import get_api

def unfavorites(api, my_userid, unfav_days):
    following_id = api.friends_ids(my_userid)
    following_id_df = pd.DataFrame(following_id, columns={'user_id'})
    my_fav_df = pd.read_csv('my_fav.csv')
    my_fav_df = my_fav_df[pd.to_datetime(my_fav_df.created_at) > (datetime.datetime.now() - datetime.timedelta(days=unfav_days))]
    my_fav_df = my_fav_df.groupby('user_id', as_index=False).count()
    my_fav_df = pd.merge(following_id_df, my_fav_df, on='user_id', how='left').fillna(0)
    my_fav_df = my_fav_df[my_fav_df.created_at == 0].reset_index()
    l = list(my_fav_df.user_id)
    random.shuffle(l)
    return l

def unfollow(api, unfollowers, num_unfollow):
    unfollow_cnt = 0
    for unfollower in unfollowers:
        if unfollow_cnt <= num_unfollow:
            api.destroy_friendship(unfollower)
            time.sleep(2)
            unfollow_cnt += 1
        else:
            break

if __name__ == "__main__":
    my_userid = 'analyze_world'
    unfav_days = 90 #直近何日間LIKEしなかった人をアンフォロー対象にするか
    num_unfollow = 19 #一回で何人をアンフォローするか。20人なら19
    api, SCREEN_NAME = get_api.get_api()
    unfollowers = unfavorites(api, my_userid, unfav_days)
    unfollow(api, unfollowers, num_unfollow)

2-5. 自動フォロー(auto_follow.py)

自動フォローのスクリプトです。

どんな人を自動フォローの対象にするか、というのはいろいろ考えましたし、今回のやり方以外にも方法はあると思いますが、今回は、「自分がフォローしている人がフォローしている人」をランダムにピックアップすることにしました。

自分がフォローしている人がどんな人をフォローしているのか、どんなTLを普段見ているのか、気になるところですし、きっとそこには自分の知らない素晴らしいツイッタラーがいるはずです。

自分がフォローしている人全員を対象にして候補リストを作ってしまうと、リスト作りだけで時間がかかりすぎてしまいます。実際にフォローするのは20人だけなので、自分がフォローしている人の中からランダムに1人を選定し、その人がフォローしている人リストを取得、その中から20人を選びます。

import tweepy
import time
import csv
import pandas as pd
import datetime
import random
import get_api

def follow_search(api, my_userid, pyfilename, num_follow_search):
    try:
        following_id = api.friends_ids(my_userid)
        random.shuffle(following_id)
        followings = api.friends_ids(following_id[num_follow_search])
        with open('error_log.csv', 'a', newline='', encoding='utf-8') as f:
            writer = csv.writer(f, lineterminator='\n')
            writer.writerow(list([pyfilename, 'succeed', datetime.datetime.now().isoformat()]))
        return followings
    except:
        with open('error_log.csv', 'a', newline='', encoding='utf-8') as f:
            writer = csv.writer(f, lineterminator='\n')
            writer.writerow(list([pyfilename, 'error', datetime.datetime.now().isoformat()]))
        pass

def follow(api, my_userid, pyfilename, followings, num_follow):
    following_id = api.friends_ids(my_userid)
    l = list(set(followings) - set(following_id))
    random.shuffle(l)

    follow_cnt = 0
    for following in l:
        if follow_cnt <= num_follow:
            follow_cnt += 1
            try:
                api.create_friendship(user_id = following, follow = False)
                time.sleep(2)
                with open('error_log.csv', 'a', newline='', encoding='utf-8') as f:
                    writer = csv.writer(f, lineterminator='\n')
                    writer.writerow(list([pyfilename, 'succeed', datetime.datetime.now().isoformat()]))
                pass
            except:
                with open('error_log.csv', 'a', newline='', encoding='utf-8') as f:
                    writer = csv.writer(f, lineterminator='\n')
                    writer.writerow(list([pyfilename, 'error', datetime.datetime.now().isoformat()]))
                pass

if __name__ == "__main__":
    my_userid = 'analyze_world'
    pyfilename = 'auto_follow.py'
    num_follow_search = 0 #何人のフォローしている人のフォローを探索するか。1人なら0
    num_follow = 19 #何人のフォローを一度に増やすか。20人なら19
    api, SCREEN_NAME = get_api.get_api()
    followings = follow_search(api, my_userid, pyfilename, num_follow_search)
    follow(api, my_userid, pyfilename, followings, num_follow)

ちなみに、このスクリプトで3人分、自動フォローしてみた結果。

f:id:shokosaka:20200103232642j:plain
auto_follow(3人分)の結果

「おおお、見たことないけど、プロフィール的にはすごくよさそう」って感じの3アカウントでした。ので、たぶんこれはイケる。

2-6. 定期実行

スクリプトができあがったら、これをタスクスケジューラで定期実行設定します。僕はWindows使いなので。

この記事を参考にしました。

www.atmarkit.co.jp

ちなみに、僕はpython関連のライブラリはcondaで入れているのですが、環境変数のpathを設定してなくてかなり詰まったので、ご注意ください。

タスクスケジューラの「操作」タブ、プログラム/スクリプトに「C:\Users\ユーザー名\AppData\Local\Continuum\anaconda3\python.exe」を入れ、このpython.exeからライブラリが入ったフォルダまでのパスが通っておらず動かなかったのですが、以下を追加してなんとかなりました。

C:\Users\ユーザー名\AppData\Local\Continuum\anaconda3\Library\mingw-w64\bin
C:\Users\ユーザー名\AppData\Local\Continuum\anaconda3\Library\pkgs
C:\Users\ユーザー名\AppData\Local\Continuum\anaconda3\Library\bin
C:\Users\ユーザー名\AppData\Local\Continuum\anaconda3\Scripts
C:\Users\ユーザー名\AppData\Local\Continuum\anaconda3\condabin

3. まとめ

はい、ということで、今後、このbotを使っていきます!決意は一切不要!年明けからいい仕事ができました。

noteも書いてるので、ぜひー。
note.com