Streaming APIで大量のつぶやきをリアルタイムに保存する方法(Python編)


前提:Twitter Developersでアプリのconsumer_key等を取得しておく必要があります。詳しく知りたい人は「Twitter OAuth」などを調べるといいでしょう。

なぜこんな技術が必要なのかは、cURL編で書きました。

Streaming APIを使うときは、接続が切れてしまったときにいかに接続し直すかというのがキモなわけですが、cURLで簡単に実現する方法がわからなかったので、240秒ごとに強制的に接続し直すという方法を採用しました。この方法には、(1)再接続のときにつぶやきを取りこぼす危険と重複して取得する可能性があること、(2)接続が切れてから再接続を試みるまでに平均で120秒かかる、という問題がありました。

Streaming APIは、つぶやきが無いときには「CR LF」を30秒おきに送信してくるので、これをチェックすれば切断を検出できるのですが、検出と再接続の機能を自分で実装するのは大変なので、ライブラリに頼りましょう。

ここでは、PythonでTwitterを利用するためのライブラリ、Tweepyを使います。Ubuntuなら次のようにインストールできます。

sudo apt-get update
sudo apt-get install python3-setuptools python3-pip
sudo easy_install3 tweepy

CentOSなら、suで管理者になってから、

yum install python-setuptools
easy_install tweepy

Tweepyは切断の検出と再接続の機能を持っているので、Streaming APIで受け取ったデータを出力するプログラム(stream.py)は、サンプルを参考にして次のように簡単に書けます。StreamListenerで、つぶやきに対応するon_status()ではなくon_data()をオーバーライドしているのは、データをそのまま(JSON形式で)出力したいからです。とりあえず全部保存しておいて、必要なものをあとで取り出すというわけです。JSON以外のものがdataとして来る場合もあるので、最初が「{」のものだけをJSONデータと見なして出力するようにしています(ちょっとかっこわるい)。

# -*- coding: utf-8 -*-

from tweepy.streaming import StreamListener
from tweepy import OAuthHandler
from tweepy import Stream

consumer_key = ""#引用符の中にconsumer_keyの情報を記述する
consumer_secret = ""#引用符の中にconsumer_secretの情報を記述する

access_token = ""#引用符の中にaccess_tokenの情報を記述する
access_token_secret = ""#引用符の中にaccess_token_secretの情報を記述する

class StdOutListener(StreamListener):
    def on_data(self, data):
        if data.startswith("{"):
            print(data, flush=True)
        return True

    def on_error(self, status):
        print(status, flush=True)

if __name__ == '__main__':
    l = StdOutListener()
    auth = OAuthHandler(consumer_key, consumer_secret)
    auth.set_access_token(access_token, access_token_secret)

    stream = Stream(auth, l)
    stream.filter(track = [keyword])#検索する場合
#    stream.sample()#ツイートのランダムサンプリングを取得する場合
#    stream.userstream()#タイムラインを取得する場合

consumer_keyとconsumer_secret、access_token、access_token_secret、つぶやきを絞り込むためのキーワード(例:「track = ["UK USA"]」、「track = ["日本"]」。OR条件はカンマ、AND条件はスペースで連結)を入力して、「python3 stream.py」で実行します。うまく動いていたら,「python3 stream.py >> result.dat」などとして結果をresult.datに保存します。

「せっかくPython使っているんだから、この場でパースして・・・」と思うかもしれませんが、後で何が必要になるかわからないので、とりあえず生データを保存しておくのがいいでしょう(リアルタイムでアウトプットしたいという場合は別です)。ファイルではなくデータベースに保存してもいいのですが、話が複雑になるのでやめておきます(データベースを使う場合でも、後で何が必要になるかわからないので、全データを保存しておくのがいいでしょう)。

再接続できるからといって、通信の不安定な場所での利用はおすすめできません。通信の安定した環境に置いたサーバで動かしっぱなしにするのがいいでしょう。何らかの原因でプロセス自体が落ちたときのために、以下のようなスクリプトを使うといいかもしれません(通常の切断ではプロセスは落ちません)。

#!/usr/bin/env bash

while :
do
  python3 stream.py >> result.dat
  sleep 240
done

Streaming APIで取得したつぶやきの処理方法

ソーティングはComparatorを定義してやるくらいがちょうどいい


ソーティング(並び替え)のためにプログラムを書く人はあまりいません。プログラミングを習うとすぐに、ソーティングの例題に出会いますが、それはあくまでアルゴリズムの勉強のためのものであって、実際の問題でソートが必要なときは、言語に組み込まれた、あるいは定評のあるライブラリを使ってソートします。

数値や文字列はそのようなライブラリですぐにソートできますが、複数の属性を持つオブジェクトなどを並び替えるためには、オブジェクト同士を比較するための関数やオブジェクト(以下ではComparatorと総称)を用意してからでないとソートできません。C++のSTLにあるsort()にはパラメータを2つ持つ関数を、JavaのCollections.sort()にはインターフェースComparatorを実装するクラスを与えなければなりません(あるいは、オブジェクトにComparableインターフェースを実装させる)。(C++本ウェブアプリ本を参照)

スクリプト系の言語だと、Comparatorを用意しなくても、なんとなく並び替えてくれるので便利です。たとえば、数値と文字列の組からなる以下のような配列を、Pythonなら簡単にソートできます。

>>> data=[(1,"a"), (2, "d"), (1,"c"), (1,"b")]
>>> data.sort()
>>> data
[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'd')]

