微信機器人:有道翻譯小助手——Django + SAE + 微信公眾帳號自動回復開放接口
海南中立科技 | 2018-01-26 23:32:14 | 閱讀:31986
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,就能完美處理中文了。