KLab若手エンジニアの これなぁに?

KLab株式会社の若手エンジニアによる技術ブログです。phpやRubyなどの汎用LLやJavaScript/actionScript等のクライアントサイドの内容、MySQL等のデータベース、その他フレームワークまで幅広く面白い情報を発信します。

Python

クリスマスが待てないのでVPC対応Cloudformer自作はじめました

CDP Advent Calendar 2012 12月12日分です。アドベントカレンダー初心者のsasaki-k (@siroken3)と申します。 当初は「Stack Deployment パターンに関すること」でタイトル提出しまして、自分のCloudFormationを利用した経験を書こうと思ってました。

そう、アドベントカレンダーが始まるまでは・・・

始まってみると諸先輩方のエントリーはCloudFormationが当たり前のように登場しているではないですか。持ちネタがみるみる貧相に見えてきて「これが、アドベントカレンダー・・」とガクブルしたので急遽ネタを追加しました。

それでは内容に入ります。当社は物理サーバによるDSASというソリューションを長年手がけて来ておりまして国内のソーシャルゲームはこのDSASで提供しています。一方、海外進出やチャレンジングな企画に関しては AWSのようなクラウドを活用しています。この活動については今年11月3日〜4日に開催された PHP Matsuriで講演させていただきました。

Stack Deploymentパターンに即してこの取り組みについて補足しますと、当社では各新規に立ち上がるゲームタイトル別に VPCを構築しつつ、そのフィードバックを元にVPCの共通形を育て、新規タイトル立ち上げ時には、その時点での共通系を CloudFormation テンプレート化したものを利用してシステムを起動し運用開始、フィードバックを受けてテンプレートを更新する流れです。

しかし、残念なことに既存環境のテンプレート化フェーズにおける重要なツールである CloudFormerは VPCに対応していないです・・ね。SubnetなどVPCの構成自体タイトル間に差異はなさそうですが、 既存タイトルのCloudFormation テンプレート化時にはそれなりの手数がかかります。

前置きが長くなりました。このような背景もありましてVPCに対応したCloudformerの代替を週末に書いてgithubのpublicリポジトリに公開しました。名前をかっこよくCloudformer-Vとかにしようと思ったのですが商標上の問題があるのではないかと考え無難にFloccus(房状雲)と名付けました。

https://github.com/siroken3/floccus

まずは VPC特有のVPC,Subnet,RouteTable,EC2Instanceあたりから手を付けています。実践未投入ですがVPCとSubnet定義だけで数百行近くのCloudformationテンプレートを数秒で出力できています。現状そのままではCloudFormationファイルとして投入すると失敗するかもしれないのが残念な点ですが今後の発展にご期待くださいませ。

CLIツールでして、使い方は上記githubリンクのREADME.mdをご参照いただければと思います。VPCIDを引数に指定すると対応するCloudFormationのJSONが標準出力に出力されます。

MITライセンスにしていますのでforkしていただいて改良いただくいただいたり、ご指摘いただいたり、さらにはpull requestしていただいた暁には幸甚です。

と言ってたらアマゾンさんからVPC対応 CloudFormerがクリスマスプレゼントで発表されたりして。(´Д⊂)

IRCボットコンテストエントリ: Monty the Python bot

