公司员工餐选用的美餐平台,但是经常由于太忙忘记点餐,导致饿着肚子加班。反正菜品的可选项比较少,想着要是能自动点一个就好了。于是有了以下脚本。
接口分析
参数分析
自动点餐无非就是模拟人的行为,自动请求一些关键接口。于是分析了一下美餐网的web端接口,发现关键接口如下:
- 获取菜单接口:https://meican.com/preorder/api/v2.1/restaurants/show?tabUniqueId=???&targetTime=???&restaurantUniqueId=???&client_id=???&client_secret=???
- 下单接口:https://meican.com/preorder/api/v2.1/orders/add?client_id=???&client_secret=???
有了接口只是第一步,第二步开始分析、尝试接口的哪些参数是静态的,哪些是动态的。
首先通过多个请求,可以得知,上述两个关键接口中的client_id、client_secret是静态的固定不变的,猜测可能是web端的身份。
然后第一个接口中的restaurantUniqueId可以发现多次请求并没发生变化,并且通过命名基本可以得出,应该是类似企业食堂在美餐那边的一个唯一id。
第一个接口中的targetTime,通过数据样例分析,以及对比其他非关键接口数据,可以得知,应该是企业在美餐网设置的点餐截止时间。例如我们公司配置的是早餐6:00截止点餐,那么这个targetTime就是 yyyy-MM-dd +06:00。
最后第一个接口还有一个参数tabUniqueId ,最开始我以为是完全静态的,后面通过踩坑得知,这个应该是代表每一餐的唯一id,早、中、晚均不一样。
响应分析
第一个接口拿到的关键响应如下(忽略了一些):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| "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" } ]
|
这个需要结合第二个接口参数来分析,哪些是有用的信息,第二个接口的请求参数如下:
1
| {"corpAddressRemark":"","corpAddressUniqueId":"xxxx","order":orderP,"remarks":remarkP,"tabUniqueId":uu['tab'],"targetTime":targetTime,"userAddressUniqueId":"xxxx"}
|
其中targetTime应该与第一个接口的一致。remarks没懂到底有没有用,tabUniqueId跟上个接口也是一致。最后order是跟第一个接口响应有关的,如下:
1
| [{"count":1,"dishId":y1}]
|
其中dishId就是第一个接口dishList中的id。好了关键信息有了,接下来就可以开始编写脚本了。
脚本编写
脚本就不废话了,由于Python不是主力语言,平时写的少,而且由于是摸鱼时间🐟来写的,所以写的比较粗糙和不规范。🤦♂️
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?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>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脚本包了一下:
1
| /usr/local/bin/python3.9 /Users/zhaojingzhou/workspace/sources/meican.py
|
脚本一定要给执行权限啊
StartCalendarInterval
我这里定义的是周1,2,3,4,7下午16:00执行。需要注意的是0,7都代表周日。
OutPath
StandardOutPath,StandardErrorPath 代表标准输出,和错误输出,用来排查脚本错误。
加载任务
所有的的都准备好了之后执行:
1
| launchctl load -w com.meican.plist
|
如果修改了任务定义需要unload之后在load,unload:
1
| launchctl unload com.meican.plist
|
如果想立即执行一次:
1
| launchctl start com.meican.plist
|
最后
目前跑了一段时间下来,感觉还行,再也不用饿肚子啦。。。
希望美餐别调整接口。。。
码字不易,且看且珍惜