今年三月完成了一个比较有趣的Project,Della查询机器人。半年了程序也断断续续更新,没什么气力去纠缠在这个上面了,所以也差不多是时候总结下,算是一个完整的结束吧,现在Della的版本已经是1.2,和刚开始的程序结构有了很大的变化,所以我想就直接把原理简单介绍下,也可以让自己没事回顾下。

Della1.1

不过在此之前,还是简单说说第一个版本的主要结构,如果你对此没兴趣而只是想知道怎么在GAE上搭建miniblog查询机器人的话,请直接忽视本段落以及下面一段,Della1.1是完全由python的第三方模块xmpppy实现的,XMPP是一种以XML为基础的开放式即时通讯协议,而GTalk所使用的消息传输协议与XMPP兼容,所以采用XMPP协议的一个开源Python实现xmpppy可以实现Gtalk在线操作查询功能,至于在饭否上实现查询,很简单,只要把饭否机器人加为Della机器人的好友,这样就等于绑定了饭否的信息,再根据从Gtalk上接受到的饭否信息来进行操作并返回结果,所以Della第一版是完全建立在Xmpppy上的程序,至于xmpppy具体原理我会在下一篇文章中比较详细的介绍,如果你需要详细了解Della1.1结构,也可以到Della的项目主页上下载1.1版本。

Della1.2

Della1.1在线的时间很不确定,因为没有服务器去放置基于xmpppy的Della1.1,所以只能利用闲暇时间在自己电脑上挂着,这样很不方便,所以在六月的时候抽着时间把Della的程序做了调整,原来的程序一分为二,饭否和Gtalk这两部分功能彻底分离了,也可以区别为在IM上查询和在miniblog上查询两部分,IM部分还是依据1.1的结构原理,miniblog部分则利用强大的GAE实现了24小时在线查询,这样也把Della成功移植到Twitter上了。本篇文章将简单说说怎么利用GAE实现在诸如饭否,twitter,叽歪等等miniblog上搭建自己的查询机器人:

在GAE上搭建miniblog查询机器人

言归正传,其实在GAE上搭建自己的机器人是很简单的,本例将以Della1.2的词典查询部分为例,我们将在twitter上建立一个小词典查询机器人,语言使用Python,首先你所需要准备的是:

一个Google帐号
一个Twitter帐号

然后申请一个Google app engine帐号,建立一个Application,Application名称随便取吧,这里我们姑且设置为cidian 。GAE有一个非常酷的cron功能,能实现程序定时运行,所以cidian的总体思路就是:

运行程序,查询GAE数据库中最新消息的id
利用twitter api获取此id为起始的所有消息
把信息逐条分析,符合查询格式的则把信息发送到dict.cn进行查询,并获取返回的结果
把结果再返回给twitter上的查询用户,把最近一条查询消息的id放到GAE数据库中
利用cron实现程序每分钟执行一次

程序分为三部分,主程序,封装Twitter一些功能的程序,还有查询程序,先贴Twitter部分的程序(twitter.py):

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
#coding:utf-8
import re
import base64
import httplib, urllib,urllib2
import time
from google.appengine.ext import db
 
class Tiwdata(db.Model):
    re_id=db.StringProperty()
    re_date=db.StringProperty()
 
 
class Twitter:
    """Class to processing information from the twitter users"""
 
    def __init__( self ):
        """initializing the username and password"""
        self.__username="帐号名"
        self.__password="密码"
        self.__since_id=""
        # Twitter 采用的是base64验证
        self.__authStr = base64.b64encode(self.__username + ":" + self.__password);
 
 
    def setMessage( self , message ):
        """function to set the message"""
        regex=r'^@帐号名 (.*)'
        m=re.findall(regex,message.lower()) #提取其他用户查询消息
        return m
 
    def getMessage( self ):
        """function to return the message"""
        return self.__message
 
 
    def parseMessage( self ):
 
        since_id=self.getSinceId() #从数据库获取最新id  
        #以since_id为参数,通过API读取新收到的所有消息
        if(since_id<>""):
            url="https://twitter.com/statuses/mentions.xml?since_id=%s"%(since_id)
        else:
            url="https://twitter.com/statuses/mentions.xml"
        req = urllib2.Request(url)
        req.add_header("Authorization", "Basic " + self.__authStr)
        try:
            response = urllib2.urlopen(req)
            page=response.read() 
        except:
            return []
 
 
        matches = re.findall(
        r"""(?sx)<created_at>(.*?)</created_at>s*
        <id>(.*?)</id>s*                              
        <text>(.*?)</text>.*?                                
        <screen_name>(.*?)</screen_name>.*?
        </status>
        """,page)
        # 并将最近一条消息的id放置到数据库中
        if(matches<>[]):
            per=Tiwdata()
            per.re_id=matches[0][1]
            per.re_date=self.parseTime(matches[0][0])
            per.put()       
        return matches
 
    def parseTime( self, time_str ):
 
        T="%a %b %d %H:%M:%S +0000 %Y"
        t=time.strptime(time_str,T)
        return time.strftime('%Y-%m-%d-%H-%M-%S', t)
 
 
 
    def sentMessage(self, message):
        """sent message to twitter"""
        headers = {"Content-type": "application/x-www-form-urlencoded",
                "Accept": "text/xml",
                "Authorization": "Basic " + self.__authStr}
        try:
            params = urllib.urlencode({"status": message})
            req = urllib2.Request("https://twitter.com/statuses/update.xml", params, headers)
            response = urllib2.urlopen(req)
        except:
            pass
 
 
 
    def getSinceId( self ):
        msg=db.GqlQuery("SELECT * FROM Tiwdata ORDER BY re_date DESC")
        x=msg.count()
        if x:
            return msg[0].re_id
        else:
            return ""

