群报数自动打卡系统实现原理
一个基于微信小程序逆向的自动打卡工具,支持多账号、定时执行、邮件通知。
起因
我的辅导员一位非常"负责"的老师,要求我们每天必须在群报数小程序上进行定位打卡。
每天打卡这件事本身不难,难的是每天都记得打卡。忘记一次就要被点名,与其每天提心吊胆怕忘记,
不如花点时间一劳永逸——分析一下群报数小程序的内部结构,写个自动化脚本。
于是就有了这个项目。
核心难点:Token 获取链路
整个系统最关键的部分是如何获取打卡接口的 Authorization Token。这涉及到微信小程序的授权机制。
认证流程图
用户微信授权
↓
获取 refresh_token(长期有效,约30天)
↓
调用微信 API 刷新 → 获取 access_token(短期有效,2小时)
↓
用 access_token 登录群报数 → 获取 Authorization Token
↓
携带 Authorization 调用打卡接口
第一步:抓取初始 Token
工具准备
- 安卓手机 + 微信
- 抓包工具(HttpCanary / Charles / Fiddler)
- 需要配置 HTTPS 证书信任
抓取目标
打开群报数小程序,抓取微信授权请求,找到以下关键信息:
{
"access_token": "98_xxxxx",
"refresh_token": "98_xxxxx", // 这个最重要!
"openid": "oxkDB6xxxxx",
"unionid": "oBFnS5xxxxx"
}
关键点:refresh_token 是长期有效的(约30天),只要定期使用就会自动续期。这是整个自动化的基础。
第二步:刷新 access_token
微信提供了 refresh_token 刷新接口:
def refresh_wechat_token(refresh_token):
url = "https://api.weixin.qq.com/sns/oauth2/refresh_token"
params = {
"appid": "wxe7be3420358ab61e", # 群报数小程序的 appid
"grant_type": "refresh_token",
"refresh_token": refresh_token
}
response = requests.get(url, params=params)
result = response.json()
# 返回新的 access_token 和 refresh_token
return {
"access_token": result["access_token"],
"refresh_token": result["refresh_token"], # 新的 refresh_token,需要保存
"openid": result["openid"]
}
注意:每次刷新会返回新的 refresh_token,必须保存更新,否则旧的会失效!
第三步:登录群报数获取 Authorization
拿到 access_token 后,调用群报数的登录接口:
def login_qun100(openid, unionid, access_token):
url = "https://form.qun100.com/v1/app/login"
payload = {
"appId": "wxe7be3420358ab61e",
"openId": openid,
"sessionKey": access_token, # 这里用 access_token 作为 sessionKey
"unionId": unionid
}
headers = {
'Content-Type': 'application/json; charset=utf-8',
'User-Agent': 'okhttp/4.11.0'
}
response = requests.post(url, json=payload, headers=headers)
result = response.json()
# 返回 Authorization Token
return result["data"]["token"]
第四步:调用打卡接口
有了 Authorization,就可以调用打卡接口了:
def do_checkin(authorization, name, location):
url = "https://form.qun100.com/v1/1767814617359114241/form_data"
headers = {
'Authorization': authorization,
'Content-Type': 'application/json',
'Client-App-Id': 'wx6b67694378f555b6',
'Client-Form-Id': '1767814617359114241',
'User-Agent': 'Mozilla/5.0 ... MicroMessenger/8.0.65 ...',
'Referer': 'https://servicewechat.com/wx6b67694378f555b6/173/page-frame.html'
}
payload = {
"catalogs": [
{
"cid": "1767814618705485825",
"type": "WORD",
"value": name # 姓名
},
{
"cid": "1767814618709680129",
"type": "LOCATION",
"value": {
"address": location["address"],
"title": location["title"],
"location": {
"coordinates": location["coordinates"],
"type": "Point"
}
}
}
],
"formVersion": 5
}
response = requests.post(url, json=payload, headers=headers)
return response.json()
完整的 Token 刷新链路
def get_valid_authorization(account):
"""获取有效的 Authorization Token"""
# 1. 用 refresh_token 刷新微信 token
wx_result = refresh_wechat_token(account["refresh_token"])
if not wx_result:
return None
# 2. 保存新的 refresh_token(重要!)
account["refresh_token"] = wx_result["refresh_token"]
save_config()
# 3. 登录群报数获取 Authorization
authorization = login_qun100(
account["openid"],
account["unionid"],
wx_result["access_token"]
)
return authorization
定时任务设计
为了模拟真人操作,打卡时间在指定时间段内随机:
def schedule_next_checkin(account):
start_hour = 18 # 开始时间
end_hour = 19 # 结束时间
# 随机分钟和秒
random_minute = random.randint(0, 59)
random_second = random.randint(0, 59)
# 如果今天已打卡,安排明天
if is_today_checked_in(account_id):
next_time = tomorrow.replace(hour=start_hour, minute=random_minute)
else:
next_time = today.replace(hour=start_hour, minute=random_minute)
return next_time
配置文件结构
{
"wechat_app": {
"appid": "wxe7be3420358ab61e"
},
"accounts": [
{
"id": "用户ID",
"name": "真实姓名",
"wechat": {
"openid": "oxkDB6xxxxx",
"unionid": "oBFnS5xxxxx",
"refresh_token": "98_xxxxx"
},
"location": {
"address": "完整地址",
"title": "地点名称",
"coordinates": [经度, 纬度]
},
"schedule": {
"start_hour": 18,
"end_hour": 19
},
"email_receivers": ["xxx@qq.com"]
}
]
}
关键技术点总结
| 技术点 | 说明 |
|---|---|
| refresh_token 续期 | 每次使用后保存新的 token,保持长期有效 |
| 请求头伪装 | User-Agent、Referer 等必须模拟微信环境 |
| 随机时间 | 避免固定时间打卡被检测 |
| 错误处理 | token 过期时邮件通知,需要重新抓包 |
技术栈
- 后端:Python + Flask
- 数据库:SQLite
- 前端:TailwindCSS + 腾讯地图
- 通知:SMTP 邮件
本文仅供技术学习交流,请勿用于违规用途。