こんばんは、最近なんだかバテ気味の若手ブログ初登場、nakazawa-kです。 よろしくお願いします。 どうやらIRCボットのコンテストをやるらしいと聞いて息巻き、大急ぎでざくっと実装してから実に2ヶ月ほど寝かせてしまったボットを投下してみます。 最初のお題が出た瞬間に思いついたのが「Pythonボット」でした。『Pythonで書かれた』という意味ではなく『Pythonを実行してくれる』ということです。
(nakazawa-k) >>>print "hello!"
(bot) hello!
こういう風にIRCがPythonコードであふれたら素敵だと思いませんか!? 私は思います。作りましょう。これで機能が決まりました。 次は超重要、名前です。 みなさんパイソンといえば何を思い浮かべるでしょう。 ニシキヘビ? いえいえパイソンといえばモンティ・パイソンです。テリー・ギリアムです。 Pythonを実行してくれるボットの名前にモンティ以上のものはないでしょう。 ということでPythonで書かれたPythonコードを実行してくれるモンティボットを作ってみました。 実装を始めるにあたり、まず目標をKLabの社内にある「どぶろく制度」を使って『1時間以内にサクッと動くようにする』と設定しました。 ちなみに「どぶろく制度」とは標準業務時間の10%を好きに使い、上司に断ることなく自分の興味が赴くまま研究や開発を行えるというものです。Googleの20%ルールや3Mの15%ルール(こちらは不文律ですね)と似たものです。 標準業務時間の10%とは、だらだらとやっていてはすぐに過ぎ去ってしまう位です。KLab入社間もなかったnakazawa-kにとって、IRCボットは打って付けのネタでした。 閑話休題。目標が決まったのでとにかくシンプルな実装を目指していきました。 IRCのプロトコルにも多少興味はあったのでRFC1459を流し読みし、その上でIRC接続ライブラリとしてPython IRC libraryを利用しました。 ※ソースは記事の最後に貼り付けてあります。 使い方
$ ./monty.py server[:port] #channel nickname
いじり方
(nakazawa-k) >>>self.v = range(1,101) (nakazawa-k) >>>print self.v (monty) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
こういう感じで、>>>に続けてPythonの式を書くと評価してくれます。printは結果を取得してIRCへ流してくれます。 「あの実装どうやるんだっけ?誰か教えてー」 という質問にサンプルコードを返信するだけではあまりテンションが上がりませんが、コードの先頭に">>>"を書くだけで実行結果を即確認出来超ハイテンションになれます。これで皆もっとPythonが好きになってくれるはずです!!
超簡単な仕組み SingleServerIRCBotクラスを継承すると、チャンネルでの発言時にon_pubmsg()が呼ばれます。この中に">>>"で始まるものを見つけると、右から左でPythonコードとして実行しています。 これだけでもPythonの実行権限で出来ることは、実質相当色々出来てしまいます。それこそファイルの作成や削除など、結構思いのままなので専用VMを作成して走らせています。
応用例 若手ブログ未登場のkさんによる「チャンネル内のユーザからなると(op権限)を奪いまくる」ボットの機能を模倣してみる
>>>self.excp="logbot" >>>[(nick not in [self.excp,self.nick]) and (c.notice(self.channel, nick + ' is dead'), c.mode(self.channel, "-o "+ nick)) or 1 for nick in [u for u in self.channels.items()[0][1].users()]]
発言が行われたチャンネルで、指定nickと自分以外の全ユーザからop権限を剥奪してくれます。 このように、MontyはPythonの式として書けるものなら、非常に多くの処理を自由に実行することが出来ます。
残念なところ ・execでのコード実行は、あまりにもフリーダムすぎる →PyPyベースのsandbox環境へ持ち込みたい。ただ、既存ライブラリから完全に切り離された環境ではあまり面白いことが出来ないのでほどほどに・・・。 例えば
(nakazawa-k) >>>weather (monty) =六本木付近の天気予報= 8/19晴れ 8/20曇りのち雨のち晴れのち小雨
こういうことが出来ると段々夢が広がってくるじゃないですか!
夢破れて・・・ 1) 当初はインタラクティブシェルをそのままIRC上へ持ち込み、謎のIRCペアプログラミング(複数人が1つのインタラクティブシェルを使ってコードを開発するというすさまじい共同作業)などをやりたいなーと夢想しました。まあこれは洒落なので実際にやりたければscreenを使うのが近道でしょう。 2) 当初はpopenして適宜入出力をパイプ取得すれば良いと考えましたが、そうそう素直にstdoutへ各行出力をしてくれるわけではありませんでした。ターミナルの機能を内部でそこそこ使っていたりと結構複雑化するポイントが垣間見えました。それでは手軽に書いてサクッと動かすという主旨に反してしまうので泣く泣く断念。
最後に マルチチャンネル非対応のため、社内の技術雑談チャンネルで放し飼いにしています。 ちょっと残念なコードを食べさせるとinternal exceptionといって実行を放り出してしまうドジッ子(いえ、コードを書いた人のほうがドジッ子なんです)ですがKLab IRCサーバへお立ち寄りの際(!?)は可愛がってあげてください。
#! /usr/bin/env python

from ircbot import SingleServerIRCBot
from irclib import nm_to_n, nm_to_h, irc_lower, ip_numstr_to_quad, ip_quad_to_numstr
import random
import re
import os
import sys
import StringIO
import time

class Monty(SingleServerIRCBot):
    def __init__(self, channel, nickname, server, port=6667):
        SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
        self.nick = nickname
        self.channel = channel

    def on_nicknameinuse(self, c, e):
        c.nick(c.get_nickname() + "_")
        self.nick += "_"

    def on_welcome(self, c, e):
        c.join(self.channel)

    def on_pubmsg(self, c, e):
        nick = nm_to_n(e.source())
        matched = re.match(r">>>(.*)", e.arguments()[0])
        if matched != None:
            outputBuffer = StringIO.StringIO()
            sys.stdout = outputBuffer
            exceptionBuf = ''
            try:
                exec matched.group(1)
            except:
                exceptionBuf = sys.exc_info()[0]
                print "internal exception"
            c.notice(self.channel, outputBuffer.getvalue())
            sys.stdout = sys.__stdout__
            print exceptionBuf
        return

def main():
    import sys
    if len(sys.argv) != 4:
        print "Usage: testbot   "
        sys.exit(1)

    s = sys.argv[1].split(":", 1)
    server = s[0]
    if len(s) == 2:
        try:
            port = int(s[1])
        except ValueError:
            print "Error: Erroneous port."
            sys.exit(1)
    else:
        port = 6667
    channel = sys.argv[2]
    nickname = sys.argv[3]

    bot = Monty(channel, nickname, server, port)
    bot.start()

if __name__ == "__main__":
    main()

IRCボットコンテストエントリ その一 ~めかぶで夏を乗り切ろう