详细功能见注释,twitter.py主要实现了获取以及提取最新消息,数据库id的更新,已经发送消息到twitter的功能

下面是词典查询功能(dict.py):

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
#!/usr/bin/env python
#coding:utf-8
import urllib2,re
 
class Dict:
    """Class to get EN2CN & CN2EN translation from dict.cn"""
    def __init__( self, word="Della" ):
        """initializing the word and the url received"""   
        self.setWord(word)
        self.setUrl()
 
 
    def setWord( self, word ):
        """function to set the word"""
        self.__word=word
 
 
    def setUrl(self):
        """function to set the url"""
        self.__url="http://dict.cn/ws.php?utf8=true&q=%s"%self.__word
 
 
    def getPage(self):
        """function to get the content of the web page.return the string page content"""        
        url=self.__url
        try:
            page = urllib2.urlopen(url)
            page_content = page.read()
            page.close()
        except:
            return ""
        return page_content
 
 
    def getWord(self):
        """function get the info what we needed from the web page.return a list reply"""
        page_content=self.getPage().replace("\n"," ")
        page_content=unicode(page_content,"utf-8") # set the page content encoding to unicode
        regex=r'<def>(.*)</def>' 
        match=re.findall( regex , page_content )     
        return match

这里利用Dict.cn的词典查询api接口,getWord()获取单词查询结果。

最后是主函数(cidian.py):

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
#coding:utf-8
from Twitter import Twitter
from Dict import Dict
 
def getService( match ):
 
    respons=[]
    try:
        word=match[0].replace(" ","%20")
        dict=Dict(word)
        reply=dict.getWord() #获取查询结果
    except:
        return ["服务端连接有误,请稍后再试"]
    if (reply==[] or cmp(reply[0],unicode("Not Found","utf-8"))==0):
        return ["Oops,你查询的单词不存在或输入格式有误,请检查下或输入'词典'获取帮助"]
    reply[0]="["+match[0]+"] "+reply[0].encode('utf-8')
    respons=reply
 
    return respons
 
twitter=Twitter()
receive=twitter.parseMessage() #获取所有查询信息
for y in reversed(receive):
    word=twitter.setMessage(y[2]) #提取用户查询消息
    if(word<>[]):
        reply=getService( word ) # 交给Dict去处理
        if(reply<>[]):
            for i in reversed(reply):
                if(i<>None):
                    i="@"+y[3]+" "+i #y[3]为twitter上查询用户的帐号名
                    twitter.sentMessage(i) #将查询结果返回

以上就是程序的3个主要部分,最后所要做的就是让这个程序在GAE上定时运行,这样就等于不间断地处理twitter上的查询请求了,利用cron job可以很容易做到。
上传到GAE的程序需要设置好app.yaml和cron.yaml文件,具体使用可以参考GAE的文档,比如在此例中要实现程序每分钟运行一次,可以把app文件和cron文件设置如下:

application: cidian
version: 1
runtime: python
api_version: 1
handlers:
- url: /cidian
script: cidian.py
secure: never

注意app文件中的application就是你在GAE上新建app的名称

cron:
- description: daily job
url: /cidian
schedule: every 1 minutes
timezone: Asia/Shanghai

然后上传,大功告成:) 利用这种思路和网上各种应用功能遍布的api可以很容易做出一个像Della那样多功能的查询机器人,而只要有开发api的miniBlog,你就能为其制作一个机器人。

我前几天就又做了一个twitter机器人Twity,算是Della的姐妹机器人吧,呵呵,当是试手,实现了天气预报,股票查询,还有美剧节目单查询,Twity功能少,但查询的内容是全球性的,比如可以查询全球各个地方的天气~有兴趣的话可以移步这里

4 Comments

brycholeSeptember 13, 2009

Link佐你鸟~

KavinSeptember 22, 2009

okay,link u back ;D

无限September 23, 2009

我也在做GAE上的twitter代理 想不到你先做出来了 呵呵

KavinSeptember 23, 2009

我也是”蓄谋已久”了,哈哈,盯了GAE大半年就等着它出XMPP,一出来我就赶紧做了那Gtalk机器人,呵呵
好像我以前逛过你的Blog哦

Leave a comment

Name (required)
Email (required)
Website