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=2019052.部署教程
3.代码如下
import randomimport requestsimport reimport timeimport osfrom bs4 import BeautifulSoupimport jsonimport schedulefrom datetime import datetimeimport 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:breakCookies.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"] = ClassIDjson_data["lat"] = Xjson_data["lng"] = Yjson_data["acc"] = ACCjson_data["cookie"] = Cookiesjson_data["scheduletime"] = scheduletimejson_data["pushplus"] = pushtokenjson_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:# 创建 loggerlogger = logging.getLogger()logger.setLevel(logging.INFO)# 创建文件处理器并设置编码为 UTF-8file_handler = logging.FileHandler('AutoCheckBJMF.log', encoding='utf-8')formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')file_handler.setFormatter(formatter)# 将处理器添加到 loggerlogger.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_offsetnew_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_numdef 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 // 3600remaining_seconds = remaining_seconds_main % 3600# 计算剩余分钟数remaining_minutes = remaining_seconds // 60remaining_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))# 使用正则表达式提取目标字符串 - Cookiepattern = 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 对象解析 HTMLsoup = 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 + "/" + matchnewX = 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.textprint(h1_text)printLog("info", "用户UID[%d%s] | %s"%(uid+1, username_string, h1_text))# encoding:utf-8if 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 += 1print("未找到匹配的字符串,检查Cookie是否错误!")return errorCookie, nullCookiedef 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 成功通知(仅对"签到成功"触发)。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论