読者です 読者をやめる 読者になる 読者になる

StatsBeginner: 初学者の統計学習ノート

初学者が統計学、機械学習、R、Pythonの勉強の過程をメモっていくノート。

Pythonの"Tweepy"でTwitter APIから取得したデータを読んでみる

[追記]
 このエントリを書いた後、レスポンスデータの読み方について別途詳しくエントリにまとめました。
 
www.statsbeginner.net
[/追記]

 前回のエントリで、PythonのTweepyライブラリを導入し、TwitterのAPIをいじることができるようになりました。

 statsbeginner.hatenablog.com

 
 で、APIから情報を取得して分析とかをするのであれば、Tweepyが返してくるデータから必要な項目を抜き出さなければならず、「Tweepyはいったいどんなデータを返してきているのか」を理解してなければ話にならないですよね。
 とにかくまずはTweepy公式サイトのReferenceを読んでいくべきですが、取得されるデータの内容について細かいことは書いてないような気が?
 どっかにドキュメントあるのかなと思ってGitHubをみてみたら、ソースコードとかは読めましたが、データモデルを解説する「model reference」というドキュメントは「To Do」となってて未作成でした。が、ソースコードのフォルダを開いて、理解できる範囲で眺めてみると、結構参考にはなりました。「models.py」というソースコードを開くと、クラス定義とかが書かれてあります。


 ひとまず理解のとっかかりとして、Pythonのインタラクティブシェル上で

>>> api.me()


 と打った時に取得されるデータの中身を読んでみました。なおこれは前回のエントリの続きで、APIクラスのインスタンスを持つ「api」というオブジェクトを生成してあることが前提です。
 APIクラスのインスタンスに.meというメソッドを適用すると、ユーザ自身についての情報がtweepy.models.Userという型(以下めんどうなのでUser型という)のオブジェクトとして返されます。
 この型のオブジェクトの中には、属性として色々な情報が埋まっている。長くてみるのが面倒なんですが、属性ごとに改行をはさみながら、以下に貼り付けてみます。マジマジと見つめると気持ち悪くなるのでざっと眺めるだけにしてください。

