公众号api接口开放平台(公众号api功能怎么授权)
341
2022-09-06
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Network_Detection.html
#! /usr/bin/env python # -*- coding: utf-8 -*- """ time : 2020-07-16 notive : add WeChar IP white list """ import requests import json class WeChat(object): """ -1 系统繁忙,此时请开发者稍候再试 0 请求成功 40001 AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性 40002 请确保grant_type字段值为client_credential 40164 调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。(小程序及小游戏调用不要求IP地址在白名单内。) 89503 此IP调用需要管理员确认,请联系管理员 89501 此IP正在等待管理员确认,请联系管理员 89506 24小时内该IP被管理员拒绝调用两次,24小时内不可再使用该IP调用 89507 1小时内该IP被管理员拒绝调用一次,1小时内不可再使用该IP调用 """ def __init__(self): self._access_token = "" self._base_url = "https://api.weixin.qq.com" self._bak_url = "https://api2.weixin.qq.com" def _get_access_token(self): try: print("正在获取 WeChat access") app_id = "wx***********" app_secret = "*********************" get_url = "{0}/cgi-bin/token?grant_type=client_credential&appid={1}&secret={2}".format(self._base_url, app_id, app_secret) res = requests.get(url=get_url) if res.status_code != 200 or res.text is None: get_url = "{0}/cgi-bin/token?grant_type=client_credential&appid={1}&secret={2}".format(self._bak_url, app_id, app_secret) res = requests.get(url=get_url) if res.status_code != 200 or res.text is None: print("获取 WeChar url error") return False res_wechar = json.loads(res.text) if "errcode" in res_wechar: print("WeChar _get_access_token error {0}:{1}".format(res_wechar["errcode"], res_wechar["errmsg"])) return False #print("access_token:", res_wechar["access_token"]) #print("expires_in:", res_wechar["expires_in"]) self._access_token = res_wechar["access_token"] print("获取 WeChar token successful") url = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token={}".format(self._access_token) res = requests.post(url=get_url, data={}) print(res.text) except Exception as e: print("WeChat _get_access_token error:{0}, line number:{1}".format(str(e), e.__traceback__.tb_lineno)) return False return True if __name__ == "__main__": abc = WeChat() if (abc._get_access_token()): print("token:", abc._access_token)
axios的使用
puppeteer自动化
docker的初识
api服务器的部署
在上次完成了服务器收发消息功能后,本打算做一个小案例实现如下功能,
当我输入特定文字的时候,服务器返回我需要的文字。
听起来就这么简单,但是万万没想到,这个小案例竟遇到了数不清的坑,让这个项目变得不简单,前前后后花了1天半的时间总算完成初步的实现。本来是想一笔带过的测试,现在变成能够单独写一篇出来,下面就让我来娓娓道来。
因为是做个api所以我一开始就决定在本地先测试,完成后再传到服务器,觉得1个小时差不多就能够搞定。最初的想法:
做成一个模块,当用户输入特定文字后,调用这个模块来获得数据。
需要的数据在某个网站上,所以用axios发送请求去获取页面
解析页面的元素获取要的数据返回给服务器
服务器收到需要的数据后发回给用户
axios收发请求我用过很多次,得心应手了,没想到这次就翻车了。安装axios是必要的,axios的中文文档: http://www.axios-js.com/zh-cn/docs/
npm install axios
先看下我的代码:
//getDailyVerse.js module.exports = () =>{ return new Promise((reslove, reject) => { const axios = require('axios') axios.get('https://www.bible.com/zh-CN/verse-of-the-day').then(res => { reslove(res.data) }) }) }
上述代码在遇到80%的网站都能正常解析html页面,但如果网页是前端渲染的话,对不起,你拿不到想要的数据。在这个案例中,恰好我碰到了20%的情况,而且那20%不是前端渲染的问题,而是屏蔽爬虫的问题,当网站检测到你是爬虫后我直接被403没有权限访问了。
我想权限的问题好说,在axios请求加一个请求头来模拟浏览器应该能解决,于是修改下代码, 在请求中添加个请求设置,在设置中添加headers:
//getDailyVerse.js //... axios.get('https://www.bible.com/zh-CN/verse-of-the-day', { headers: { 'user-agent': `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36` } }).then(res => { reslove(res.data) }) //...
尝试重新运行发现被403,我开始怀疑网站不仅仅是检测浏览器吗?,在我尝试伪造cookie等字段完全没起作用后,又尝试使用node原生的https来访问,node文档: http://nodejs.cn/api/https.html#httpsgeturl-options-callback
//getDailyVerse.js const https = require('https') https.get(require('./config').dailyVerseLink,{ headers: { 'user-agent': `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36` } }, res =>{ res.on('data',data=>{ console.log(data.toString()) }).on('end', ()=>{ console.log(res.statusCode) }) })
同样也不行,我放弃了,准备使用方案二。
如果有哪个大神在这里能有新的突破请联系我。
puppeteer是我之前使用自动化浏览器的库,同样js能有puppeteer写爬虫软件也不输python,我的计划就是使用puppeteer代替axios,其余的方法都一样。
puppeteer文档: https://zhaoqize.github.io/puppeteer-api-zh_CN/
安装puppeteer
npm install puppeteer
连接代码
//getDailyVerse.js module.exports = async() => { let data = '' const puppeteer = require('puppeteer') const browser = await puppeteer.launch() const page = await browser.newPage() //这里把网址写入配置文件中,就不会被上传到git上,也方便修改 await page.goto(require('./config').dailyVerseLink) await page.screenshot({path: './puppeteer.png'}) await browser.close(); return data }
以上代码会在工程目录下生成一个截图,查看截图发现果然被屏蔽了。
解决这种问题这里有两个方案,第一个是不要使用无头浏览器,另一个我先不表。我先选择了第一个方案,没想到这是后续噩梦的开始。
解决puppeteer屏蔽的方法一
设置有头浏览器的方法就是一句话,修改下代码:
//getDailyVerse.js //... //取消无头设置 const browser = await puppeteer.launch({ headless: false }) //...
果然成功了。
获取元素
先在chrome打开网页使用“开发者工具”来定位需要的元素的标签,然后获取里面的文字,代码就直接在这里写完全了:
//getDailyVerse.js module.exports = async() => { let data = '' const puppeteer = require('puppeteer') const browser = await puppeteer.launch({ headless: false }) const page = await browser.newPage() //这里把网址写入配置文件中,就不会被上传到git上,也方便修改 await page.goto(require('./config').dailyVerseLink) //使用$eval选择器定位class类是‘.verse-wrapper.ml1.mr1.mt4.mb4’的元素,然后获取该元素内部的文字 data = await page.$eval('.verse-wrapper.ml1.mr1.mt4.mb4', el => el.textContent) await page.screenshot({path: './puppeteer.png'}) //打印测试 console.log(data) await browser.close(); return data }
运行一下,成功!但是我却忘记了一个大问题,这个是要跑在服务器上的。
这里有个小提示,因为浏览器运行后每次必要关掉,但关掉后数据就没有了,所以我在代码最前面let声明了data的字符串,当获得数据后就保存在其中,关闭浏览器后再return数据也就不丢失了。
因为代码不大,只要添加一个文件我就直接使用vscode连接服务器(如何使用vscode远程连接服务器,请参考之前的文章:https://ivanccc.com/archives/gong-zhong-hao-1 )同样的测试,额。。。出问题了,给大家思考下问题出在哪里。
问题就是我这个系统是没有界面的,如何能在linux上使用有界面的浏览器呢。
这个问题很头疼,我花了一整天时间查阅了各种文档资料,尝试数十种方法都无法解决后认为这又是一条死路。
此处省略一万字。。。。。。
关于这个问题,如果有哪个大神知道解决方案请联系我。
我重启原来的方法,还是使用无头浏览器,把headless设置为true后看看能不能使用。额。。。这回是新的错误,错误中也提示了解决的文档:https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md
意思是不能够在root下使用sandbox环境,这里按照文档加上一个设置即可:
//getDailyVerse.js //... //取消无头设置,设置no-sandbox环境 const browser = await puppeteer.launch({ headless: false, args: ['--no-sandbox'] }) //...
新的错误又来了,这个错误是puppeteer包本身的内部错误
我解决不了。。。
想了一晚,重新查看官方提供解决方案中的内容,看到了一个技术docker。
决定尝试新的方案如下:
找个能运行puppeteer的稳定镜像
在这个合适的镜像生成实例,然后在其上测试代码
成功后设置一个端口映射,所以这次的方法不仅仅只是一个模块,还需要升级成为一个服务
使用项目来访问这个端口实现
我遇到的这个问题其实是环境引起的,所以如果能用配置完成的环境是最省力的方案。
https://www.docker.com/
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。
安装docker
我直接在服务器宝塔面板中打开软件商店搜索docker并安装
注册账号
在上面提供的官网链接中注册账号,完成后转到 https://hub.docker.com/
这里可以搜索需要的镜像,和查看镜像的版本号
这里我尝试了一些puppeter的镜像,花了半天的时间总算找到合适的环境来运行我这个服务,在此基础上安装了需要的其余依赖和环境,生成了满足需求的镜像,大家需要的话直接下载就可以了。
此处继续省略一万字。。。。。。
镜像简介
镜像名:ayachensiyuan/puppeteer, 镜像开放了3001端口作为镜像服务端,使用方法是直接http发起‘GET’请求’/api/getDailyVerse‘即可收到数据。一键安装,真香。。。
这里直接在浏览器查看需要提前把端口加入安全组,同时在宝塔安全设置中开放端口,测试完成记得关闭端口。图片中我是在本地环境运行。)
安装镜像
使用termius打开并连接远程服务器,使用以下命令下载镜像(前提是在宝塔中安装好了docker)
docker search ayachensiyuan/puppeteer docker pull ayachensiyuan/puppeteer:1.3 docker images
docker search name —— 该命令和在网站上搜索的是一个效果
docker pull name:label —— 该命令就是下载该镜像,label是版本号,如果不指定就是默认latest
docker images —— 列出你下载的所有镜像,在宝塔面板中也可查看
docker run -id --name=bibleAPI -p 3001:3001 centos:7 node /root/dailyBible/app.js
docker run <参数> <镜像> <命令> —— 运行镜像
–name 容器的名字,不指定的话会自动生成奇怪的名字
-id 后台运行容器
-v 可以设置数据卷,持久化储存数据(具体参看文档)
-p设置映射端口,这里设置了服务器的3001对应容器的3001
<命令> 进入容器后执行的操作,这里是启动node客户端操作
docker其他命令
在容器中使用exit命令就可退出容器,如果是-it进入的话,退出的同时就会终止运行容器,其他容器、镜像相关的命令有:
docker ps -a —— 查看所以容器(包括停止的,不加-a参数是查看运行的容器)
docker exec —— 进入运行的容器
docker stop —— 停止容器
docker start —— 运行容器
docker rm —— 删除容器(需停止)
docker rmi —— 删除镜像(需先删除对应容器)
docker inspect —— 查看容器
docker commit yourDockerName:label —— 容器生成镜像
docker save -o <imageName.tar> imageName:label —— 镜像打包
docker load -i <imageName.tar> —— 镜像解压生成
镜像答疑
有兴趣的小伙伴可以使用以下命令进入镜像查看代码:
docker exec -it bibleAPI /bin/bash
项目目录在’/root/dailyBible/‘中
看一下getDailyVerse.js中关键代码
//getDailyVerse.js module.exports = async() => { let data = '' const puppeteer = require('puppeteer') const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] }) const page = await browser.newPage() await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4181.9 Safari/537.36") await page.goto(require('./config').dailyVerseLink) data = await page.$eval('.verse-wrapper.ml1.mr1.mt4.mb4', el => el.textContent) await page.screenshot({path: './puppeteer.png'}) await browser.close(); return data }
之前说绕过浏览器检测一个方法是把headless设置为false,但者会产生linux环境下无法启动的问题,所以另一个方式还是请求头的问题,puppeteer这里是在页面goto语句之前就先把“user-agent”设置好,加上这句话:
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4181.9 Safari/537.36")
原来网站还是由于请求头的“user-agent”的原因被屏蔽,所以我有点疑问,为什么在axios和https中设置headers都不起作用?不知道有人能解答下吗?
服务器的项目在之前的文章中有介绍,如有疑问可以查看页首链接跳转。
当你在服务器上安装上面提供的命令运行了ayachensiyuan/puppeteer:1.3的镜像容器后,先在宝塔面板的网站选项卡中停止node项目
之后使用vscode远程连接到服务器工程目录,修改app.js中POST’/wx’的RESTAPI。
之前代码有个问题也一并解决,就是我仅仅设置消息类型为text的解析,没有考虑到其他类型,如果当用户关注或取消关注这类消息的类型没有content字段,服务器解析时候就会崩溃,所以这里消息处理仅仅针对文字类型的消息,其他类型先不用理会。
//getDailyVerse.js //... //接受消息并做出反应 app.post('/wx', async (req, res) => { const xmlMsg = await require('./getUserMessage')(req) const jsonData = await require('./paresMsg')(xmlMsg) //教学环境做个简单的处理 if (jsonData.MsgType == 'text') { //发送每日经文 if (jsonData.Content[0] == '每日经文') { const axios = require('axios') //使用axios来处理请求 axios.get('http://127.0.0.1:3001/api/getDailyVerse').then(response => { //使用sendMsg方法发回收到的数据 //解析数据都是数组,所以需要[0]获取 const xmlSendData = require('./sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], response.data) res.send(xmlSendData) }) } else { //其余文字还是暂时以倒转文字返回 const xmlSendData = require('./sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], jsonData.Content[0].split('').reverse().join('')) //const xmlSendData = require('./jsToXml')(sendData) //console.log(xmlSendData) res.send(xmlSendData) } } else { //暂时目前无需处理其他类型数据 res.end('') } }) //... //parseMsg.js const xml2js = require('xml2js') const xmlParser = new xml2js.Parser() module.exports = parseMsg = (xmlmsg)=>{ return new Promise((reslove,reject)=>{ xmlParser.parseString(xmlmsg,(err,res)=>{ // console.log(res) const callbackData = { ToUserName: res.xml.ToUserName, FromUserName: res.xml.FromUserName, MsgType: res.xml.MsgType, CreateTime: res.xml.CreateTime, Content: res.xml.Content, MsgId: res.xml.MsgId } reslove(callbackData) }) }) }
测试一下:
项目其实并不复杂,但过程还是走了很多弯路的,尤其在制作docker镜像期间尝试了很多镜像,最终找到适合puppeteer的环境。随着功能越来越多app.js的文件也变得庞大,下次的更新就整理分离代码,按照业务逻辑去规划你的代码。请继续关注。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~