C++やJavaでは、数値と文字列の組の配列をこんなに簡単にソートすることはできないので、「やっぱスクリプト言語だよね」ということになるのは仕方ありません。

4873113644しかし、この結果をよく見ると、数値だけでなく文字列も考慮して並び替えられています。多くの場合、これはやりすぎです。たとえば、Toby Segaran『集合知プログラミング』(オライリー・ジャパン, 2008)では、映画の評価データ10万件を使った推薦システム(協調フィルタリング)の出力として、次のような例が紹介されています。

5.0 What’s Eating Gilbert Grape (1993)
5.0 Temptress Moon (Feng Yue) (1996)
5.0 Street Fighter (1994)
5.0 Spice World (1997)
5.0 Sliding Doors (1998)
5.0 Shooting Fish (1997)
5.0 Roseanna’s Grave (For Roseanna) (1997)
5.0 Rendezvous in Paris (Rendez-vous de Paris, Les) (1995)
5.0 Reluctant Debutante, The (1958)
5.0 Police Story 4: Project S (Chao ji ji hua) (1993)
5.0 Palmetto (1998)
5.0 New York Cop (1996)
5.0 Love Is All There Is (1996)
5.0 Johns (1996)
5.0 Innocents, The (1961)
5.0 Hollow Reed (1996)

これは、(スコア, タイトル)の組の配列を、ソーティングして反転したものです。反転することによって、スコアの高い物から出力されるようになっているわけですが、並び替えにはタイトルも使われているため、得られる結果はいつも、アルファベット順で後ろにあるものが優先されてしまっています。独自のComparatorを使うとかスコアに乱数を付加するなど、何らかの対策をしてからでないと、このコードを使うことはできないでしょう。

ソーティングはComparatorを定義してやるくらいがちょうどいい、というわけです。

Pythonで亀型全自動お掃除ロボットを作る


「Eclipse 上でPythonのプログラムを書くためのプラグインPyDev」から続く

辻真吾『Pythonスタートブック』(技術評論社, 2010)4774142298を読みました。

この本は、最初に学ぶプログラミング言語がPythonだという人のために書かれた入門書ですが、全10章の最終章つまり第10章はかなりハードでした。とりあえず、初心者は飛ばしていいと思うのですが、私にとっては昔遊んだLOGOを思い出させる懐かしい内容だったので、途中まで自分なりに作ってみました。本文よりはずいぶん手を抜いたつもりですが、それでもまだこの章だけは初心者お断りかもしれません。

参照:24.4 turtle (Python Documentation)

#coding:utf-8
import turtle
import random

class Kame(turtle.Turtle):
    def __init__(self):
        super(Kame, self).__init__()
        self.xmax = 200
        self.ymax = 200
        self.getscreen().setup(2 * self.xmax + 2, 2 * self.ymax + 2, 0, 0) #ウィンドウサイズ
        self.getscreen().screensize(2 * self.xmax, 2 * self.ymax, 'gray') #キャンバスサイズ
        self.pencolor('white')
        self.width(10)
        self.left(random.randint(1, 360))

    def run(self):
        while True:
            self.forward(10)
            if self.xcor() > self.xmax: #右の壁にぶつかった
                self.rotate(90)
            if self.ycor() > self.ymax: #上の壁にぶつかった
                self.rotate(180)
            if self.xcor() < -self.xmax: #左の壁にぶつかった
                self.rotate(270)
            if self.ycor() < -self.ymax: #下の壁にぶつかった
                self.rotate(360)

    def rotate(self, t): #tはheading()の基準を変換するためのパラメータ
        self.undo()
        self.left(t - self.heading() + random.randint(0, 180))

