はてブロ@ama_ch

https://twitter.com/ama_ch

python-twitterで遊んでみた 基本編

python-twitterで遊んでみたよ!とりあえず基本的な使い方のお勉強。
ちなみにドキュメントはここにあるからね!
http://static.unto.net/python-twitter/0.5/doc/twitter.html
モジュールの機能自体シンプルだから、ドキュメントも短くて読みやすいです。僕の英語力だと半分くらいしか理解できないけど><


まずはimportから

>>> import twitter
>>> api = twitter.Api("ユーザー名", "パスワード")

とりあえずアカウント情報を登録してあげます。これをしないと、色んな機能が使えません。


フレンドリストを取得

users = api.GetFriends()
>>> for cnt, i in enumerate(users):
...     print cnt, i.name
... 
0 nipotan
1 yusukebe
2 はまちや2
--- 中略 ---
97 fuji
98 ポリタンク
99 すぴ☆すた

多分自分がフォローしてる人を取得できます。
あれ、100人しか取得できない?ドキュメントのGetFriends()には

Returns:
A sequence of twitter.User instances, one for each friend

と書かれてる。one for each friendってなんだ!
よくわからないので直接モジュール内のGetFriends()関数を定義しているところを見る

  def GetFriends(self, user=None):
    if not self._username:
      raise TwitterError("twitter.Api instance must be authenticated")
    if user:
      url = 'http://twitter.com/statuses/friends/%s.json' % user
    else:
      url = 'http://twitter.com/statuses/friends.json'
    json = self._FetchUrl(url)
    data = simplejson.loads(json)
    return [User.NewFromJsonDict(x) for x in data]

うん、http://twitter.com/statuses/friends.jsonから読み込んでるのはわかった。
それではアドレスを開いてみます!

