微信公众平台的开发者文档
https://www.w3cschool.cn/weixinkaifawendang/
python,flask,SAE(新浪云),搭建开发微信公众账号
http://www.oschina.net/code/snippet_1768500_36580
从零开始 Python 微信公众号开发
https://zhuanlan.zhihu.com/p/21354943
新浪云应用
http://www.sinacloud.com/doc/sae/python/
SAE Python如何搭建本地开发环境
https://www.liaoxuefeng.com/article/00137389260145256f699d538ae4fd3910be06d2753b192000
从零开始这篇文章讲得很清楚,新建好一个python应用后,就可以在上面搭建跟微信公众号的接口了。
使用文章的wxpytest代码,会出现not found错误,不知道什么原因,后来重新找了个文章的例子就可以了。
用get方法验证,用post方法接受发送XML格式的信息
config.yaml
name: wxpy
version: 1 libraries:
- name: webpy
version: "0.36" - name: lxml
version: "2.3.4"
index.wsgi
import sae
sae.add_vendor_dir('vendor')
from meishidaren import app
application = sae.create_wsgi_app(app)
meishidaren.py
#encoding=utf-8
import sys
reload(sys)
sys.setdefaultencoding('utf8')
import time
from flask import Flask, request,make_response
import hashlib
import xml.etree.ElementTree as ET
'''
import sys
sys.path.append("utils")
'''
from utils import timehelper
from utils import travelhelper app = Flask(__name__)
app.debug=True
@app.route('/',methods=['GET','POST']) def wechat_auth():
if request.method == 'GET':
token='weixintest'
data = request.args
print "Get data:",data
signature = data.get('signature','')
timestamp = data.get('timestamp','')
nonce = data.get('nonce','')
echostr = data.get('echostr','')
s = [timestamp,nonce,token]
s.sort()
s = ''.join(s)
if (hashlib.sha1(s).hexdigest() == signature):
return make_response(echostr)
else:
return make_response(echostr)
else:
rec = request.stream.read()
print "Post data:",rec
xml_rec = ET.fromstring(rec)
msgType=xml_rec.find("MsgType").text
tou = xml_rec.find('ToUserName').text
fromu = xml_rec.find('FromUserName').text
content = xml_rec.find('Content').text.strip()
content = get_response(msgType,xml_rec,content)
xml_rep = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content><FuncFlag>0</FuncFlag></xml>"
response = make_response(xml_rep % (fromu,tou,str(int(time.time())),content))
response.content_type='application/xml'
return response def get_response(msgType,xml_rec,content):
#try:
if msgType == 'image':
picurl = xml_rec.find('PicUrl').text
content = "图片地址是:"+picurl
else:
print content
if content == "1": #旅游
t = travelhelper.TravelHelper()
content = t.GetZhuhaiHuWai()
elif content == '2':
content = '未开发'
else:
content = "收到的内容是:"+ content+"<br/>参考1.旅游"
'''except Exception,e:
content = 'Error:'+e.message
finally:'''
return content
配置微信公众号的时候又出现验证失败,从日志中找到url用浏览器打开,发现提示实名认证未通过,看来这就是原因了。果然,在拍身份证上传后,配置微信公众号就成功了。
接着搭建本地SAE环境,注意必须运行在该目录的git bash下,我之前不知道,在命令行运行dev_server.py总是打开文本,纳闷了好久
$ git clone https://github.com/sinacloud/sae-python-dev-guide.git
$ cd sae-python-dev-guide/dev_server
$ python setup.py install
$ dev_server.py
MySQL config not found: app.py
Start development server on http://localhost:8080
改端口:$ dev_server.py -p 8090
看到廖雪峰文章说本地和SAE上环境代码有些不一样,但我这里用的代码都是可以不用修改
本地代码deploy到新浪上也是通过git bash命令行,刚开始先clone,后来每次都是无脑输入下面三个命令
● 在你应用代码目录里,克隆git远程仓库
● $ git clone https://git.sinacloud.com/wxpy
● 输入您的安全邮箱和密码。
● $ cd wxpy
● 编辑代码并将代码部署到 `origin` 的版本1。
● $ git add .
或者git add . --ignoreremoval
● $ git commit -m 'Init my first app'
● $ git push origin 1
push的时候次次都要输入用户名密码,好烦,我还不知怎么搞,下面这些命令无效啊
$git config --global credential.helper wincred
$git config --global user.email "you@example.com"
$git config --global user.name "Your Name"
有时候报错,是因为我在代码代理上自己编辑了代码,要求用git pull,才能继续deploy
● ![rejected] master->master(fetch first)
● error:failed to push some refs to 'https://github.com/xxx/xxx.git'
git pull
本地上的测试代码,用到reqhelper.py里面的 SavePostResponse function ,其它是不用理会
reqhelper.py
#encoding=utf-8
import time
import traceback
import requests
import json
import confhelper
import os
from bs4 import BeautifulSoup #__version__ = 4.3.2
from urlparse import urljoin
import threadpool
import sys class ReqHelper(object):
def __init__(self, url=r'http://www.baidu.com',headers={}, outfile=r'd:\temp\log.txt',proxies={},timeout=3):
self.confile = r"..\conf\test.conf"
if os.path.exists(self.confile):
self.conf=confhelper.ConfHelper(self.confile)
self.confs = self.conf.GetAllConfig()
if not(url.startswith('http://')) and not(url.startswith('https://')):
url = 'http://'+url
#代理格式 http://user:password@host/
if len(proxies)==0 and self.confs.has_key("username") and self.confs.has_key("password") and self.confs.has_key("httpserver"):
proxies['http'] ="http://%s:%s@%s"%(self.confs["username"],self.confs["password"],self.confs["httpserver"])
proxies['https'] ="http://%s:%s@%s"%(self.confs["username"],self.confs["password"],self.confs["httpsserver"])
self.proxies = proxies
#SOCKS 代理格式 socks5://user:pass@host:port
'''proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}'''
if len(headers)==0:
headers={'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
,'Accept-Encoding':'gzip,deflate,sdch'
,'Accept-Language':'zh-CN,zh;q=0.8'
,'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
,'Content-Type':'application/x-www-form-urlencoded'}
self.headers = headers
self.url = url
if outfile=="" and self.confs.has_key("logpath"):
outfile=self.confs["logpath"]
self.outfile = outfile
self.session = requests.session()
self.session.proxies=self.proxies
if self.confs.has_key("theadnum") and self.confs["theadnum"].isdigit():
self.theadnum = int(self.confs["theadnum"])
else :
self.theadnum = 10 #下载文件到本地
def DownloadFile(self,sourceurl,destdir):
try:
with requests.Session() as s:
s = requests.Session()
r = s.get(sourceurl,headers=self.headers)
destfile = os.path.join(destdir,os.path.basename(sourceurl))
with open(destfile,"wb") as file:
file.write(r.content)
print time.ctime(), 'Download :',sourceurl,'\nTo :',destfile
except Exception,e:
print time.ctime(), 'Error :',e.message,'\n',traceback.format_exc() # 如果目录不存在就新建
def GetDownloadPath(self,dp=""):
if dp=="":
dp=self.confs["downloadpath"]
if not os.path.exists(dp):
os.mkdir(dp)
return dp # 按条件取出网页上所有href地址
def GetHrefs(self,func, url):
r = requests.get(url, proxies=self.proxies,headers=self.headers,timeout=3)
bs = BeautifulSoup(r.text) #解析获取的网页
links=bs.find_all('a')
newls=filter(func,links)
durls=[]
for link in newls:
hrefstr = link['href']
if not hrefstr.startswith('http'):
if not url.endswith('/'):
url = url + '/'
durls.append(urljoin(url,hrefstr))
#去重
durls = list(set(durls))
return durls # 下载网页上所有文件
def DownloadUrlFiles(self,func=None,url="",dp=""):
#func=lambda x:x['href'].endswith('.ipk')
#func=None
dp = self.GetDownloadPath(dp) data=[]
if len(url) > 0:
durls=self.GetHrefs(func,url)
for durl in durls:
data.append(((durl,dp), None))
else:
urls = self.conf.GetSectionConfig("downloadurlfiles")
for (dkey,durl) in urls.items():
destdir = os.path.join(dp,dkey)
destdir = self.GetDownloadPath(destdir)
durls = self.GetHrefs(func,durl)
for durl in durls:
data.append(((durl,destdir), None))
#for durl in durls:
# self.DownloadFile(durl,dp)
#用多线程下载
pool = threadpool.ThreadPool(self.theadnum)
reqs = threadpool.makeRequests(self.DownloadFile, data)
[pool.putRequest(req) for req in reqs]
pool.wait() # 下载单个文件链接
def DownloadUrlFile(self,url="",dp=""):
dp = self.GetDownloadPath(dp)
if len(url) > 0:
self.DownloadFile(url,dp)
else:
urls = self.conf.GetSectionConfig("downloadurlfile")
for (dkey,durl) in urls.items():
#dfname = os.path.join(dp,fkey)
self.DownloadFile(durl,dp) # 保存网页内容
def SaveHtmlContent(self,r,outfile="",writemode='a'):
if outfile == "" and writemode == 'a' and self.confs.has_key("logpath"):
outfile = self.confs["logpath"]
if outfile == "" and writemode == 'w' and self.confs.has_key("htmlpath"):
outfile = self.confs["htmlpath"]
f = file(outfile, writemode)
if writemode=='a':
lines=[r.request.method,'\n',r.request.url,'\n',str(r.headers),'\n',str(r.request.body),'\n',r.url,'\n',str(r.status_code),'\n', str(r.headers),'\n',r.text.encode('utf-8','ignore')]
print lines
f.writelines(lines)
if writemode=='w':
f.write(r.text.encode('utf8'))
f.close() # 测试网址是否能打开
def TestUrls(self,save=False):
httpurls=self.conf.GetSectionConfig("httpurl")
httpsurls=self.conf.GetSectionConfig("httpsurl")
urls=dict(httpurls,**httpsurls)
for (urlkey,url) in urls.items():
try:
if not(url.startswith('http://')) and not(url.startswith('https://')):
url = 'http://'+url
r = requests.get(url, proxies=self.proxies,headers=self.headers,timeout=3)
print time.ctime(),r.url," Reponse status code :",r.status_code
if save == True:
self.SaveHtmlContent(r,self.outfile,'a')
except Exception,e:
print time.ctime(), 'Error:',e.message,'\n',traceback.format_exc() def SaveHtml(self, user='', password = '', outfile=''):
r = requests.get(self.url, auth=(user, password))
self.SaveHtmlContent(r,outfile,'w') # 用同一个会话打开链接
def SaveHtmlAfterLogin(self, geturl, user='', password = '',outfile=''):
r = requests.get(self.url, auth=(user, password))
s = requests.Session()
r = s.get(geturl)
self.SaveHtmlContent(r,outfile,'w') def SavePostResponse(self, data={}, cookies={} , outfile=''):
r = requests.post(self.url,data=data, cookies=cookies)
self.SaveHtmlContent(r,outfile,'w') def SaveResponseWithCookie(self, headers={'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
,'Accept-Encoding':'gzip,deflate,sdch'
,'Accept-Language':'zh-CN,zh;q=0.8'
,'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
,'Content-Type':'application/x-www-form-urlencoded'}
, cookies={}):
r = requests.get(self.url, headers=headers,cookies=cookies);
f = file(self.outfile, 'w')
f.write(json.dumps(r.headers.__dict__))
f.write('\n')
f.write(r.text.encode('utf8'))
f.close() def GetCookies(self, user='', password = ''):
r = requests.get(self.url, auth=(user, password))
return r.headers["Set-Cookie"] if __name__ == '__main__':
''' test.conf
#reqhelper
[httpurl]
baidu=http://www.baidu.com
openwrt=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/base
json=https://api.github.com/repositories/1362490/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad [httpsurl] [downloadurlfiles]
#baseipk=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/base/
luciipk=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/luci/
managementipk=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/management/
oldpackagesipk=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/oldpackages/
routing=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/routing/
telephony=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/telephony/ [downloadurlfile]
ipk1=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/base/6in4_17-1_all.ipk
ipk2=http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/base/agetty_2.24.1-1_ramips_24kec.ipk
'''
url="http://www.sobaidupan.com/search.asp?wd=pyspider&so_md5key=7db75ceb9f6873de9fb027aa3a7cd7"
req = ReqHelper(url=url)
#保存某个网页
req.SaveHtml(url,outfile=r"d:\temp\test.html")
#测试网址
req.TestUrls()
if len(sys.argv) > 1 and sys.argv[1]=="download":
#下载文件
req.DownloadUrlFiles(func=lambda x:x['href'].endswith('.ipk'),url="",dp="")
>>> wxlink="http://1.wxpy.applinzi.com/"
>>> req2=reqhelper.ReqHelper(wxlink)
>>> textdata=
'<xml>\n<ToUserName>\n<![CDATA[fromUser]]>\n</ToUserName>\n<FromUserName>\n<![CDATA[toUser]]>\
n</FromUserName>\n<CreateTime>1503586446</CreateTime>\n<MsgType>\n<![CDATA[text]]>\n</MsgType>
\n<Content>\n<![CDATA[this is a test]]>\n</Content>\n<FuncFlag>0</FuncFlag>\n</xml>'
>>> req2.SavePostResponse(data=textdata)
总结下SAE遇到的坑:
1.一个应用扣了10个云豆,刚开始觉得好乐观,一进账户送了200个,实名认证又送了300个,后来才知道是每天扣10个云豆。。。这样也一个应用只能用一个月而已
2.mysql数据库要收费我预料到,想着自己玩下用sqlite就好了,但自带的sqlite它居然不支持。。。真绝
3.好吧,我不用数据库了,用pandas把取到的数据处理下吧,毕竟官方也说可以在vendor目录下放第三方包的,可是比方说pyquery require lxml库,pandas require numpy,six,pytz,无论我全部拷进vendor目录还是用pip install -t vendor pandas
当把代码deploy去sae后,总是import module报错。
4.这些第三方包太大,上传着居然说我达到限额了,应用空间的5G size实在给了我太乐观的假象。
5.代码管理是可以在线编辑代码的,虽然慢点,但有时候懒得敲命令。可是,原来这样修改应用不生效的,一定要用git bash来deploy.
也没有个手动重启应用的功能,而且这样改还会需要再pull一次才行。
6.sae上使用logging没有用,可以直接用print打印出信息,在日志那里可以看到
写了个travelhelper.py文件用来拿珠海户外网发布的活动,代码在git上,本地测试Ok,无奈SAE上import module错误不知怎样fix
代码放在github上
https://github.com/sui84/sae