>>> api.me()
User(
 follow_request_sent=False, 
 has_extended_profile=False, 
 profile_use_background_image=True, 

 _json={u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'333333', u'default_profile_image': False, u'suspended': False, u'id': 257791390, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'profile_location': None, u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/6HCF2FYowE', u'indices': [0, 22], u'expanded_url': u'http://statsbeginner.hatenablog.com/', u'display_url': u'statsbeginner.hatenablog.com'}]}, u'description': {u'urls': []}}, u'followers_count': 1567, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'257791390', u'profile_background_color': u'C0DEED', u'needs_phone_verification': False, u'listed_count': 24, u'status': {u'lang': u'ja', u'favorited': False, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'contributors': None, u'truncated': False, u'text': u'\u30b9\u30d7\u30e9\u30c8\u30a5\u30fc\u30f3\u306f\u3084\u3070\u3044\u3002\u5ec3\u4eba\u306b\u306a\u3063\u3066\u3057\u307e\u3046\u306e\u3067\u535a\u8ad6\u63d0\u51fa\u307e\u3067\u306f\u63a7\u3048\u308b\u3088\u3046\u306b\u3057\u3066\u308b\u3002', u'created_at': u'Wed Oct 21 23:24:41 +0000 2015', u'retweeted': False, u'in_reply_to_status_id_str': None, u'coordinates': None, u'in_reply_to_user_id_str': None, u'source': u'<a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>', u'in_reply_to_status_id': None, u'in_reply_to_screen_name': None, u'id_str': u'656974416312598528', u'place': None, u'retweet_count': 1, u'geo': None, u'id': 656974416312598528, u'favorite_count': 0, u'in_reply_to_user_id': None}, u'is_translation_enabled': True, u'utc_offset': 32400, u'statuses_count': 11180, u'description': u'\u6771\u4eac\u3067\u30b5\u30e9\u30ea\u30fc\u30de\u30f3\u3084\u308a\u306a\u304c\u3089\u3001\u95a2\u897f\u306e\u5927\u5b66\u9662\uff08\u5de5\u5b66\u7814\u7a76\u79d1\u30fb\u535a\u58eb\u8ab2\u7a0b\uff09\u3067\u90fd\u5e02\u8a08\u753b\u306b\u95a2\u9023\u3059\u308b\u5fc3\u7406\u5b66\u306e\u7814\u7a76\u306a\u3069\u3092\u3057\u3066\u307e\u3059', u'friends_count': 1022, u'location': u'\u3064\u304f\u3070\u5e02', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'name': u'bata', u'lang': u'ja', u'profile_background_tile': False, u'favourites_count': 965, u'screen_name': u'statsbeginner', u'notifications': False, u'url': u'http://t.co/6HCF2FYowE', u'created_at': u'Sat Feb 26 04:59:47 +0000 2011', u'contributors_enabled': False, u'time_zone': u'Tokyo', u'protected': False, u'default_profile': True, u'is_translator': False},

 time_zone=u'Tokyo', 
 suspended=False, 
 id=257791390, 
 
 description=u'\u6771\u4eac\u3067\u30b5\u30e9\u30ea\u30fc\u30de\u30f3\u3084\u308a\u306a\u304c\u3089\u3001\u95a2\u897f\u306e\u5927\u5b66\u9662\uff08\u5de5\u5b66\u7814\u7a76\u79d1\u30fb\u535a\u58eb\u8ab2\u7a0b\uff09\u3067\u90fd\u5e02\u8a08\u753b\u306b\u95a2\u9023\u3059\u308b\u5fc3\u7406\u5b66\u306e\u7814\u7a76\u306a\u3069\u3092\u3057\u3066\u307e\u3059', 
 
 _api=<tweepy.api.API object at 0x1026bfe50>, 
 verified=False, 
 profile_location=None, 
 profile_image_url_https=u'https://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', 
 profile_sidebar_fill_color=u'DDEEF6', 
 is_translator=False, 
 geo_enabled=True, 
 
 entities={u'url': {u'urls': [{u'url': u'http://t.co/6HCF2FYowE', u'indices': [0, 22], u'expanded_url': u'http://statsbeginner.hatenablog.com/', u'display_url': u'statsbeginner.hatenablog.com'}]}, u'description': {u'urls': []}}, 
 
 followers_count=1567, 
 protected=False, 
 id_str=u'257791390', 
 default_profile_image=False, 
 needs_phone_verification=False, 
 listed_count=24, 
 status=Status(contributors=None, truncated=False, text=u'\u30b9\u30d7\u30e9\u30c8\u30a5\u30fc\u30f3\u306f\u3084\u3070\u3044\u3002\u5ec3\u4eba\u306b\u306a\u3063\u3066\u3057\u307e\u3046\u306e\u3067\u535a\u8ad6\u63d0\u51fa\u307e\u3067\u306f\u63a7\u3048\u308b\u3088\u3046\u306b\u3057\u3066\u308b\u3002', in_reply_to_status_id=None, id=656974416312598528, favorite_count=0, _api=<tweepy.api.API object at 0x1026bfe50>, source=u'Twitter Web Client', _json={u'lang': u'ja', u'favorited': False, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'contributors': None, u'truncated': False, u'text': u'\u30b9\u30d7\u30e9\u30c8\u30a5\u30fc\u30f3\u306f\u3084\u3070\u3044\u3002\u5ec3\u4eba\u306b\u306a\u3063\u3066\u3057\u307e\u3046\u306e\u3067\u535a\u8ad6\u63d0\u51fa\u307e\u3067\u306f\u63a7\u3048\u308b\u3088\u3046\u306b\u3057\u3066\u308b\u3002', u'created_at': u'Wed Oct 21 23:24:41 +0000 2015', u'retweeted': False, u'in_reply_to_status_id_str': None, u'coordinates': None, u'in_reply_to_user_id_str': None, u'source': u'<a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>', u'in_reply_to_status_id': None, u'in_reply_to_screen_name': None, u'id_str': u'656974416312598528', u'place': None, u'retweet_count': 1, u'geo': None, u'id': 656974416312598528, u'favorite_count': 0, u'in_reply_to_user_id': None}, coordinates=None, entities={u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, in_reply_to_screen_name=None, id_str=u'656974416312598528', retweet_count=1, in_reply_to_user_id=None, favorited=False, source_url=u'http://twitter.com', geo=None, in_reply_to_user_id_str=None, lang=u'ja', created_at=datetime.datetime(2015, 10, 21, 23, 24, 41), in_reply_to_status_id_str=None, place=None, retweeted=False), 
 
 lang=u'ja', 
 utc_offset=32400, 
 statuses_count=11180, 
 profile_background_color=u'C0DEED', 
 friends_count=1022, 
 profile_link_color=u'0084B4', 
 profile_image_url=u'http://pbs.twimg.com/profile_images/517659681313923073/_m3UMIAR_normal.jpeg', 
 notifications=False, 
 favourites_count=965, 
 profile_background_image_url_https=u'https://abs.twimg.com/images/themes/theme1/bg.png', 
 profile_background_image_url=u'http://abs.twimg.com/images/themes/theme1/bg.png', 
 screen_name=u'statsbeginner', 
 is_translation_enabled=True, 
 profile_background_tile=False, 
 profile_text_color=u'333333', 
 name=u'bata', 
 url=u'http://t.co/6HCF2FYowE', 
 created_at=datetime.datetime(2011, 2, 26, 4, 59, 47), 
 contributors_enabled=False, 
 location=u'\u3064\u304f\u3070\u5e02', 
 profile_sidebar_border_color=u'C0DEED', 
 default_profile=True, 
 following=False
 )


 頭がクラクラしてきます。
 でもこうやって眺めてみると、全体の構造と、だいたいどういう情報が取り出せるのかってのがわかりますね。
 全体としてはUser()というくくりになっていて、このカッコの中に「follow_request_sent=False」のように「名前=値」の形式で属性が埋め込まれている。
 内容は属性の英語名称から想像つくものが多いです。内容が文字コード表示になってて分かりづらいところを言うと、descriptionというのは自己紹介文であり、statusはたぶん最新のツイートかな。

 なおいたるところに出てくるu'...'という書式はPythonにおけるUnicode文字列ってやつで、uは記号として無視しておけばいいです。


 よくみると、属性の中にはすごく長いものがありますね。たとえば「_json=」というやつはTwitterから返ってきた生のJSONのデータなんだろうか?
 JSONは一般的に、{"キー":値, "キー":値, "キー":値}という情報のセットになっていて、これはPythonでいうと辞書型の記述方法に相当します。実際、このUser型に埋め込まれている_jsonという属性の中身を取り出すには、辞書型としてアクセスすれば良いようです。

>>> me = api.me()  # 自分についての情報を持つUser型のオブジェクトをつくる
>>> type(me)  # 型をみてみる
<class 'tweepy.models.User'>
>>> me_json = me._json  # JSONが埋まってる属性を抜き出す
>>> type(me_json)  # 型をみてみる
<type 'dict'>
>>> me_json['followers_count']  # フォロワー数を意味するキーを指定してみる
1567


 上記のようにするとフォロワー数が取得できるわけですが、もちろん、フォロワー数を取得するためにこのJSONの中まで見に行く必要はない。
 User型の属性の中に直接的に「followers_count=1567」というのがあるわけだから、

>>> me.followers_count
1567


 と書けばいいわけです。当然、

>>> number_of_followers = api.me().followers_count
>>> print(number_of_followers)
1567


 というような書き方でも成り立つ。
 ただし、TweepyのAPIクラスインスタンスはメソッドが適用されるたびにTwitter APIにリクエストを投げるものなので、上記のようにapi.なんたらと書くたびに通信が発生して時間がかかるので、プログラムを書くときはこのapiが何回も登場するとヤバい。
 User型の属性情報に何度もアクセスするのであれば、最初に書いたみたいに一旦「me」というようなオブジェクトをつくって値を渡してから中身を掘ったほうがいいと思う。


 あとでstatus型など他のデータも中身をしっかり読んでみようと思いますが、だいたい同じような勝手で読み解いていけばいいんじゃないでしょうか。