1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/55RK8C若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/55RK8C若失效,可用地址
https://www.aliyun.com/daily-act/ecs/activity_selection?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.代码如下
# -*- coding: utf-8 -*-"""52ch.net 自动签到脚本(Discuz + dsu_paulsign)- 自动登录- 自动获取 formhash- 自动执行签到使用方式(Linux/Mac):export CH52_USERNAME="你的用户名"export CH52_PASSWORD="你的密码"python3 sign_52ch.pyWindows PowerShell:setx CH52_USERNAME "你的用户名"setx CH52_PASSWORD "你的密码"python sign_52ch.py"""import osimport reimport sysimport timeimport randomfrom dataclasses import dataclassimport requestsBASE = "https://www.52ch.net"LOGIN_PAGE = f"{BASE}/member.php?mod=logging&action=login&mobile=no"# Discuz 常见登录提交地址(会带 loginhash)LOGIN_SUBMIT_TEMPLATE = f"{BASE}/member.php?mod=logging&action=login&loginsubmit=yes&inajax=1&loginhash={{loginhash}}"# 签到页(你在网页里能打开的那个)SIGN_PAGE = f"{BASE}/dsu_paulsign-sign.html"# dsu_paulsign 常见签到提交接口(Discuz 默认插件风格)SIGN_SUBMIT = f"{BASE}/plugin.php?id=dsu_paulsign:sign&operation=qiandao&inajax=1"class SignResult:ok: boolmsg: strraw: str = ""class Discuz52chSigner:def __init__(self, username: str, password: str, timeout: int = 20):self.username = usernameself.password = passwordself.timeout = timeoutself.s = requests.Session()self.s.headers.update({"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/122.0 Safari/537.36"),"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language": "zh-CN,zh;q=0.9","Connection": "keep-alive",})# -------------------- 工具方法 --------------------def _get(self, url: str) -> str:r = self.s.get(url, timeout=self.timeout, allow_redirects=True)r.raise_for_status()return r.textdef _post(self, url: str, data: dict, headers: dict | None = None) -> str:h = {}if headers:h.update(headers)r = self.s.post(url, data=data, headers=h, timeout=self.timeout, allow_redirects=True)r.raise_for_status()return r.text@staticmethoddef _extract_formhash(html: str) -> str | None:# Discuz 常见:name="formhash" value="xxxx"m = re.search(r'name="formhash"\s+value="([^"]+)"', html)return m.group(1) if m else None@staticmethoddef _extract_loginhash(html: str) -> str | None:# 登录 form action 里通常会带 loginhash=xxxxm = re.search(r'loginhash=([A-Za-z0-9]+)', html)return m.group(1) if m else None@staticmethoddef _is_logged_in(html: str) -> bool:# Discuz 登录后页面通常会出现 "退出/退出登录/我的/设置"等return ("退出" in html) or ("logout" in html) or ('id="um"' in html) or ('id="loginstatus"' in html)# -------------------- 核心流程 --------------------def login(self) -> SignResult:html = self._get(LOGIN_PAGE)formhash = self._extract_formhash(html)loginhash = self._extract_loginhash(html)if not formhash or not loginhash:return SignResult(False, "登录页解析失败:未拿到 formhash/loginhash,可能页面结构变化或被风控。", html[:5000])submit_url = LOGIN_SUBMIT_TEMPLATE.format(loginhash=loginhash)payload = {"formhash": formhash,"referer": BASE + "/","loginfield": "username", # 常见字段:username / email / auto"username": self.username,"password": self.password,"questionid": "0","answer": "",}# Discuz 登录一般要求带这个头headers = {"Content-Type": "application/x-www-form-urlencoded","Origin": BASE,"Referer": LOGIN_PAGE,}resp_text = self._post(submit_url, payload, headers=headers)# 登录返回通常是 JS/HTML,包含 "succeed" 或者 "欢迎您回来"# 也可能返回错误:密码错误、需要验证码、账号异常等if ("succeed" in resp_text) or ("欢迎您回来" in resp_text) or ("登录成功" in resp_text):# 再访问首页确认 cookie 生效home = self._get(BASE + "/")if self._is_logged_in(home):return SignResult(True, "登录成功")return SignResult(False, "登录请求看似成功,但未检测到登录态(可能需要二次验证/风控)。", resp_text[:5000])# 常见失败提示if ("验证码" in resp_text) or ("安全验证" in resp_text) or ("请先完成验证" in resp_text):return SignResult(False, "登录失败:站点触发验证码/安全验证,requests 方案无法直接处理。", resp_text[:5000])if ("密码错误" in resp_text) or ("登录失败" in resp_text) or ("用户名" in resp_text):return SignResult(False, "登录失败:账号或密码错误/账号异常(请检查输入)。", resp_text[:5000])return SignResult(False, "登录失败:未知响应(建议把 raw 打印出来排查)。", resp_text[:5000])def get_sign_page_formhash(self) -> SignResult:html = self._get(SIGN_PAGE)# 未登录会提示"您需要先登录才能继续本操作"if ("需要先登录" in html) or ("登录才能继续" in html):return SignResult(False, "未登录:访问签到页被要求先登录。", html[:2000])formhash = self._extract_formhash(html)if not formhash:# dsu_paulsign 有时 formhash 在 JS 里m = re.search(r"formhash=([a-zA-Z0-9]+)", html)formhash = m.group(1) if m else Noneif not formhash:return SignResult(False, "签到页解析失败:未拿到 formhash(可能插件页面结构变化)。", html[:5000])# 已签到常见提示(不同站点 wording 不一样)if ("今日已签" in html) or ("已经签到" in html) or ("已签到" in html):return SignResult(True, f"检测到页面提示:可能已签到(formhash={formhash})", formhash)return SignResult(True, "获取签到页 formhash 成功", formhash)def do_sign(self, formhash: str, mood: str = "kx", say: str = "") -> SignResult:"""dsu_paulsign 常见参数:- formhash: 必填- qdxq: 心情(如 kx=开心,ym=郁闷 等,站点可自定义)- qdmode: 签到模式(一般 1)- todaysay: 签到内容- fastreply: 是否快速回复(一般 0)"""payload = {"formhash": formhash,"qdxq": mood,"qdmode": "1","todaysay": say,"fastreply": "0",}headers = {"Content-Type": "application/x-www-form-urlencoded","Origin": BASE,"Referer": SIGN_PAGE,"X-Requested-With": "XMLHttpRequest",}resp_text = self._post(SIGN_SUBMIT, payload, headers=headers)# dsu_paulsign 通常返回:恭喜你签到成功 / 今日已签到 / 权限不足 等if ("签到成功" in resp_text) or ("恭喜" in resp_text):return SignResult(True, "签到成功", resp_text)if ("已经签到" in resp_text) or ("今日已签到" in resp_text):return SignResult(True, "今日已签到", resp_text)if ("权限" in resp_text) or ("禁止" in resp_text):return SignResult(False, "签到失败:可能没有权限/需要满足等级/绑定手机等。", resp_text)if ("验证码" in resp_text) or ("安全验证" in resp_text):return SignResult(False, "签到失败:触发验证码/安全验证。", resp_text)return SignResult(False, "签到失败:未知响应(可查看 raw)。", resp_text[:5000])def run(self) -> int:# 1) 登录r = self.login()print(f"[LOGIN] {r.msg}")if not r.ok:return 1# 2) 访问签到页拿 formhashr2 = self.get_sign_page_formhash()print(f"[SIGN_PAGE] {r2.msg}")if not r2.ok:return 2# 3) 执行签到formhash = r2.raw if r2.raw else ""mood_pool = ["kx", "ng", "ym", "wl", "fd"] # 心情参数站点可能不同,不行就固定用 kxmood = random.choice(mood_pool)say = "每日签到打卡~"time.sleep(random.uniform(1.0, 2.0))r3 = self.do_sign(formhash=formhash, mood=mood, say=say)print(f"[SIGN] {r3.msg}")# 你需要的话可以把 r3.raw 输出到文件排查# print(r3.raw)return 0 if r3.ok else 3def main():username = os.getenv("CH52_USERNAME", "").strip()password = os.getenv("CH52_PASSWORD", "").strip()if not username or not password:print("❌ 缺少环境变量:CH52_USERNAME / CH52_PASSWORD")sys.exit(1)signer = Discuz52chSigner(username=username, password=password)code = signer.run()sys.exit(code)if __name__ == "__main__":main()
该脚本为成都吃喝论坛自动签到脚本,主要作用包括:
建立会话(Session):用
requests.Session()维持 Cookie,让"登录 → 访问签到页 → 提交签到"处于同一登录态。登录 Discuz:从登录页解析出
formhash、loginhash,再 POST 到登录提交接口。进入签到插件页:访问
dsu_paulsign-sign.html,解析签到所需的formhash。提交签到请求:向
plugin.php?id=dsu_paulsign:sign&operation=qiandao&inajax=1发 POST,完成签到。输出结果:成功/已签到/失败原因(验证码、权限、账号密码问题等)。
主要方法
login()
作用:完成论坛登录。
关键点:
先 GET 登录页,解析
formhash(防 CSRF)和loginhash(Discuz 登录提交 URL 的动态参数)。再 POST 登录表单,拿到登录 Cookie。
最后访问首页二次校验是否真的登录成功。
get_sign_page_formhash()
作用:访问签到页并拿到签到用的
formhash。为什么要单独拿:
dsu_paulsign签到提交也需要formhash,而且可能和登录页不一样,最稳妥是签到页再取一次。附带逻辑:如果页面出现"需要先登录",说明登录态丢失或登录没成功。
do_sign(formhash, mood, say)
作用:真正执行签到动作。
参数含义(常见 dsu_paulsign):
formhash:CSRF token(必填)qdxq:签到心情(站点可能自定义,不行就固定kx)qdmode:签到模式(多数站点为 1)todaysay:签到留言fastreply:快速回复开关(一般 0)返回处理:识别"签到成功 / 今日已签到 / 权限不足 / 验证码"等关键词。
run()
作用:把整个流程串起来,按顺序执行:登录 → 取签到 formhash → 签到提交。
附加:加入随机等待、随机心情,减少"像机器人"。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论