[{"name":"nipotan","screen_name":"nipotan","description":"\u30db\u30eb\u30e2\u30f3\u597d\u304d","location":"San Jose, CA, USA","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/52311334\/kao_normal.gif","url":"http:\/\/iddy.jp\/profile\/nipotan\/","id":1410371,"followers_count":2066,"protected":false,"status":{"text":"\u3042\u3042\u3001\u7bb1\u6839\u306e\u6e29\u6cc9\u306b\u884c\u304d\u305f\u3044\u3002","created_at":"Sun May 18 21:05:54 +0000 2008","id":814421334}},{"name" ...中略... reated_at":"Sun May 18 16:35:37 +0000 2008","id":814283215}}]

おうふ、意味わかんねw
とりあえず色んなステータスがまとめて書かれてるな。固有の属性の数をカウントすれば何人かわかりますね!
"name"(ダブルクォーテーション含む)の数を数えるスクリプトを書いてみた。汚い><

#!usr/bin/env python
# -*- coding: utf-8 -*-
import fileinput

cnt = idx = 0
target = '"name"'

for line in  fileinput.input():
    while True:
        if line.find(target, idx) >= 0:
            cnt += 1
            idx = line.find(target, idx) + 1
        else:
            break
print "%s は %d 個みつかりました。" % (target, cnt)

実行結果

$ python test.py friends.json
"name"100 個みつかりました。

やっぱり100人しか取得できないみたい! ・・・あ、検索したらAPIの仕様書なんてあるんですね><
Twitter API 仕様書 (勝手に日本語訳シリーズ)
ありがたく使わせていただきます。仕様書によると、

friends
自分の friend の一覧を(各 friend の最新ステータス付きで)取得する
引数 id を指定すれば、その id のユーザの friend の一覧を取得できる
ただし、この API で取得できるデータは最大100件(100人分)である

やっぱり100人までなんだ・・・てか最初にこれ見つけてればさっきのスクリプト書く手間がはぶけたのに><


お、気になる項目発見!

page=ページ番号 (オプション)
(1ページを100件とみなしたときの)ページ番号を指定することで、指定ユーザの friend の一覧を100件単位で取得する

例:
http://twitter.com/statuses/friends.xml?page=2
API実行時点で101件目から200件目に相当する(自分の)friend の一覧を XML 形式で取得する

?page=のパラメータを与えてあげれば、101件目以降のデータを取得できそうです!
でも先述のモジュールの実装を見ると、?page=のパラメータを与えて取得は無理っぽいですね。


・・・という訳で、書いてみました。相変わらずその場しのぎできたない><

class TwitterApi(Api):
  def GetFriends(self, pageno=1, user=None):
    if user:
      url = 'http://twitter.com/statuses/friends/%s.json' % user
    elif pageno > 1:
      url = 'http://twitter.com/statuses/friends.json?page=%d' % pageno
    else:
      url = 'http://twitter.com/statuses/friends.json'
    json = self._FetchUrl(url)
    data = simplejson.loads(json)
    return [User.NewFromJsonDict(x) for x in data]

はあはあ、メソッドのオーバーライドの仕方がわからなくて超時間かかった・・・
ええと、Apiクラスから継承して、元のメソッドに引数(整数値)をひとつ足して、ユーザー名を指定しないで整数値を与えたときに?page=整数値からデータを取得するようにしました。とりあえず自分が使う用。
import twitterで読み込むモジュールファイル「twitter.py*1」の最後に、このソースを付け足します。
そして、以下のように実行すれば全フレンドリストが取得できます!

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import twitter

page = 2  #何ページまで取得するか
cnt = 1

# オーバーライドしたクラスからインスタンスを作成
api = twitter.TwitterApi("ユーザー名", "パスワード")
for i in range(page):
    users = api.GetFriends(i+1)
    print "page:%d" % (i+1)
    for  user in users:
        print "%3d %s  %s" % (cnt, user.id, user.name)
        cnt += 1

実行結果

$ python p-twitter.py
page:1
  1 1410371  nipotan
  2 3216921  yusukebe
  3 3314811  はまちや2
--- 中略 ---
 98 11408572  fuji
 99 11408892  ポリタンク
100 11543272  すぴ☆すた
page:2
101 11595102  ちゃっぴー
102 11747282  黒猫
103 12038372  yapcasia
--- 中略 ---
123 14682848  camelmasa
124 14712428  Hisato
125 14742523  ottu

できた!!!
何ページまで取得するかを自分で指定するんじゃなくて、プログラム側で判断するように作った方がスマートな処理ですね。余力があったら書いてみます><


とにかくGetFriends()フレンドリストを取得できることがわかりました。
次はタイムラインの取得をしてみます。

>>> f_timeline = api.GetFriendsTimeline()
>>> for i in f_timeline:
...     print"%s: %s" % (i.user.name, i.text)
... 
りあん: ちぃはまだ関西にいるのか
mmk_chocolate: いや,そうなんだけどw@Misho に大昔譲ったThinkPadはR51だっけ.持ち運ぶのが苦##ならないのが欲しいんだよね(´・ω・`)
Michitaka: 月島もんじゃオフやろうかな。
Misho: あー,今日3限はあるのか。めんどいなぁ。あの講義切ろうかなw
せんさん: 根津駅なう
いけしょー: 昼飯はパン
陽坂智佐: スペースキーの反応が悪くなった
ななき: 考えごとをしていたら挙動不振な人が隣に座った。な、なんなんですか?どうしてこの人は不安を煽るようなリズムで胸を叩いてるんですか><
UK: 久々に猫と戯れたい感じだ
じゅみたか: [B!] Big Sky :: twitterユーザに対してタグ付け出来るウェブサービス「Tagtter」作った。 http://tinyurl.com/3eo7cc
じゅみたか: [B!] ゲスト:アニメ会、比嘉さん  #001  ノトフの部屋 Aパート‐ニコニコ動画(SP1) http://tinyurl.com/5gzgpo
じゅみたか: [B!] 単純な疑問を「知るかボケ」で流せば、思考は硬直化する - good2nd http://tinyurl.com/5r5lb3
ama_ch: 結局再帰で書けなかった ふがーいないやいや〜
kiyoya: @yuyarin くわしく
やおっち: 皆がんばってんなぁ。俺何もできてないぞー
ななき: 電車内の暇潰しアイテムを忘れた。手帳片手に思索にふけるしかないな。
satzz: YAPCいってないけどYAPC++
やおっち: 10分ほど寝落ち→回復。眠いときに英語読むとだめだ
星一: Something Else って人たちがそういえばいたな
Hisato: ガッコ行こう。ガソリン詰めてかなきゃ

上が最新。自分が普段眺めてるのと同じタイムラインが取得できます。


次はパブリックタイムライン(全体の最新発言)を取得してみます。

>>> p_timeline = api.GetPublicTimeline()
>>> for i in p_timeline:
...     print"%s: %s" % (i.user.name, i.text)
... 
CyberMagic: うちのインターネットが繋がらなくなった件[mb-web]
Diego Leal: @Nervioso Yup, Sunny Rio... LOL   :-D
haru.fm: 昨晩はウコンの力を飲み忘れて、今朝ひどい目にあったorz
もっと大人の飲み方を勉強しますorz orz
 http://tinyurl.com/3ozmne
C @ FoodieTots: BBQ tonight, rhubarb mojitos were a hit. Peanut satay chicken, salmon, chips & dips, strawberry rhubarb crisp.
Tracy: welcome mat at target: Thanks for swimming by. With pictures of fish on it.
DanP_70: Nite all
Anfetaminico :.: El concierto es en Chile
The Duke: @gordonhall ..eating  BLT for dinner @lukemundy if it were only as good as a RR Peppercorn burger
Laura Savastinuk: flickring
jam5201: 沉痛悼念汶川地震中的遇难同胞
Chris: If my cable or broadband isn't back when I get home...I don't know what i'll do tonight. [I could clean my house, I guess]
haru.fm: 150円で焼きそばロール開始〜
 http://tinyurl.com/4quqtl
Kittysafe: Evil Dead is a classic I agree.  Recommendations...   The Bridge on the River Kwai, C'era una volta il West, The Deer Hunter,
Daniel Fuller: playing a little Halo 3 before bed
aschek: @MLKtoSCL yo llevo 2 años con mi Motorola Q con Widnows Mobile 2005 que la verdad me gusta.... pero estoy muy viejo...
haru.fm: 今日は昼当番だったか。ゼミに間に合うかなぁ。
 http://tinyurl.com/3vl3om
bpfurtado: Created the RAIMBO project on Netbeans, after trying 4 times... Eclipse UI is much more intuitive to create new projects from SCMs.
Benjamin Satterfield: as Neven said, wherecamp st Google was in tents!
teddyblass: Indiana Jones and the Temple of Doom time!
Johnline: Watching how to podcasts

やっぱり外人さんが多いですね!


最後は自分の発言履歴を取得してみます。

>>> my_timeline = api.GetUserTimeline()  #ここでユーザー名を指定することもできる
>>> for i in my_timeline:
...     print"%s: %s" % (i.created_at, i.text)
... 
Mon May 19 03:13:45 +0000 2008: 結局再帰で書けなかった ふがーいないやいや〜
Mon May 19 02:45:33 +0000 2008: pythonで再帰処理が書けそうで書けない
Mon May 19 02:45:21 +0000 2008: @msax わかりません><
Mon May 19 02:31:24 +0000 2008: find を何度も fineと書いてしまう。元気です!
Mon May 19 02:12:31 +0000 2008: とりあえず項目の集計してみよう
Mon May 19 02:12:20 +0000 2008: friends.jsonというファイルを読む
Mon May 19 01:53:52 +0000 2008: @msax モジュールの説明で、「A sequence of twitter.User instances, one for each friend」を返すらしいんだけど、文脈がよくわからないんだ!
Mon May 19 01:46:21 +0000 2008: 急募:one for each friend の意味。「友達全員」では違う?
Mon May 19 01:15:57 +0000 2008: 今までインタラクティブシェルを一々中断してたけど、iTermで新##いTab開けばいいじゃないか。baka!
Mon May 19 01:10:24 +0000 2008: 正規表現を勉強しないとマズイなぁ
Mon May 19 01:06:12 +0000 2008: 研究室とーちゃく。
Sun May 18 23:01:06 +0000 2008: 【急募】はてなダイアリーのバックアップの保存場所
Sun May 18 23:00:40 +0000 2008: 昨日日記を編集途中で外出したまま忘れてて、今見たら見事に消えてるorz
Sun May 18 22:53:19 +0000 2008: おはよーございます
Sun May 18 12:22:16 +0000 2008: 試飲しすぎて3時間も寝てしまった  買ってきた酒が飲めない
Sun May 18 07:19:04 +0000 2008: 東光の酒蔵資料館に来ている  日の高いうちから日本酒うめぇwwww
Sun May 18 05:41:39 +0000 2008: いったん終わり。おでかけすーるだ。
Sun May 18 05:40:23 +0000 2008: 無限ループで時間取得しながら時報っぽいことやってたらやはりCPU使用率100%でわろたw だめぽ
Sun May 18 04:48:35 +0000 2008: ナウでヤングなロリポップ!
Sun May 18 04:24:15 +0000 2008: 自前サーバはいやだしなぁ

今度は名前:発言内容じゃなくて、投稿日時:発言内容という風に出力しています。
・投稿時刻について
時刻はGMT(グリニッジ標準時)で表示されているので、日本での時刻とは違います。日本での時刻にするには、+9時間します。例えば最新の03:13:45は、12:13:45です。
あと、出力時にi.created_atをi.relative_created_atにすると、こんな風に出力できます

about 35 minutes ago: 結局再帰で書けなかった ふがーいないやいや〜
about an hour ago: pythonで再帰処理が書けそうで書けない
about an hour ago: @msax わかりません><
about 1 hours ago: find を何度も fineと書いてしまう。元気です!
--- 中略 ---
about 15 hours ago: 試飲しすぎて3時間も寝てしまった  買ってきた酒が飲めない
about a day ago: 東光の酒蔵資料館に来ている  日の高いうちから日本酒うめぇwwww
about a day ago: いったん終わり。おでかけすーるだ。
about a day ago: 無限ループで時間取得しながら時報っぽいことやってたらやはりCPU使用率100%でわろたw だめぽ
about a day ago: ナウでヤングなロリポップ!
about a day ago: 自前サーバはいやだしなぁ

an hour ago と 1 hours ago の違いはなんだろう。



今日はここまで!基本編とかいいながら自分の中ではとっても応用編でした。
今度は取得してきたデータを色々いじくったりする応用編を書くつもりです。自信ないけど!

*1:僕の環境では"/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/site-packages"にあるよ!