こんにちは。暑いですがみなさんどうお過ごしでしょうか。 tanaka-m です。 投稿一発目は「Gスボット」です。 オープンソースの形態素解析エンジン「MeCab」を利用して、 チャンネルの発言に関係のある名ゼリフを返答します。 ・MeCab http://mecab.sourceforge.net/
============================================== 形態素解析エンジンとは(Wikipedia) 対象言語の文法の知識や辞書を情報源として用い、自然言語で書かれた文を形態素の 列に分割し、それぞれの品詞を判別する作業を指す。(ナンノコッチャ) 例) すもももももももものうち 以下のようなアウトプットになります。 すもも  名詞,一般,*,*,*,*,すもも,スモモ,スモモ も      助詞,係助詞,*,*,*,*,も,モ,モ もも    名詞,一般,*,*,*,*,もも,モモ,モモ も      助詞,係助詞,*,*,*,*,も,モ,モ もも    名詞,一般,*,*,*,*,もも,モモ,モモ の      助詞,連体化,*,*,*,*,の,ノ,ノ うち    名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
ブログのタギングなどに利用できるのではないかと言われています。 ==============================================
各発言に対して分析を行い、関連のある文章をひっぱってくるようにさせ、 絶妙な脱力感を醸し出します。
イメージ↓ (Aさん) 豚が食べたい。 (bot)    飛べない豚はただの豚だぜ。 昨日から社内のチャンネルにて放し飼いにしてますが、 いかにして「空気を読まない奴の発言」的に見せるかは実装する人の工夫次第のよう です。(※今はただ脈絡のない発言にしか見えない) このbotのいいところは、データを用意すればジャンルを自由に入れ替えられることです。 同じソースで「ドラゴン●ールbot」「セーラー●ーンbot」も動くわけですね。
今回は初Pythonで実装しました。(ワッショーイ) 以下、ソース (1)名ゼリフを単語に紐付けてDBに登録する
#!/usr/bin/python
# coding: utf-8

import MeCab
import sys
import string
import re
import MySQLdb

file = sys.argv[1]
print file
connect = MySQLdb.connect(db="irc_db", host="127.0.0.1", port=3306, user="root", passwd="password")
cur = connect.cursor()
cur.execute("truncate table sentence;")

for line in open(file):
    value = line.split(' - ')
    msg = value[0]
    print msg
    
    t = MeCab.Tagger ()
    m = t.parseToNode (msg)
    cnt = 0
    while m:
        print m.surface, "\t", m.feature
        if m.surface.find('BOS/EOS') >= 0 or len(m.surface) == 0:
            m = m.next
            continue
        feat = m.feature
        arr = feat.split(',')
        gram = arr[0] #品詞
        word = arr[6] #原形
        if gram in ('名詞', '動詞', '形容詞'):
            print gram, word
            cur.execute("insert into sentence values(NULL, '"+ gram +"', '"+ word +"', '" + msg +"');")
     
        cnt = cnt + 1
        m = m.next
        
connect.commit();
cur.close()
connect.close()
(2)IRCの発言から関連するセリフを選び出す
while (1):
    buffer = IRC.recv(1024)
    print buffer
    msg = string.split(buffer)
    if msg[0] == "PING": 
        send_data("PONG %s" % msg[1]) 
    if msg [1] == 'PRIVMSG':
        
        nick_name = msg[0][:string.find(msg[0],"!")] 
        message = ' '.join(msg[3:])

        print message
        if message == ':stop '+NICKNAME:
            print 'stopping..'
            send_data("QUIT ")
            break            
        
        #毎回出るとウザいので5回に1回
        luck = random.randint(0, 5)
        if luck != 1:
            continue
        
        message = unicode(message, 'iso-2022-jp').encode('utf-8')
        print 'encoded:'
        print message
        t = MeCab.Tagger ()
        m = t.parseToNode (message)
        
        alist = []
        while m:
            print m.surface, "\t", m.feature
            if m.surface.find('BOS/EOS') >= 0 or len(m.surface) == 0:
                m = m.next
                continue
            feat = m.feature
            arr = feat.split(',')
            gram = arr[0] #品詞
            word = arr[6] #原形
            if gram in ('名詞', '動詞', '形容詞') and word != '*':
                print gram, word
                arr = {'gram':gram, 'word':word}
                alist.append(arr)
            m = m.next

        print alist
        if len(alist) > 0:
            connect = MySQLdb.connect(db="irc_db", host="127.0.0.1", port=3306, user="root", passwd="password")
            cur = connect.cursor()

            where = ' (1 = 2) '
            for arr in alist:
                where = where + " OR ( word ='" + arr['word'] + "' AND gram ='" + arr['gram'] + "')"

            print where
            cur.execute('select * from sentence where' + where)
            rows = cur.fetchall()
            for row in rows:
                print row[3]
            cur.close()
            connect.close()
                
            if len(rows) > 0:
                idx = random.randint(0, len(rows) - 1)
                send_data("PRIVMSG " + CHANNEL +" :" + unicode(rows[idx][3], 'utf-8').encode('iso-2022-jp'))

夏バテに負けずにがんばっていきましょう!!
 KLab若手エンジニアブログのフッター