1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/DT4XYh若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/DT4XYh
若失效,可用地址
https://www.aliyun.com/activity/wuying/dj?source=5176.29345612&userCode=49hts92d
腾讯云:
https://curl.qcloud.com/wJpWmSfU
若失效,可用地址
https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console
华为云
https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=201905
2.部署教程
3.代码如下
import random
import requests
import re
import time
import os
from bs4 import BeautifulSoup
import json
import schedule
from datetime import datetime
import logging
# 获取当前目录
current_directory = os.getcwd()
file_name = "config.json"
file_path = os.path.join(current_directory, file_name)
# 检查文件是否存在
if not os.path.exists(file_path):
# 定义默认的 JSON 数据
default_config = {
"class": "", # 班级ID
"lat": "", # 纬度
"lng": "", # 经度
"acc": "", # 海拔
"time": 0, # 等待时间(已弃用)
"cookie": "", # 用户令牌
"scheduletime": "", # 定时任务
"pushplus": "", # pushpush推送令牌
"debug": False, # 调试模式
"configLock": False #配置编辑状态,
}
# 文件不存在,创建并写入默认数据
with open(file_path, "w") as file:
json.dump(default_config, file, indent=4)
print("----------初始化----------")
print(f"文件 {file_name} 不存在,已创建并填充默认数据。")
# 读取外部 JSON 文件中的数据
with open(file_path, 'r') as file:
json_data = json.load(file)
debug = json_data["debug"]
# 判断是否首次使用或解除配置锁定
if not json_data['configLock']:
ClassID = input("请输入班级ID:")
X = input("请输入纬度(X):")
Y = input("请输入经度(Y):")
ACC = input("请输入海拔:")
Cookies = []
print("请输入你的Cookie,输入空行结束,支持用户备注格式如下")
print("username=<备注>;remember....<魔方Cookie>")
while True:
cookie = input("Cookie: ")
if not cookie:
break
Cookies.append(cookie)
print("----------配置定时任务(可选)----------")
print("格式为00:00,例如1:30要填写为01:30!不设置定时请留空")
print("Tip:请注意以上格式并使用英文符号":"不要使用中文符号":"")
scheduletime = input("请输入签到时间:")
if scheduletime=="":
print("您未填写签到时间,未启用定时签到,启动即开始签到")
print("----------远程推送----------")
pushtoken = input("(未适配新版多人签到,如果是多人签到建议不使用)\n请输入pushplus推送密钥,不需要请留空:")
print("配置完成,您的信息将写入json文件,下次使用将直接从json文件导入")
# 2. 修改数据
json_data["class"] = ClassID
json_data["lat"] = X
json_data["lng"] = Y
json_data["acc"] = ACC
json_data["cookie"] = Cookies
json_data["scheduletime"] = scheduletime
json_data["pushplus"] = pushtoken
json_data["configLock"] = True
# 3. 写回JSON文件
with open(file_path, "w") as file:
json.dump(json_data, file, indent=4) # indent 设置缩进为4个空格
print("数据已保存到"+current_directory+"下的data.json中。")
else:
print("----------欢迎回来----------")
ClassID = json_data["class"]
X = json_data["lat"]
Y = json_data["lng"]
ACC = json_data["acc"]
Cookies = json_data["cookie"]
scheduletime = json_data["scheduletime"]
pushtoken = json_data["pushplus"]
print("配置已读取")
if scheduletime=="":
print("当前签到模式为:手动,即将开始签到")
else:
print("当前签到模式为:自动,启动定时任务")
print("----------信息----------")
print("班级ID:" + ClassID)
print("纬度:" + X)
print("经度:" + Y)
print("海拔:" + ACC)
# print("检索间隔:" + str(SearchTime))
print("Cookie数量:" + str(len(Cookies)))
print("定时:" + scheduletime)
print("通知token:" + pushtoken)
if debug:print("Debug:" + str(debug))
print("---------------------")
def printLog(type, message):
if debug:
if type == "info":
logger.info(message)
elif type == "warning":
logger.warning(message)
elif type == "error":
logger.error(message)
elif type == "critical":
logger.critical(message)
else:
logger.info(message)
if debug:
# 创建 logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# 创建文件处理器并设置编码为 UTF-8
file_handler = logging.FileHandler('AutoCheckBJMF.log', encoding='utf-8')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# 将处理器添加到 logger
logger.addHandler(file_handler)
printLog("info", "已启动Debug")
print("一切就绪,程序开始执行\\^o^/")
# 随机经纬,用于多人签到定位偏移
def modify_decimal_part(num):
num = float(num)
# print(num)
# 将浮点数转换为字符串
num_str = f"{num:.8f}" # 确保有足够的小数位数
# 找到小数点的位置
decimal_index = num_str.find('.')
# 提取小数点后4到6位
decimal_part = num_str[decimal_index + 4:decimal_index + 9]
# 将提取的小数部分转换为整数
decimal_value = int(decimal_part)
# 生成一个在-150到150范围的随机整数
random_offset = random.randint(-15000, 15000)
# 计算新的小数部分
new_decimal_value = decimal_value + random_offset
new_decimal_value = abs(new_decimal_value)
# 将新的小数部分转换为字符串,并确保它有3位
new_decimal_str = f"{new_decimal_value:05d}"
# 拼接回原浮点数
new_num_str = num_str[:decimal_index + 4] + new_decimal_str + num_str[decimal_index + 9:]
# 将新的字符串转换回浮点数
new_num = float(new_num_str)
return new_num
def thisTime(hour,minute):
# 指定的小时和分钟,这里示例为21:50,你可以按需修改
target_hour,target_minute = hour,minute
# while True:
# 获取当前时间的时间戳
current_time_stamp = time.time()
# 获取当前时间的结构体
current_time_struct = time.localtime(current_time_stamp)
# 获取当天的日期部分,构造一个新的时间结构体,用于设置指定时间
today_date = time.strftime("%Y-%m-%d", current_time_struct)
target_time_struct = time.strptime(today_date + " " + str(target_hour) + ":" + str(target_minute) + ":00", "%Y-%m-%d %H:%M:%S")
target_time_stamp = time.mktime(target_time_struct)
if target_time_stamp < current_time_stamp:
# 如果目标时间已经小于当前时间,说明今天的时间已经过了,那就设置为明天的同样时间
target_time_stamp += 24 * 3600
# 计算时间差(单位为秒)
remaining_seconds_main = int(target_time_stamp - current_time_stamp)
# 计算剩余小时数
remaining_hours = remaining_seconds_main // 3600
remaining_seconds = remaining_seconds_main % 3600
# 计算剩余分钟数
remaining_minutes = remaining_seconds // 60
remaining_seconds %= 60
# 格式化当前时间结构体为字符串
current_time = time.strftime("%Y-%m-%d %H:%M", current_time_struct)
# 区分剩余时间的显示逻辑,以优化终端内容的显示阅读体验
if remaining_seconds_main < 300:
# 如果剩余时间小于5分钟则每秒刷新
print("\r当前时间:{},距离下次任务执行{}:{} 还剩{}分钟{}秒\t\t".format(
current_time, target_hour, target_minute, remaining_minutes, remaining_seconds), end="")
time.sleep(1)
else:
# 如果剩余时间大于5分钟则每分钟刷新
print("\r当前时间:{},距离下次任务执行{}:{} 还剩{}小时{}分钟\t\t".format(
current_time, target_hour, target_minute, remaining_hours, remaining_minutes), end="")
time.sleep(60)
def qiandao(theCookies):
# title = '班级魔法自动签到任务' # 改成你要的标题内容
url = 'http://k8n.cn/student/course/' + ClassID + '/punchs'
errorCookie = []
nullCookie = 0
# 多用户检测签到
for uid in range(0,len(theCookies)):
onlyCookie = theCookies[uid]
# 使用正则表达式提取目标字符串 - 用户备注
pattern = r'username=[^;]+'
result = re.search(pattern, onlyCookie)
if result:
username_string = " <%s>"%result.group(0).split("=")[1]
else:
username_string = ""
# 用户信息显示与5秒冷却
print("☆☆☆☆☆ 用户UID:%d%s 即将签到 ☆☆☆☆☆"%(uid+1,username_string),end="")
time.sleep(1) #暂停5秒后进行签到
print("\r★☆☆☆☆ 用户UID:%d%s 即将签到 ☆☆☆☆★"%(uid+1,username_string),end="")
time.sleep(1)
print("\r★★☆☆☆ 用户UID:%d%s 即将签到 ☆☆☆★★"%(uid+1,username_string),end="")
time.sleep(1)
print("\r★★★☆☆ 用户UID:%d%s 即将签到 ☆☆★★★"%(uid+1,username_string),end="")
time.sleep(1)
print("\r★★★★☆ 用户UID:%d%s 即将签到 ☆★★★★"%(uid+1,username_string),end="")
time.sleep(1)
print("\r★★★★★ 用户UID:%d%s 开始签到 ★★★★★"%(uid+1,username_string))
# 使用正则表达式提取目标字符串 - Cookie
pattern = r'remember_student_59ba36addc2b2f9401580f014c7f58ea4e30989d=[^;]+'
result = re.search(pattern, onlyCookie)
if result:
extracted_string = result.group(0)
if debug:
print(extracted_string)
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 9; AKT-AK47 Build/USER-AK47; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 XWEB/1160065 MMWEBSDK/20231202 MMWEBID/1136 MicroMessenger/8.0.47.2560(0x28002F35) WeChat/arm64 Weixin NetType/4G Language/zh_CN ABI/arm64',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/wxpic,image/tpg,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'X-Requested-With': 'com.tencent.mm',
'Referer': 'http://k8n.cn/student/course/' + ClassID,
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh-SG;q=0.9,zh;q=0.8,en-SG;q=0.7,en-US;q=0.6,en;q=0.5',
'Cookie': extracted_string
}
response = requests.get(url, headers=headers)
print("响应:", response)
# 创建 Beautiful Soup 对象解析 HTML
soup = BeautifulSoup(response.text, 'html.parser')
title_tag = soup.find('title')
if debug:
print("★☆★")
print(soup)
print("===")
print(title_tag)
print("★☆★")
if title_tag and "出错" not in title_tag.text:
# 使用正则表达式从 HTML 文本中提取所有 punch_gps() 中的数字
pattern = re.compile(r'punch_gps\((\d+)\)')
matches = pattern.findall(response.text)
print("找到GPS定位签到:", matches)
pattern2 = re.compile(r'punchcard_(\d+)')
matches2 = pattern2.findall(response.text)
print("找到扫码签到:", matches2)
matches.extend(matches2)
if matches:
for match in matches:
url1 = "http://k8n.cn/student/punchs/course/" + ClassID + "/" + match
newX = modify_decimal_part(X)
newY = modify_decimal_part(Y)
payload = {
'id': match,
'lat': newX,
'lng': newY,
'acc': ACC, #未知,可能是高度
'res': '', #拍照签到
'gps_addr': '' #未知,抓取时该函数为空
}
response = requests.post(url1, headers=headers, data=payload)
print("签到请求已发送: 签到ID[%s] 签到定位[%s,%s] 签到海拔[%s]"%(match, newX, newY, ACC))
printLog("info", "用户UID[%d%s] | 签到请求已发送: 签到ID[%s] 签到定位[%s,%s] 签到海拔[%s]"%(uid+1, username_string, match, newX, newY, ACC))
if response.status_code == 200:
print("请求成功,响应:", response)
# 解析响应的 HTML 内容
soup_response = BeautifulSoup(response.text, 'html.parser')
# h1_tag = soup_response.find('h1')
div_tag = soup_response.find('div', id='title')
if debug:
print("★☆★")
print(soup_response)
print("===")
print(div_tag)
print("★☆★")
if div_tag:
h1_text = div_tag.text
print(h1_text)
printLog("info", "用户UID[%d%s] | %s"%(uid+1, username_string, h1_text))
# encoding:utf-8
if pushtoken != "" and h1_text== "签到成功":
url = 'http://www.pushplus.plus/send?token=' + pushtoken + '&title=' + "班级魔法自动签到任务" + '&content=' + h1_text # 不使用请注释
requests.get(url) # 不使用请注释
continue # 返回到查找进行中的签到循环
else:
print("未找到 <h1> 标签,可能存在错误")
printLog("info", "用户UID[%d%s] | 未找到 <h1> 标签,可能存在错误或签到成功"%(uid+1, username_string))
else:
print("请求失败,状态码:", response.status_code)
printLog("error", "用户UID[%d%s] | 请求失败,状态码: %d"%(uid+1, username_string, response.status_code))
print("将本Cookie加入重试队列")
errorCookie.append(onlyCookie)
else:
print("未找到在进行的签到")
else:
print("登录状态异常,将本Cookie加入重试队列")
printLog("error", "用户UID[%d%s] | 登录状态异常"%(uid+1, username_string))
errorCookie.append(onlyCookie)
else:
nullCookie += 1
print("未找到匹配的字符串,检查Cookie是否错误!")
return errorCookie, nullCookie
def job():
current_time = datetime.now()
print("\n进入检索,当前时间为:", current_time)
errorCookie,nullCookie = qiandao(Cookies)
if len(errorCookie)>0:
print("检测到有Cookie签到失败,等待5分钟后启动一次重试队列")
time.sleep(300)
errorCookie = qiandao(errorCookie)
if len(errorCookie)>0:
print("检测到仍然有Cookie签到失败,等待15分钟后最后启动一次重试队列")
time.sleep(900)
errorCookie = qiandao(errorCookie)
if len(errorCookie)>0:
print("!!! 检测到仍然有Cookie签到失败,请检查Cookie是否过期或网络异常 !!!")
elif nullCookie>0:
print("!!! 本次签到存在异常,请检查Cookie是否均已正常配置 !!!")
else:
print("签到成功")
print("分割线")
if scheduletime:
print("本次签到结束,等待设定的时间%s到达☆\n"%scheduletime)
if (scheduletime != ""):
print("等待设定时间" + scheduletime + "到达☆")
# 设置定时任务,在每天的早上8点触发
schedule.every().day.at(scheduletime).do(job)
# 格式化时间
hour,minute = scheduletime.split(":")
while True:
schedule.run_pending()
thisTime(hour,minute) # 倒计时
else:
job()
input("手动签到已结束,敲击回车关闭窗口☆~")
解析
该脚本为班级魔方自动签到脚本,主要作用为:
首次运行交互式生成并锁定
config.json
(班级ID、经纬度、海拔、Cookies、定时等);读取配置后,单次或定时扫描当前班级课程的进行中签到(GPS/扫码);
为每个 Cookie(可多人)自动提交签到(带经纬度微偏移);
结果解析与失败重试(5 分钟与 15 分钟两轮);
可选 pushplus 成功通知;
支持 debug 日志(写入
AutoCheckBJMF.log
)。
主要方法的作用
配置初始化与读取
创建/读取
config.json
,首启交互填写并configLock
锁定;之后直接载入。支持多账户 Cookies;可设置定时
scheduletime
与 pushplus token。printLog(type, message)
在
debug=True
时,把不同级别日志写到AutoCheckBJMF.log
。modify_decimal_part(num)
对输入的经纬度数值随机扰动小数第4–8位(±15000 量级的五位微调),实现多人签到时的定位微偏移,避免定位完全一致。
thisTime(hour, minute)
定时模式下的倒计时输出:距下次执行小于 5 分钟按秒刷新,否则按分钟刷新。
qiandao(theCookies)
抓取
punch_gps(<id>)
(GPS签到)与punchcard_<id>
(扫码签到);生成随机偏移后的经纬度,POST 提交签到;
解析返回页
<div id="title">
展示结果文本(如"签到成功");如开启 pushplus 且成功则推送;
核心签到流程(对传入的 Cookie 列表逐个执行):
返回
(errorCookie, nullCookie)
给上层处理。从
remember_student_...
中提取有效 Cookie;显示备注username=...
(若有)。访问
http://k8n.cn/student/course/{ClassID}/punchs
获取签到页 HTML。解析 HTML(BeautifulSoup):
对每个签到 ID:
失败或登录异常的 Cookie 放入重试队列返回;统计 Cookie 缺失/错误。
job()
调用
qiandao(Cookies)
;对失败 Cookie 分两轮重试(5 分钟与 15 分钟后各一次);
输出总结:圆满成功 / 存在异常 / 仍有失败提示。
定时模式下打印等待提示。
一次完整的扫描+签到任务:
定时与入口
若
scheduletime
非空:schedule.every().day.at(scheduletime).do(job)
,循环
schedule.run_pending()
并用thisTime()
倒计时;否则直接执行一次
job()
,并等待回车退出。其他要点
多用户:
Cookies
为列表,逐个执行并显示"UID+备注"。容错:HTML 结构变化/登录异常会落入重试或提示。
网络:全部基于
requests
;页面解析用BeautifulSoup
。通知:可选 pushplus 成功通知(仅对"签到成功"触发)。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论