kame = Kame()
kame.run()

Eclipse上でPythonのプログラムを書くためのプラグインPyDev


「なぜPythonなのか」から続く

『Pythonスタートブック』4774142298を読んでいます。コードが単純な初めのうちは、Pythonの特徴を活用した、その場で試せるサンプルが続いていましたが、後の方ではコードが複雑になることもあって、ファイルにコードを書かなければいけなくなりました。オブジェクト指向のコードを一般的なエディタで書くのは私には無理なので、Python開発のためのEclipseプラグインであるPyDevを導入しました。コード補完機能や、エラーの即時表示機能が便利です。

Eclipse上でHelp→「Install New Software」とし、「Work with」に「http://pydev.org/updates」を入力すればインストールできます。

なぜPythonなのか


Quotes about Pythonを見ると、Pythonがさまざまな場所で重要な役割を担っているプログラミング言語だということがわかります。たとえば、GoogleのPeter Norvigさん、彼はハッカーを育てるためのLisp本Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp日本語訳)の著者ですが、Pythonも重視しているようで、次のように言っています。

Googleにとって、Pythonは最初から重要でした。システムが大きく成長した今もそれは変わりません。現在、Googleには多くのPythonエンジニアがいますが、もっとたくさん採用したいと考えています。

4274065979Paul Grahamさんは、『ハッカーと画家』に収録されているエッセイ「素晴らしきハッカー」の中で、「Googleは、Javaプログラミングの求人広告を出すとき、賢明にもPythonの経験を要求している」と言っています。(同様の議論が「Pythonのパラドックス」でもなされています。)

Javaのプロジェクトで働くために雇われるプログラマは、 Pythonを使うプロジェクトで雇えるプログラマほど 賢くはないだろう。そして、雇えるハッカーの質は、たぶん言語の選択よりもずっと重要だ。もっとも、正直に言えば、良いハッカーはJavaよりPythonを好むという事実が、これらの言語の相対的な利点について何かを暗示していると思う。

たしかに、かつてはそうだったのでしょう。学校で教えられるのは、C言語やC++、Javaなどでしょうから、Pythonを知っているということは、少なくとも「習ったことしかできない奴」ではないことの証ではあったわけです。

489471163Xしかし今は21世紀です。MITはSICPを使った伝説的な講義6.001で使うプログラミング言語をSchemeからPythonに変えたそうです(Why MIT switched from Scheme to Python)。ソフトウェアをGoogleのクラウド上で動かす仕組みGoogle App Engineが最初にサポートした言語はPythonでした(後にJavaが追加されました)。

こうなってくると、Pythonを使って優秀なエンジニアを簡単に見つけることはできなくなっているでしょう。普及のための閾値を超えて、「常識」になりつつあるからです。

これからは、経験を積んだエンジニアだけでなく、プログラミングの初心者がPythonを選択するということも増えてくるはずです。そういう人のための資料は、C言語やJavaに比べてまだまだ少なく、需要に供給が追いついていないことが予想されます。

辻真吾『Pythonスタートブック』(技術評論社, 2010)4774142298は、最初に学ぶプログラミング言語がPythonだという、新しい世代のための入門書です。キャプチャ画面を使って作業手順を丁寧にしているのはもちろん、つまずきそうなことがらについて説明の仕方や、対話的に使えるというPythonの特徴を最大限に生かす題材を独自に考案しています。まったくの初心者でも、この本を読めば短時間でプログラミングのエッセンスを学ぶことができるでしょう。たとえば、私はこの本を読むまで、「変数への代入」という概念につまずく初心者のことなんて考えたこともありませんでした(自分もかつてそうだったにも拘わらず)。そういうレベルから説明の仕方を見直そうという著者の姿勢には感心します(独自の工夫のすべてに賛同できるというわけではありませんが)。

Pythonがほんようによい言語かどうかの判断は、プログラマ個人に委ねるのがいいでしょう。Paul Grahamさんのように、「プログラミング言語の重要な9つのアイディアのうち、Pythonがサポートしているのは7つまでであり、Lispには劣っている」という人もあれば(技術野郎の復讐)、Paul Prescodさんのように、「Pythonはちょうど良いところで妥協している」という人もいます(PythonとLispの関係について)。

関連:Eclipse 上でPythonのプログラムを書くためのプラグインPyDev