微信机器人:有道翻译小助手——Django + SAE + 微信公众帐号自动回复开放接口
昌江中立科技 | 2018-01-26 23:32:14 | 阅读:39003
SinaAppEngine首先作为一个屌丝开发者要解决服务器的问题。去SAE的官网上用新浪微博的帐号注册一个帐号,成功后会赠给你500云豆,可供一个开发者试用大概5天。SAE非常优秀,如果以后我们想在上面写点应用什么的,可以去申请实名认证和开发者认证,那样每个月都会给你一定数量的云豆,应该能满足日常需求。由于现在SAE上部署Python还处于公测阶段,因此我们要去申请开通可以在上面部署Python程序的权限,现在很好申请的,不一会就会收到已经为你开放了部署Python应用权限的邮件,网上搜到的啥啥还需要排队都是过去了(不排除当你看到这篇文章时SAE已经可以允许所有开发者部署Python的应用了)。OK,完成这些后,就可以到SAE的文档中心读文档来照着文档里面的样例创建一个应用了。有道API然后,去有道API申请一个key,申请的时候网站地址随便填就行。有道API非常简单,直接以GET的形式把要翻译的文本发送到指定的url,然后它会给我们回复翻译结果,我们可以选择xml、json等返回格式,我选得是xml,接着,在浏览器里面按着指定的格式输入url,就可以看到返回结果啦:<?xmlversion="1.0"encoding="UTF-8"?><youdao-fanyi><errorCode>0</errorCode><query><![CDATA[这里是有道翻译API]]></query><!--有道翻译--><translation><paragraph><![CDATA[HereistheyoudaotranslationAPI]]></paragraph></translation></youdao-fanyi>复制代码注意,如果是对词进行翻译的话有的词还会返回一些啥网络释义,基本释义啥的,具体对这个xml解析的方法请看下面的代码。微信公众帐号接着,我们要去微信的公众帐号平台去申请一个公众帐号,不能用现有的已绑定私人微信帐号的QQ号申请,我用的是一个平时不用的QQ号申请的,申请成功后,可以大致看看微信公众帐号的管理平台(现在你知道那些公众帐号,比如王力宏的帐号啥的是怎么运作的了吧),接着去这里仔细阅读微信公众帐号自动回复开放接口的文档,你要从这里学一种如何让用户认证的思想(就是如果用户做了XX,给我返回了XX结果,那么我就能确定,用户是“合法”的),或者认证的方法。大致有一个认识后,赶紧下载他给的样例php源码,也是唯一的可以参考的源码,仔细阅读,如下:<?php/***wechatphptest*///defineyourtokendefine("TOKEN","weixin");$wechatObj=newwechatCallbackapiTest();$wechatObj->valid();classwechatCallbackapiTest{publicfunctionvalid(){$echoStr=$_GET["echostr"];//validsignature,optionif($this->checkSignature()){echo$echoStr;exit;}}publicfunctionresponseMsg(){//getpostdata,Maybeduetothedifferentenvironments$postStr=$GLOBALS["HTTP_RAW_POST_DATA"];//extractpostdataif(!empty($postStr)){echoStr$postObj=simplexml_load_string($postStr,'SimpleXMLElement',LIBXML_NOCDATA);$fromUsername=$postObj->FromUserName;$toUsername=$postObj->ToUserName;$keyword=trim($postObj->Content);$time=time();$textTpl="<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[%s]]></MsgType><Content><![CDATA[%s]]></Content><FuncFlag>0</FuncFlag></xml>";if(!empty($keyword)){$msgType="text";$contentStr="Welcometowechatworld!";$resultStr=sprintf($textTpl,$fromUsername,$toUsername,$time,$msgType,$contentStr);echo$resultStr;}else{echo"Inputsomething...";}}else{echo"";exit;}}privatefunctioncheckSignature(){$signature=$_GET["signature"];$timestamp=$_GET["timestamp"];$nonce=$_GET["nonce"];$token=TOKEN;$tmpArr=array($token,$timestamp,$nonce);sort($tmpArr);$tmpStr=implode($tmpArr);$tmpStr=sha1($tmpStr);if($tmpStr==$signature){returntrue;}else{returnfalse;}}}?>复制代码很简单吧,以至于被一些人说写的很水,但是我觉得,这份php源码还是很有含金量的;网上一些哥们还抱怨啥啥的直接部署样例php不能运行啥的,拜托,有点职业精神好不好,连我这个之前完全不会php的人都能看出来要调用里面的responseMsg()方法才能实现自动回复,样例里面的只是调用了微信接入的认证功能的函数。上面的代码写的很好,不需要我多解释,相信大家能看出来它是怎么工作的。设计与实现接着就可以实现我们自己的应用了,首先把我们在SAE上创建的应用通过SVN检出到本地,然后切换到检出的目录,用Django的命令创建一个应用,目录结构如下:其中,index.wsgi和config.yaml是SAE规定的文件,具体请仔细阅读SAE的文档。之后就可以编写我们自己的服务端代码了,大致思想就是:用户A向公众帐号发送一条消息,微信平台会按着公众帐号预先的设置,把用户A的消息内容和一些其他信息(如创建时间等)以xml的形式post到我们预先设置好的url上(这个url的服务端就是我们要写的在SAE上的应用),我们要做的就是每当接受到微信post请求,我们解析微信平台post过来的xml,得到用户A的消息内容,把消息内容以get的形式发送到有道API,获取有道API返回的xml(或json等),解析,之后按微信平台规定的格式构造成一个xml,作为微信平台post请求的结果给其返回,微信平台收到结果后,会把消息自动回复给用户,用户就能收到翻译结果了。用一个图表示上述过程如下:源码下面贴出逻辑处理部分代码如下(Views.py),各函数功能不言而喻:[python]viewplaincopy#-*-coding:utf-8-*-fromdjango.httpimportHttpResponsefromdjango.templateimportRequestContext,Templatefromdjango.views.decorators.csrfimportcsrf_exemptfromdjango.utils.encodingimportsmart_str,smart_unicodeimportxml.etree.ElementTreeasETimporturllib,urllib2,time,hashlibTOKEN="你设置的Token"YOUDAO_KEY=你申请到的有道的KeyYOUDAO_KEY_FROM="有道的key-from"YOUDAO_DOC_TYPE="xml"@csrf_exemptdefhandleRequest(request):ifrequest.method=='GET':#response=HttpResponse(request.GET['echostr'],content_type="text/plain")response=HttpResponse(checkSignature(request),content_type="text/plain")returnresponseelifrequest.method=='POST':#c=RequestContext(request,{'result':responseMsg(request)})#t=Template('{{result}}')#response=HttpResponse(t.render(c),content_type="application/xml")response=HttpResponse(responseMsg(request),content_type="application/xml")returnresponseelse:returnNonedefcheckSignature(request):globalTOKENsignature=request.GET.get("signature",None)timestamp=request.GET.get("timestamp",None)nonce=request.GET.get("nonce",None)echoStr=request.GET.get("echostr",None)token=TOKENtmpList=[token,timestamp,nonce]tmpList.sort()tmpstr="%s%s%s"%tuple(tmpList)tmpstr=hashlib.sha1(tmpstr).hexdigest()iftmpstr==signature:returnechoStrelse:returnNonedefresponseMsg(request):rawStr=smart_str(request.raw_post_data)#rawStr=smart_str(request.POST['XML'])msg=paraseMsgXml(ET.fromstring(rawStr))queryStr=msg.get('Content','Youhaveinputnothing~')raw_youdaoURL="http://fanyi.youdao.com/openapi.do?keyfrom=%s&key=%s&type=data&doctype=%s&version=1.1&q="%(YOUDAO_KEY_FROM,YOUDAO_KEY,YOUDAO_DOC_TYPE)youdaoURL="%s%s"%(raw_youdaoURL,urllib2.quote(queryStr))req=urllib2.Request(url=youdaoURL)result=urllib2.urlopen(req).read()replyContent=paraseYouDaoXml(ET.fromstring(result))returngetReplyXml(msg,replyContent)defparaseMsgXml(rootElem):msg={}ifrootElem.tag=='xml':forchildinrootElem:msg[child.tag]=smart_str(child.text)returnmsgdefparaseYouDaoXml(rootElem):replyContent=''ifrootElem.tag=='youdao-fanyi':forchildinrootElem:#错误码ifchild.tag=='errorCode':ifchild.text=='20':return'toolongtotranslate\n'elifchild.text=='30':return'cannotbeabletotranslatewitheffect\n'elifchild.text=='40':return'cannotbeabletosupportthislanguage\n'elifchild.text=='50':return'invalidkey\n'#查询字符串elifchild.tag=='query':replyContent="%s%s\n"%(replyContent,child.text)#有道翻译elifchild.tag=='translation':replyContent='%s%s\n%s\n'%(replyContent,'-'*3+u'有道翻译'+'-'*3,child[0].text)#有道词典-基本词典elifchild.tag=='basic':replyContent="%s%s\n"%(replyContent,'-'*3+u'基本词典'+'-'*3)forcinchild:ifc.tag=='phonetic':replyContent='%s%s\n'%(replyContent,c.text)elifc.tag=='explains':forexinc.findall('ex'):replyContent='%s%s\n'%(replyContent,ex.text)#有道词典-网络释义elifchild.tag=='web':replyContent="%s%s\n"%(replyContent,'-'*3+u'网络释义'+'-'*3)forexplaininchild.findall('explain'):forkeyinexplain.findall('key'):replyContent='%s%s\n'%(replyContent,key.text)forvalueinexplain.findall('value'):forexinvalue.findall('ex'):replyContent='%s%s\n'%(replyContent,ex.text)replyContent='%s%s\n'%(replyContent,'--')returnreplyContentdefgetReplyXml(msg,replyContent):extTpl="<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[%s]]></MsgType><Content><![CDATA[%s]]></Content><FuncFlag>0</FuncFlag></xml>";extTpl=extTpl%(msg['FromUserName'],msg['ToUserName'],str(int(time.time())),'text',replyContent)returnextTpl复制代码之后通过SVN把项目部署到SAE上,就OK啦~遇到的问题现在网上这种参考的代码还很少,在SAE上部署调试也非常困难,无奈下我自己写了个脚本,模仿微信平台给自己部署在SAE上的服务端POST消息,看返回的结果。如果出现错误,Django都会产生一个优美的错误页面,获取这个错误页面把它写到本地的一个html里面,用浏览器打开就可以知道是什么错误了。写的过程中还是遇到不少问题的:1.Django的CSRF错误:我用的Django1.4,我尝试了大家说的很多解决办法都会出现403错误,无奈下只能暂时通过修饰符把Django的CSRF暂时禁掉,这个还要以后学Django的深入调研一下;2.Django的编码错误:我也尝试了很多方法,但是都不行,主要是中文处理上,遇到了很多麻烦,最终在这里找到了完美的解决方案,用可爱的Django自带的可爱的方法:smart_str、smart_unicode,就能完美处理中文了。