tweepy(もしくはrequests-oauthlib)とsqlite3でフォロー状況を見る
だいぶ前に、遊びで使っているtwitterアカウントでリフォローをしようとしたときに、
フォロー制限のためにリフォローができなかった。
ということで、python用のtwitterライブラリtweepy
を使って、
sqlite3
のデータベースにフォロー状況を書き出すものを作った。
個人的にはsqlite3
などのRDBの勉強としての意味合いもあった。
環境
- python -
3.5.2
- tweepy -
3.5.0
- sqlite3
tweepyを使う準備
tweepyを介して、twitterのAPIにアクセスするには、
consumer key
とconsumer secret
access token
とaccess secret
が必要になるので、https://apps.twitter.com/でアプリケーション登録をして、トークン等を準備する。
friendsとfollowersを取得
大抵のWebAPIにはアクセス制限が付きものなので、まずはそのあたりを調べた。 なお、以下のサイトを参考にした。
- tweepy で フォローした人をリストアップする / 3846masa's memo
- API Reference - tweepy 3.5.0 documentation
- GET friends/ids | Twitter Developers
- GET followers/ids | Twitter Developers
api.friends_ids
とapi.followers_ids
は、それぞれ5000ids/回を取得でき、15回/15分のリミットなので、
フォロー数が1万を超えていても安心という数を捌ける。
以下のコードで、自アカウントがフォローしているfriends、自アカウントをフォローしているfollowersを取得できる。
import tweepy CONSUMER_KEY = "CONSUMER_KEY" CONSUMER_SECRET = "CONSUMER_SECRET" ACCESS_TOKEN = "ACCESS_TOKEN" ACCESS_SECRET = "ACCESS_SECRET" auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET) api = tweepy.API(auth) friends_ids = [] for _id in tweepy.Cursor(api.friends_ids).items(): friends_ids.append(_id) followers_ids = [] for _id in tweepy.Cursor(api.followers_ids).items(): followers_ids.append(_id)
実際に使ったコードでは、この後にそれぞれのListをSetにして、set.union
をとり、相互フォローを出す処理なども入れていた。
追記:2016-10-27 ここから
今日、何気なくQiitaを見てて、tweepy がメンテナンスされなくなった
という記事を見かけた。「あれっ」と思って、tweepyのgithubページを見たら、
本当に「もうメンテしない」って書いてた。
近い将来、twitterAPIの仕様変更などで動かなくなることは目に見えてるので、requests-oauthlib
を使って書き直したものを以下に載せる。
from requests_oauthlib import OAuth1Session import json twitter = OAuth1Session(CONSUMER_KEY, client_secret = CONSUMER_SECRET, resource_owner_key = ACCESS_TOKEN, resource_owner_secret = ACCESS_SECRET) friends_ids = [] cursor = -1 while cursor: req = twitter.get('https://api.twitter.com/1.1/friends/ids.json?cursor={}'.format(cursor)) if req.status_code == 200: data = json.loads(req.text) friends_ids.extend(data['ids']) cursor = data['next_cursor'] else: print("request error : {}".format(req.status_code)) break followers_ids = [] cursor = -1 while cursor: req = twitter.get('https://api.twitter.com/1.1/followers/ids.json?cursor={}'.format(cursor)) if req.status_code == 200: data = json.loads(req.text) followers_ids.extend(data['ids']) cursor = data['next_cursor'] else: print("request error : {}".format(req.status_code)) break
以下のページが参考になった。
- request-oauthlib/github
READMEにtwitterAPIのアクセスが例として挙げられてるので、そのまま転用できる。 - Using cursors to navigate collections
tweepyではcursor
がラップされちゃってるので… - https://dev.twitter.com/overview/api/response-codes
ここもラップされてるが故にと言ったところでしょうか。ちゃんとループの条件としては入れておかないと、攻撃しちゃいますからね。
追記:2016-10-27 ここまで
取得したfriendsとfollowersをデータベースに保存
そして、sqlite3のデータベースに保存しておく。 以下のコードは上のコードに続くような形で書いている。
import sqlite3 con = sqlite3.connect("{}.db".format("db_name")) cur = con.cursor() cur.execute("select * from sqlite_master where type='table'") tables = [] for item in cur.fetchall(): tables.append(item[1]) if "friends" not in tables: con.execute("create table friends(user_id integer primary key not null unique, friend integer, follower integer)") for _id in friends_ids: con.execute("replace into friends(user_id, friend) values({}, {})".format(_id, 1)) for _id in followers_ids: con.execute("insert or ignore into friends(user_id, follower) values({}, {})".format(_id, 1)) con.execute("update friends set follower = {} where user_id = {}".format(1, _id)) con.commit() con.close()
followersのほうの処理で面倒なことになっているのは、sqlite3のreplace
がdelete処理を含んでしまうので、
insert or ignore
とupdate
を組み合わせるという方法を採っているから。
これはStack Overflowあたりで見かけたような。
データベースを見る
あとは、sqlite3の操作でフォロー状況を確認できる。
データベースを指定して開く
sqlite3 db_name.db
相互フォロー数を出す
select count(*) from friends where friend = 1 and follower = 1;
リフォローしていない数を出す
select count(*) from friends where friend = 0 and follower = 1;
最後に
いかにも業者なアカウントのフォローは避けたいものです。
ただし、idからそのような判別はできないので、tweepyのapi.lookup_users()
で
screen_name
を引っ張てきて、リフォロー用の即席htmlページを書き出したりもしていた。
あと、この関数の説明がtweepyドキュメントになくて、
githubで確認したのを思い出した。
オンラインジャッジでの Python Tips
最近、時間があるときにオンラインジャッジの競技プログラミングもどきをやっている。 オンラインジャッジでは、入出力がCLIで行われるため、 入出力周りで何度となく同じようなコードを書く。 しかし、1ヶ月くらい別の言語をいじってたりすると、 そのようなコードを書くのに少し時間が掛かってしまう。 ということで、よく使う(使った)ものをまとめておこうと思った。
input系
単純に入力が数値のときには、input()
から得られたstrをintに変換しておくと、
後で数値演算したときにエラーを吐かれることがない。
int_single = int(input()) # <= 1 print(int_single) # => 1
スペース区切りで数値が複数渡されたときは、strのsplit()
で分割して、
intに変換しておく。これで、ループに使うときも変換がいらない。
多重代入もよく使う。
int_list = [int(x) for x in input().split()] # <= 1 2 3 print(int_list) # => [1, 2, 3] a, b, c = [x for x in input().split()] # <= test1 test2 test3 print(a, b, c) # => test1 test2 test3
上と同じ1行スペース区切りだが、数値文字列だけではなく、
通常の文字列も含まれていて、そのままint
に投げてしまうとエラーになる。
そんなときは、内包表記内に三項演算子で条件分岐処理を入れ込む。
strのisdigit()
は結構使う。
# <= l1 10 20 30\nl2 10 20 30 labeled_list = [[int(x) if x.isdigit() else x for x in input().split()] for i in range(2)] print(labeled_list) # => [['l1', 10, 20, 30], ['l2', 10, 20, 30]]
複数行で文字列が入力されるときは、range()
ループで必要な回数分、
ループを回せばOK!
str_list = [input() for i in range(3)] # <= 1\n2\n3 print(str_list) # => [1, 2, 3]
複数行の入力がある場合かつ終了条件がある場合、
これは諦めて、while True
ループを書いちゃってる。
input_text_list = [] while True: input_text = input() # <= test1\ntest2\ntest3\ntest4\ninput end. if input_text == "input end.": break input_text_list.append(input_text) print(input_text_list) # => ['test1', 'test2', 'test3', 'test4']
loop系
whileループやforループのelse
句はよく使う。
フラグ処理が省略できるので、楽々。
cnt = 0 while True: cnt += 1 for i in range(10): cnt += 1 if cnt > 100: break else: print(cnt) continue print("last cnt: {}".format(cnt)) break # => 11\n22\n33\n44\n55\n66\n77\n88\n99\nlast cnt: 101
zip()
は複数のリストやタプルをまとめてループできる。
それと、内包表記の多重ループ。平面グリッド走査のような全ての組み合わせでループを回すときなどで使える。
あと、文字列のjoin()
もよく使う。
upper_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" lower_str = "abcdefghijklmnopqrstuvwxyz" upper = ["{}:{}".format(u, ord(l)-32) for u,l in zip(upper_str, lower_str)] lower = ["{}:{}".format(u.lower(), ord(l)) for u in list(upper_str) for l in list(lower_str) if u == l.upper()] print("\n".join(upper)) # => A:65\nB:66\nC:67\n … print("\n".join(lower)) # => a:97\nb:98\nc:99\n …