GAE × Python2.7 × Twitter Api1.1でtwitterの自動フォロワー管理ツールをつくりました。
最初フォロー・フォロー削除共に1日480件という設定にしていたら、アップして数時間で速やかにtwitterからアカウント凍結されました。。まあさすがに1日48件なら大丈夫だろうと思ってるんですが、2回目以降凍結されると復活が困難らしいので、近日中にtwitterで新たな船出を迎えるかもしれません。
機能は下記になります。自分的にはかなり素早く作った方で、仕事終わりに半分寝ながらつくっていたので抜け漏れが結構ある可能性があります。今のところこれで特に問題なく動いているのですが、稼働させたばかりなので根本的なミスをしている可能性もあります。
- 設定したキーワードをつぶやいている人を自動でフォローするキーワードは3つまで設定できる)
- 1日当たりのフォロー数も自由に設定可能(様々なリスクがあるので最大100件まで)
- フォローしてから一定期間経ってもリフォローされない場合は、フォロー解除する
- フォロー解除までの日数も自由に設定可能
- 1日当たりのフォロー解除数も自由に設定可能(同じく最大100件まで)(でも100件でも十分凍結対象になりうるらしい。)
- フォロー解除したくないアカウントは、twitterのリストに登録しておけば、フォロー解除対象から外れる
- フォローする際は、以前フォロー削除したアカウントかどうかを確認して、以前のフォロー削除から一定期間が経過していない場合はフォローしない
このぐらい。リフォロー機能はまだない。
今回はtweepyという、python用のtwitter API操作モジュールを使いました。ただtwitter API 1.1 にtweepyが対応していなかったのですが、こちらでsakitoさんという人がtweepy2というのを作ってくれていたのでこれを使わせていただきました。今回作った限りでは問題なく動いてます。ありがたいです。
ソースコードは下記です。
# -*- coding: utf-8 -*- import os import webapp2 import jinja2 import cgi from google.appengine.ext import db import re import math import datetime import urllib import tweepy2 jinja_environment = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) CK = 'twitterのコンシューマーキー' CS = 'twitterのコンシューマーシークレット' AT = 'twitterのアクセストークン' ST = 'twitterのシークレットトークン' #your screen name yourScreenName = 'twitterの自分のスクリーンネーム' #1時間当たりのAPI呼び出し回数 callApiOneHour = 1 #1回当たり最大サーチ数(削除時) maxDeleteSearchCnt = 3 #1回当たり最大サーチ数(フォロー時) maxFollowSearchCnt = 5 #削除後の再フォローまでの日数 oneMoreFollowTime = 30 #database class Database(db.Model): protects = db.ListProperty(int) friendKey1 = db.StringProperty() friendKey2 = db.StringProperty() friendKey3 = db.StringProperty() friendKeyFlag = db.IntegerProperty() numOnedayF = db.IntegerProperty() numOnedayD = db.IntegerProperty() deleteTime = db.IntegerProperty() follow1day = db.ListProperty(int) follow2day = db.ListProperty(int) follow3day = db.ListProperty(int) follow4day = db.ListProperty(int) follow5day = db.ListProperty(int) followTime = db.DateProperty() class DeleteAccountData(db.Model): deleteTime = db.DateProperty() class MainHandler(webapp2.RequestHandler): def get(self): api = createClient() #create twitter client [lovesCnt,onlyFriendsCnt,onlyFollowersCnt] = countLoveFriendFollower(api) templateValues = { 'lovesCnt': lovesCnt, 'onlyFriendsCnt': onlyFriendsCnt, 'onlyFollowersCnt': onlyFollowersCnt, } #read database data = db.get(db.Key.from_path('Database', 'endo')) if data: datas = { 'pro': data.protects, 'fk1': data.friendKey1, 'fk2': data.friendKey2, 'fk3': data.friendKey3, 'nOF': data.numOnedayF, 'nOD': data.numOnedayD, 'dt' : data.deleteTime } templateValues.update(datas) template = jinja_environment.get_template('index.html') self.response.out.write(template.render(templateValues)) def post(self): api = createClient() #create twitter client fk1 = cgi.escape(self.request.get('fk1'), True) fk2 = cgi.escape(self.request.get('fk2'), True) fk3 = cgi.escape(self.request.get('fk3'), True) nOF = self.request.get('nOF') nOD = self.request.get('nOD') dt = self.request.get('dt') p = re.compile('^d+$') if p.match(nOF) and p.match(nOD) and p.match(dt): nOF = int(nOF) nOD = int(nOD) dt = int(dt) if (nOF >= 1 and nOF <= 100) and (nOD >= 1 and nOD <= 100) and (dt >= 1 and dt <= 5): #Input DB data = db.get(db.Key.from_path('Database', 'endo')) if not data: data = Database(key_name='endo') data.friendKey1 = fk1 data.friendKey2 = fk2 data.friendKey3 = fk3 data.numOnedayF = nOF data.numOnedayD = nOD data.deleteTime = dt data.put() self.redirect('/') errmsg = 'Oooops!!!<br />friends of one day is 1 - 100<br />' errmsg += 'Deletes of one day is 1 - 100<br />' errmsg += 'Time of Delete is 1 - 5<br />' data = db.get(db.Key.from_path('Database', 'endo')) pro = '' if data: pro = data.protects [lovesCnt,onlyFriendsCnt,onlyFollowersCnt] = countLoveFriendFollower(api) templateValues = { 'lovesCnt': lovesCnt, 'onlyFriendsCnt': onlyFriendsCnt, 'onlyFollowersCnt': onlyFollowersCnt, 'pro': pro, 'fk1': fk1, 'fk2': fk2, 'fk3': fk3, 'nOF': nOF, 'nOD': nOD, 'dt' : dt, 'errmsg': errmsg } template = jinja_environment.get_template('index.html') self.response.out.write(template.render(templateValues)) class Follow(webapp2.RequestHandler): def get(self): api = createClient() #エラーの場合のメッセージ templateValues = { 'errmsg': 'ERROR!!! you must put "Friend keyword 1" and "Friends of one day".' } #データ読み込み data = db.get(db.Key.from_path('Database', 'endo')) #データがある場合のみ実行する if data: #friendKey1とnumOnedayFがある場合のみ実行する if not (data.friendKey1 == '' or data.numOnedayF == ''): #キーワードを読み込む fk1 = data.friendKey1 fk2 = data.friendKey2 fk3 = data.friendKey3 #キーワードフラグを読み込む flag = data.friendKeyFlag if flag == '': flag = 1 #次のフラグと今回のキーワードを設定する if not fk2: nextFlag = 1 keyword = fk1 else: if flag == 1: nextFlag = 2 keyword = fk1 else: if not fk3: nextFlag = 1 keyword = fk2 else: if flag == 2: nextFlag = 3 keyword = fk2 else: nextFlag = 1 keyword = fk3 #最大フォロー件数を算出する maxFollowCnt = round(data.numOnedayF / 24 / callApiOneHour) #検索 tweetList = api.search_tweets(urllib.quote(keyword.encode('utf-8'))) #フォロー数カウンター followCnt = 0 #フォロワーの取得 followers = api.followers_ids() #今日の取得 today = datetime.date.today() #一定期間前の日付の取得 baseDay = datetime.timedelta(days = oneMoreFollowTime) #1日の取得 oneDay = datetime.timedelta(days = 1) for idx in range(0,maxFollowSearchCnt): #すでにフォローしてないか? if not tweetList[idx].user['id'] in followers: #デリート後一定期間が経っているか?(デリートしたことがあるか?) deleteData = db.get(db.Key.from_path('DeleteAccountData', str(tweetList[idx].user['id']))) if (deleteData and (deleteData.deleteTime < (today - baseDay))) or (not deleteData): #フォローする api.create_friendship(tweetList[idx].user['id']) #followTimeがない場合は今日の日付を登録 if not data.followTime or data.followTime == 'null': data.followTime = today #ある場合、followTimeが1日経過していないか? else: if today >= data.followTime + oneDay: #経過している場合は、followリストを移動させる data.follow5day = data.follow4day data.follow4day = data.follow3day data.follow3day = data.follow2day data.follow2day = data.follow1day #経過している場合は、followタイムを上書きする data.followTime = today #フォローデータをfollow1dayに追加 data.follow1day += [tweetList[idx].user['id']] #followCntを追加する followCnt += 1 #followCntがmaxFollowCntに達した場合終了する if followCnt >= maxFollowCnt: break #次のフラグの登録 data.friendKeyFlag = nextFlag #data.putする data.put() templateValues = { 'msg': 'OK! ' + str(followCnt) + ' followed.', } template = jinja_environment.get_template('follow.html') self.response.write(template.render(templateValues)) else: template = jinja_environment.get_template('follow.html') self.response.write(template.render(templateValues)) else: template = jinja_environment.get_template('follow.html') self.response.write(template.render(templateValues)) #onlyFriendsの中でListに登録されておらず、規定時間内にフォローしてもいない #アカウントのフォローを削除する class Delete(webapp2.RequestHandler): def get(self): api = createClient() #データの読み込み data = db.get(db.Key.from_path('Database', 'endo')) #エラーの場合のメッセージ templateValues = { 'errmsg': 'ERROR!!! you must put "Deletes of one day" and "Time of delete".' } #データがある場合のみ実行 if data: #1日当たり削除件数と削除までの規定日数が登録されているか場合のみ実行 if not (data.numOnedayD == '' or data.deleteTime == ''): #最大削除数(規定値)の算出 maxDeleteCnt = round(data.numOnedayD / 24 / callApiOneHour) #onlyFriendsのidリストの取得 onlyFriends = getOnlyFriendsList(api) #onlyFriendsからprotectsメンバーを削除 onlyFriendsSet = set(onlyFriends) protectsSet = set(data.protects) nonProtectsSet = onlyFriendsSet.difference(protectsSet) #削除規定日を取得して、規定日に応じて日別フォローリストを読み込み結合する if data.deleteTime == 1: followList = data.follow1day elif data.deleteTime == 2: followList = data.follow1day + data.follow2day elif data.deleteTime == 3: followList = data.follow1day + data.follow2day + data.follow3day elif data.deleteTime == 4: followList = data.follow1day + data.follow2day + data.follow3day + data.follow4day else: followList = data.follow1day + data.follow2day + data.follow3day + data.follow4day + data.follow5day #nonProtectsSetから規定日内フォローリストを削除 canDeleteList = list(nonProtectsSet.difference(set(followList))) #今日の取得 today = datetime.date.today() deleteCnt = 0 for idx in range(0,maxDeleteSearchCnt): #Listメンバーか否かを確認 user = api.is_list_member(yourScreenName,'hoge',canDeleteList[idx]) #リスト登録されていない場合 if not user: #削除 api.destroy_friendship(canDeleteList[idx]) #DeleteDataにdeleteTimeを登録 deleteData = DeleteAccountData(key_name = str(canDeleteList[idx])) deleteData.deleteTime = today deleteData.put() #削除カウント+1 deleteCnt += 1 if deleteCnt >= maxDeleteCnt: break #リスト登録されているのでprotectに登録する else: #protectsへのuserIDの追加 data.protects += [canDeleteList[idx]] #データ登録 data.put() templateValues = { 'msg': 'OK! ' + str(deleteCnt) + ' deleted.', } template = jinja_environment.get_template('delete.html') self.response.write(template.render(templateValues)) else: template = jinja_environment.get_template('delete.html') self.response.write(template.render(templateValues)) else: template = jinja_environment.get_template('delete.html') self.response.write(template.render(templateValues)) def countLoveFriendFollower(api): followers = set(api.followers_ids()) #get follower friends = set(api.friends_ids()) #get friends loves = followers.intersection(friends) #friend && follower onlyFriends = friends.difference(followers) #only friend onlyFollowers = followers.difference(friends) #only follower return [len(loves),len(onlyFriends),len(onlyFollowers)] def createClient(): auth = tweepy2.OAuthHandler(CK, CS) auth.set_access_token(AT, ST) return tweepy2.API(auth_handler=auth) #onlyFriendsのidリストを抽出しソートする def getOnlyFriendsList(api): followers = set(api.followers_ids()) friends = set(api.friends_ids()) onlyFriends = list(friends.difference(followers)) onlyFriends.sort() return onlyFriends app = webapp2.WSGIApplication([ ('/', MainHandler), ('/delete',Delete), ('/follow',Follow), ('/test',Test), ], debug=True)