未来は僕以外の手の中

SI屋がIT技術やビジネスのことなどを気ままに書き綴ってみるなど

Urban Airshipを利用してGoogle App EngineからPush Notificationを行う

iPhone OS 3.0からApple Push Notification Service(APNs)が使えるようになった。
iPhone/iPadバイス上のアプリケーションの更新データがサーバ上にあることを
ユーザに知らせる機能で、これによりユーザは受動的に更新情報を検知することが可能となる。

詳細や実装方法などは

などが詳しい。


しかし、Provider(Push通知するサーバ)としてGoogle App Engineでの運用を検討する場合、
APNsはSSL通信を行うためSSLが限定されているGAEをそのまま利用することができない。
そこで、Urban Airshipというサービスを利用するとGAEからもPush通知することができる。


以下、ツール工房 覚書のUrban Airship記事より引用

Urban Airship は、iPhone アプリ開発者向けに、プッシュ通知(APNs)とアプリ内課金(In App Purchase)のソースコードクラウドサービスを提供する会社です。
Django で作成した APNs サーバの運用方法を検討していて、このような会社があることを知りました。APNs を月当たり 100,000 メッセージまで無料で送信できますので、コンセプトを確認するには十分だと思います。

APNsサーバとの接続はUrban Airshipに任せて、GAEからはUrban AirshipにPush情報を
ポストするようにすればPush通知ができそうである。ということで登録してみることにする。


まずはAPNsサーバと通信するために必要なProvider証明書(Push SSL Certificate)を
Urban Airshipに渡しておく必要がある。
やり方はこちら→Exporting Your Push Notification Key


これがうまくいくと登録したPush Notification対応アプリに対して

  • Application Key
  • Application (Master) Secret

というのが発行される。
これはUrban Airshipに対してPush情報をポストする際に使用するBasic認証のユーザIDと
パスワードである。


Urban AirshipのPush Notification APIをみると

An HTTP POST to https://go.urbanairship.com/api/push/ performs a push notification to one or more users. The payload is in JSON with content-type application/json

とあるので、/api/push/にjsonデータをポストすれば良さそうである。
ということで、以下のようなコードを実装してみる。

import urllib
import urllib2
from django.utils import simplejson

URL = "http://go.urbanairship.com/api/push/"
USER = "<Application Key>"
PASS = "<Application Master Secret>"
DEVICE_TOKEN = "<64 characters string>"

# ユーザ名とパスワードを登録する
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, URL, USER, PASS)

# Postするjsonデータ
pushdata = {"device_tokens":[DEVICE_TOKEN],
             "aps":{
                "alert":"新着データがありますよん",
                "badge":4,
                "sound":"default"
             }
           }

# 辞書(dict)->json形式に変換
pushdata = simplejson.dumps(pushdata)

# ヘッダ指定
headers = {"content-type": "application/json"}

# 認証情報付きでアクセスする
handler = urllib2.HTTPBasicAuthHandler(password_mgr)
opener = urllib2.build_opener(handler)
urllib2.install_opener(opener)

req = urllib2.Request(URL, pushdata, headers)
f = urllib2.urlopen(req)


GAEでは単純にimport simplejsonと記述しても使えないので以下のように記述する。

from django.utils import simplejson


以上のコードを実行すると、GAEからポストしたPush情報をUrban AirshipがAPNsサーバに
SSL通信で送り、無事にAPNsサーバからiPhoneにPush通知が来ることになる。



これによりGAEからAPNsへはPush通知ができなくても、間にUrban Airshipをはさむことで
無事にPush通知をすることができた。めでたしめでたし!?


