公司员工餐选用的美餐平台,但是经常由于太忙忘记点餐,导致饿着肚子加班。反正菜品的可选项比较少,想着要是能自动点一个就好了。于是有了以下脚本。
接口分析
参数分析
自动点餐无非就是模拟人的行为,自动请求一些关键接口。于是分析了一下美餐网的web端接口,发现关键接口如下:
- 获取菜单接口:meican.com/preorder/ap…???
- 下单接口:meican.com/preorder/ap…???
有了接口只是第一步,第二步开始分析、尝试接口的哪些参数是静态的,哪些是动态的。
首先通过多个请求,可以得知,上述两个关键接口中的client_id、client_secret是静态的固定不变的,猜测可能是web端的身份。
然后第一个接口中的restaurantUniqueId可以发现多次请求并没发生变化,并且通过命名基本可以得出,应该是类似企业食堂在美餐那边的一个唯一id。
第一个接口中的targetTime,通过数据样例分析,以及对比其他非关键接口数据,可以得知,应该是企业在美餐网设置的点餐截止时间。例如我们公司配置的是早餐6:00截止点餐,那么这个targetTime就是 yyyy-MM-dd +06:00。
最后第一个接口还有一个参数tabUniqueId ,最开始我以为是完全静态的,后面通过踩坑得知,这个应该是代表每一餐的唯一id,早、中、晚均不一样。
响应分析
第一个接口拿到的关键响应如下(忽略了一些):
"dishList": [
{
"dishSectionId": xxxx,
"id": xxxx,
"isSection": true,
"name": "晚餐",
"originalPriceInCent": 0,
"priceInCent": 0,
"priceString": ""
},
{
"dishSectionId": xxxx,
"id": y1,
"isSection": false,
"name": "周五 水饺&花生米拌黄瓜",
"originalPriceInCent": 1100,
"priceInCent": 1100,
"priceString": "11"
},
{
"dishSectionId": xxxx,
"id": y2,
"isSection": false,
"name": "周五 A套餐 小鸡炖蘑菇&干炸小黄鱼&肉沫豆腐&土豆片炒肉",
"originalPriceInCent": 1100,
"priceInCent": 1100,
"priceString": "11"
}
]
这个需要结合第二个接口参数来分析,哪些是有用的信息,第二个接口的请求参数如下:
{"corpAddressRemark":"","corpAddressUniqueId":"xxxx","order":orderP,"remarks":remarkP,"tabUniqueId":uu['tab'],"targetTime":targetTime,"userAddressUniqueId":"xxxx"}
其中targetTime应该与第一个接口的一致。remarks没懂到底有没有用,tabUniqueId跟上个接口也是一致。最后order是跟第一个接口响应有关的,如下:
[{"count":1,"dishId":y1}]
其中dishId就是第一个接口dishList中的id。好了关键信息有了,接下来就可以开始编写脚本了。
脚本编写
脚本就不废话了,由于Python不是主力语言,平时写的少,而且由于是摸鱼时间🐟来写的,所以写的比较粗糙和不规范。🤦♂️
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import time
import sys
import requests
import json
import datetime
tomorrow = (datetime.datetime.now()+datetime.timedelta(days=1)).strftime("%Y-%m-%d")
print("tomorrow:", tomorrow)
headers={}
headers1={}
headers2={}
headers3={}
user1={'headers':headers,'phone':'xxxxx','tab':''}
user2={'headers':headers1,'phone':'xxxxx','tab':''}
user3={'headers':headers2,'phone':'xxxxx','tab':''}
user4={'headers':headers3,'phone':'xxxxx','tab':''}
users=[user1,user2,user3,user4]
add_url="https://meican.com/preorder/api/v2.1/orders/add?client_id=xxxx&client_secret=xxxx"
count=0
for i in range(0, len(users)):
try:
count=count+1
uu = users[i]
url = "https://meican.com/preorder/api/v2.1/restaurants/show?tabUniqueId="+uu['tab']+"&targetTime=" + tomorrow + "+06:00&restaurantUniqueId=xxxx&client_id=xxxx&client_secret=xxxx"
r = requests.get(url, headers=uu['headers'])
data = json.loads(r.content)
if 'dishList' in data:
bData = data['dishList'][1]['name']
aAata = data['dishList'][2]['name']
if count==1:
# 发送企业微信通知
print("发通知")
botData=json.dumps({"msgtype":"text","text":{"content":"明日早餐:\r\n"+bData+"\r\n"+aAata,"mentioned_list":["@all"]}})
botR = requests.post(url='https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx', data=botData, headers={"Content-Type":"application/json;charset=utf-8"})
print(botR.content)
print("menu: ", aAata, bData)
bDataId=str(data['dishList'][1]['id'])
targetTime = tomorrow + ' 06:00';
orderP = "[{\"count\":1,\"dishId\":"+bDataId+"}]"
remarkP = "[{\"dishId\":\""+bDataId+"\",\"remark\":\"\"}]"
postData = {"corpAddressRemark":"","corpAddressUniqueId":"xxxxx","order":orderP,"remarks":remarkP,"tabUniqueId":uu['tab'],"targetTime":targetTime,"userAddressUniqueId":"xxxxx"}
print('当前用户:',uu['phone'])
ar = requests.post(url=add_url, data=postData, headers=uu['headers'])
print(ar.content)
data2 = json.loads(ar.content)
if 'status' in data2:
if 'SUCCESSFUL'==data2['status']:
botData2 = json.dumps({"msgtype": "text", "text": {"content": "已为尊贵的会员点餐成功", "mentioned_mobile_list": [uu['phone']]}})
print("成功通知:", botData2)
# 发送企业微信通知
botR2 = requests.post(url='https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxx',data=botData2, headers={"Content-Type": "application/json;charset=utf-8"})
print(botR2.content)
print("点餐成功!")
else:
print("点餐失败!")
else:
print("点餐失败!")
else:
print('无可用信息:', data)
except Exception:
print('err')
print("Good bye!")
定时任务
最后由于本人用的Mac,所以直接使用了launchctl
。
定义任务
首先进入~/Library/LaunchAgents
,在目录下创建一个文件com.meican.plist
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.meican.plist</string>
<key>ProgramArguments</key>
<array>
<string>/Users/zhaojingzhou/workspace/sources/meican.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Minute</key>
<integer>00</integer>
<key>Hour</key>
<integer>16</integer>
<!-- <key>Day</key>
<integer>*</integer> -->
<key>Weekday</key>
<array>
<integer>1</integer>
<integer>2</integer>
<integer>3</integer>
<integer>4</integer>
<integer>7</integer>
</array>
</dict>
<key>StandardOutPath</key>
<string>/Users/zhaojingzhou/workspace/sources/stdout</string>
<key>StandardErrorPath</key>
<string>/Users/zhaojingzhou/workspace/sources/error</string>
</dict>
</plist>
ProgramArguments
任务执行的脚本,我这里用shell 将上面写的Python脚本包了一下:
/usr/local/bin/python3.9 /Users/zhaojingzhou/workspace/sources/meican.py
脚本一定要给执行权限啊
chmod 775 xxx.sh
StartCalendarInterval
我这里定义的是周1,2,3,4,7下午16:00执行。需要注意的是0,7都代表周日。
OutPath
StandardOutPath,StandardErrorPath 代表标准输出,和错误输出,用来排查脚本错误。
加载任务
所有的的都准备好了之后执行:
launchctl load -w com.meican.plist
如果修改了任务定义需要unload之后在load,unload:
launchctl unload com.meican.plist
如果想立即执行一次:
launchctl start com.meican.plist
最后
目前跑了一段时间下来,感觉还行,再也不用饿肚子啦。。。
希望美餐别调整接口。。。
码字不易,且看且珍惜