…GAE→Urban Airship間はSSL通信じゃないので盗聴される可能性があるな、どうするんだろ(´Д`)

pythonでTwitter botを作ってみる


追記(2010/11/01):

twitterではBasic認証が廃止されてしまったので、以下の方法ではAPIが使えななっています。いずれOAuth認証ネタでも書きたいところですなぁ。。。


とあるネタでTwitter botを作る事になった。
pythonで作ったので、その時のメモなど。

目標ボット

 ・フォロワーが新規発言をすると、一定確率でコメントを返す。

導入準備

他言語に比べると情報は少ないものの、一応pythonでの情報も出回っているのでそれを参考に。

 python-twitterまとめ 〜導入まで


python-twitterなる便利なモジュールがあるのでそれを利用する。

実行テスト

上記サイトを参考につぶやきテスト(というか丸移し)

import twitter
api = twitter.Api("ユーザ名", "パスワード")
status = api.PostUpdate("Hello, python-twitter!!")

# 日本語投稿の場合はUnicodeにする
status = api.PostUpdate("python-twitterでの書き込み!")

これだけで書き込み完了。こりゃ便利だ!

bot実装

とりあえずドキュメント見ながら色々と試していく。

どうやら以下の4つのクラスが存在するらしい。

クラス内容
ApiTLやフォロワーなどの各種情報を取得したり、tweetを送信したりする
DirectMessageダイレクトメッセージの情報を管理
Statusつぶやき(tweet)の情報を管理
Userユーザの情報を管理


とりあえず
 1.(botの)フォロワーのユーザ情報を抽出
 2.各ユーザの最新tweetを取得
という流れでやってみる。

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

users = api.GetFollowers() # フォロワーのUser情報を取得
for u in users:
    print u.name, u.screen_name

    s = u.status # UserのStatusを取得
    if s:
        print s.id, s.text


結果の一例はこちら↓

Naoya_S billest001
4702798105 腹減ったなぁ

何もつぶやいていないユーザの場合u.status=Noneとなる。


また非公開ユーザの場合、こちらが許可されていないのに
ユーザのtweet情報を取得(u.status)しようとするとエラーが発生する。
(取得するには、botがそのユーザをフォローして許可されておく必要がある。)


tweetにはIDがついてるので、各ユーザごとに最新tweetIDを
辞書(ハッシュ)で格納しておけば最新情報を保持できそう。


そんなこんなでおおざっぱに実装するとこんな感じ↓

from random import randint
from collections import defaultdict
from time import *
from twitter import *

api = Api(username='ユーザ名', password='パスワード') # ログイン

# フォロワーの最新tweetの取得および投稿
def get_and_tweet(tweet_list):
    users = api.GetFollowers() # フォロワーのUser情報を取得
    for u in users:
        s = u.status
        if not s:
            continue
        
        # 一応リネーム
        user_name = u.screen_name
        tweet_id = s.id

        if tweet_id > tweet_list[user_name]:
            # 新tweet情報の保存
            tweet_list[user_name] = tweet_id 

            # 10%の確率でbotコメント発動
            if randint(1, 100) <= 10:
                sentence = u"@%s 誰に許可取って発言してんの?" % user_name
                status = api.PostUpdate(sentence) # Post

def main():
    # 各々の最新tweetID格納
    tweet_list = defaultdict(int)

    # 60秒おきにTL巡回、最新tweetを補足
    while(1):
        try:
            get_and_tweet(tweet_list)
        except urllib2.URLError:
            print u"エラーが発生したナリ。"
        
        sleep(60) # 1分間70リクエスト規制に注意

if __name__ == '__main__':
    main()

twitterAPI規制についてはこちらなどを参照。


実際に作ったのはもうちょい色々とやってるけど、基本はこんな感じ。
twitterAPIのおかげでだいぶ簡単にbotを作る事ができる。


今回は非公開内輪ネタbotなのでここでは紹介しませんが、
いずれもうちょっと有益な一般公開botなどを作ってみたいところ。

...アイデアないけど\^o^/


SciPyでの疎行列の扱い、保存など

Python、特にSciPyは日本語でのドキュメントが少ないので、メモがてら記述。


単語文書行列などでは大規模疎行列になることがよくある。
そこで格納方法の工夫などを行っていく必要がある。


最もスタンダードな方法としては圧縮行格納方式などがある。
基本的に疎行列の要素は0なので、非零の要素の位置だけ覚えておこうという発想。
もちろん(?)SciPyには疎行列を扱うモジュールsparseが用意されている。


lil_matrixにて疎行列を生成。引数には行列の大きさ(m, n)を指定。

from numpy import *
from scipy import io, sparse

A = sparse.lil_matrix((3, 3)) # 疎行列生成
A[0,1] = 3
A[1,0] = 2
A[2,2] = 5

【Aの型、中身】
3×3行列であること、および非零の位置のみ格納されていることが分かる。

>>> type(A)
<class 'scipy.sparse.lil.lil_matrix'>
>>> A
<3x3 sparse matrix of type '<type 'numpy.float64'>'
	with 3 stored elements in LInked List format>
>>> print A
(0, 1)  3.0
(1, 0)  2.0
(2, 2)  5.0

通常の行列の形に戻すにはtodense()を使う。

>>> A.todense() # 行列の形に戻す
matrix([[ 0.,  3.,  0.],
        [ 2.,  0.,  0.],
        [ 0.,  0.,  5.]])


matrixの保存にはio.savematを使う。

io.savemat("matrix_a", {"A":A})

これを実行すると「matrix_a.mat」というファイルが生成される。
保存する際には{"A":A}のように辞書のような形で書き込む。
なお、lil_matrix型でも保存できる。


また読み込みはio.loadmatを使う。

>>> A = io.loadmat("matrix_a")["A"]
>>> A.todense()
matrix([[ 0.,  3.,  0.],
        [ 2.,  0.,  0.],
        [ 0.,  0.,  5.]])

大規模疎行列の生成計算を一度きりにしたい場合は
lil_matrix型にて生成およびファイル保存を行い、使い回すと良い。


lil_matrix型とmatrix型における保存ファイルの容量の違いを見てみる。

from numpy import *
from scipy import io, sparse

A = sparse.lil_matrix((1000, 1000))
A.setdiag( ones(1000) ) # 対角要素を1にする
B = A.todense()

io.savemat("matrix_a", {"A":A}) # lil_matrix型で保存
io.savemat("martix_b", {"B":B}) # matrix型で保存

容量チェック

$ ls -lh
-rw-r--r--  1 billest  billest   7.6M  9  7 05:23 martix_b.mat
-rw-r--r--  1 billest  billest    23K  9  7 05:23 matrix_a.mat

これからも疎行列の格納にはlil_matrix型の方が良いことが分かる。当たり前ではあるが…

SciPyを用いて潜在的意味解析(LSA)

自然言語処理の技法の1つに、潜在的意味解析(LSA)というものがある。


単語文書行列Aがあった場合、特異値分解(SVD)により
 A=UΣV
に分解し、特異値を大きいほうからk個使って
 Ak=UkΣkk
のように階数の低減を行うことで、階数kのAへの近似を最小誤差で得ることができる。


つまり特異値分解の計算さえできてしまえばLSAもすぐできるわけだが、
pythonの数値解析モジュールScipyにかかれば特異値分解もあっという間である。


まずは特異値分解まで↓

from numpy import *
from scipy import linalg

A = matrix([ [5, 8, 9, -4, 2, 4],
             [2, -4, 9, 4, 3, 3],
             [-3, 4, 8, 0, 5, 6],
             [-2, 5, 4, 7, 0, 2] ])

u, sigma, v = linalg.svd(A) # 特異値分解
rank = shape(A)[0] # 階数
u = matrix(u)
s = matrix(linalg.diagsvd(sigma, rank, rank))
v = matrix(v[:rank, :])
print u*s*v

出力結果1

  5.00000000e+00   8.00000000e+00   9.00000000e+00  -4.00000000e+00   2.00000000e+00   4.00000000e+00
2.00000000e+00 -4.00000000e+00 9.00000000e+00 4.00000000e+00 3.00000000e+00 3.00000000e+00
-3.00000000e+00 4.00000000e+00 8.00000000e+00 4.44089210e-16 5.00000000e+00 6.00000000e+00
-2.00000000e+00 5.00000000e+00 4.00000000e+00 7.00000000e+00 -1.33226763e-15 2.00000000e+00

これより、ちゃんとA=UΣVになっていることが分かる。


なお、linalg.svdの出力であるsigma

[ 19.51915253  10.39603796   8.26622837   5.62090278]
のように特異値のみがリストで帰ってくるので、linalg.diagsvdによって行列の形に戻してやる必要がある。



さて、もとの行列の階数が4なので、LSAにより階数3に低減して近似を行ってみる。

z = 3
u3 = u[:, :z]
s3 = matrix(linalg.diagsvd(sigma[:z], z, z))
v3 = v[:z, :]

print u3*s3*v3

出力結果2

  3.58369761  7.65615451  8.72804857 -4.88803344  2.93407657  4.64581738
1.10005237 -4.21848649 8.82719648 3.43572531 3.59353144 3.41036563
0.06134317 4.74322337 8.58782408 1.91948777 2.98098982 4.60406321
-3.62255534 4.60608106 3.68844489 5.98264423 1.07010406 2.73986632

微妙にずれはありつつも、まぁなんとか元の行列に近似されてるといえる。


なお、

print sum(sigma[:z])/sum(sigma)
→0.871675688141

より累積寄与率は約0.872である。つまり、出力結果2は元の行列Aの情報を約87.2%含んでいるといえる。


いずれにせよ、Scipyを用いれば高度な計算も簡単に出来てしまう。
やはりScipyはええもんですな〜。



Javaで認証コード生成(HMAC-SHA1)

【追記】暗号化→認証コード生成に修正 2009/08/29

ハッシュ関数を用いて「秘密鍵」と「認証すべき任意長のメッセージ」から暗号認証コード(MAC値)を生成する。HMACの詳細はHMAC(by Wiki)で。


まずはこの記事から引用(一部改変)

import javax.crypto.*;
import org.apache.commons.codec.binary.Hex;

public class hmacsha1 {
  public static void main(String[] args) throws Exception {
    KeyGenerator kg = KeyGenerator.getInstance("HmacSHA1");
    SecretKey sk = kg.generateKey(); // skが秘密鍵

    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(sk);
    byte[] result = mac.doFinal("hoge".getBytes()); // "hoge"が認証メッセージ
    System.out.println(new String(Hex.encodeHex(result))); 
    }
}

出力例は以下の通り。

06f3f3bf2acac18af258313041da55fbca82a82648686b78adac9839e


なお、上記サイトにもあるようにbyte配列をprintするのにtoString()ではうまくいかない。
そこで、Hex関数を使うとよさ気。指定された値を16進数文字列で返す関数。
jarファイル置き場はココです。commons-codec-1.3.zipがいいかも。


どうやら以下の部分にて秘密鍵は自動生成してくれてるらしい。

    KeyGenerator kg = KeyGenerator.getInstance("HmacSHA1");
    SecretKey sk = kg.generateKey(); // skが秘密鍵

自分で定めた秘密鍵を使いたい場合は、上の2行を以下に置き換える。

    String kagi = "geho"; // "geho"が秘密鍵
    SecretKeySpec sk = new SecretKeySpec(kagi.getBytes(), "HmacSHA1");

(import javax.crypto.spec.SecretKeySpec;が必要)


とりあえずJavaでの暗号化認証コード生成はこんな感じなのかな〜。