This Python script automates daily sign-in on a Sichuan community forum. It logs in via environment variables (username/password or cookie), detects sign-in links, and submits forms. The script supports fallback paths for common Discuz plugins and outputs sign-in results.
阿里云:
服务器购买地址
https://t.aliyun.com/U/kcPAeY若失效,可用地址
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 -*-import osimport reimport sysimport timeimport jsonimport requestsfrom bs4 import BeautifulSoupfrom urllib.parse import urljoin, urlparse, parse_qsBASE_URL = "https://bbs.scol.com.cn/"TIMEOUT = 15class ScolAutoSign:def __init__(self):self.username = os.getenv("SCOL_USERNAME", "").strip()self.password = os.getenv("SCOL_PASSWORD", "").strip()self.cookie_str = os.getenv("SCOL_COOKIE", "").strip()self.session = requests.Session()self.session.headers.update({"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/124.0.0.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",})# 如果用户提供 cookie,则直接注入if self.cookie_str:self._inject_cookie(self.cookie_str)# ------------------ 基础工具 ------------------def _inject_cookie(self, cookie_str: str):"""把 'a=1; b=2' 写入 requests.Session cookies"""cookies = {}for part in cookie_str.split(";"):part = part.strip()if not part or "=" not in part:continuek, v = part.split("=", 1)cookies[k.strip()] = v.strip()self.session.cookies.update(cookies)def _get(self, url: str, **kwargs) -> requests.Response:return self.session.get(url, timeout=TIMEOUT, **kwargs)def _post(self, url: str, data=None, **kwargs) -> requests.Response:return self.session.post(url, data=data, timeout=TIMEOUT, **kwargs)def _soup(self, html: str) -> BeautifulSoup:return BeautifulSoup(html, "lxml")def _extract_formhash(self, html: str) -> str:"""Discuz 常见 formhash 提取方式:- <input type="hidden" name="formhash" value="xxx">- 或 JS 变量里出现 formhash=xxx"""m = re.search(r'name="formhash"\s+value="([^"]+)"', html)if m:return m.group(1)m = re.search(r"formhash=([0-9a-zA-Z]+)", html)if m:return m.group(1)return ""def _is_logged_in(self, html: str) -> bool:"""粗略判断是否已登录:- 页面里出现 "退出/注销/登录后可见/欢迎您"- 或 Discuz 常见 "退出"链接"""keywords = ["退出", "注销", "欢迎您", "设置", "消息", "积分"]hit = sum(1 for k in keywords if k in html)if hit >= 2:return True# 有些 Discuz 会有 logout 链接if re.search(r"action=logout", html):return Truereturn False# ------------------ 登录相关 ------------------def fetch_home(self) -> str:"""拉取首页 HTML"""resp = self._get(BASE_URL)resp.encoding = resp.apparent_encoding or "utf-8"return resp.textdef login_if_needed(self) -> bool:"""若 cookie 已登录则跳过;若没登录则用用户名密码进行 Discuz 登录尝试。"""home_html = self.fetch_home()if self._is_logged_in(home_html):print("✅ 已处于登录态(Cookie有效或已登录)")return Trueif not (self.username and self.password):print("❌ 未登录,且未提供 SCOL_USERNAME / SCOL_PASSWORD(也没有可用 Cookie)")return False# Discuz 登录入口通常是 member.php?mod=logging&action=loginlogin_page = urljoin(BASE_URL, "member.php?mod=logging&action=login")resp = self._get(login_page)resp.encoding = resp.apparent_encoding or "utf-8"html = resp.textformhash = self._extract_formhash(html)if not formhash:print("❌ 未能从登录页提取 formhash,可能站点做了特殊处理/风控")return False# Discuz 登录提交一般是:# member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yeslogin_submit = urljoin(BASE_URL,"member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes")data = {"formhash": formhash,"referer": BASE_URL,"username": self.username,"password": self.password,"questionid": "0","answer": "",}# 有的站点要求带 Cookie/Refererheaders = {"Referer": login_page}r2 = self._post(login_submit, data=data, headers=headers)r2.encoding = r2.apparent_encoding or "utf-8"text2 = r2.text# 登录后再拉首页验证home_html2 = self.fetch_home()if self._is_logged_in(home_html2):print("✅ 登录成功")return True# Discuz 可能返回 XML/JS,尝试从返回里找错误提示err = self._extract_login_error(text2) or "登录失败(可能需要验证码/二次验证/账号密码不正确)"print(f"❌ 登录失败:{err}")return Falsedef _extract_login_error(self, html: str) -> str:"""从登录返回中粗略提取错误信息"""# Discuz 常见错误片段:<p>抱歉,您尚未登录,没有权限访问该页面</p>soup = self._soup(html)text = soup.get_text(" ", strip=True)# 取一段较短的if "密码" in text or "错误" in text or "验证码" in text or "登录" in text:return text[:120]return ""# ------------------ 签到相关 ------------------def discover_signin_url(self) -> str:"""从页面自动发现"签到"入口:- 首页/任务页里 a 标签文本包含:签到/打卡/每日签到- 或 href 包含:sign / misign / paulsign / checkin"""html = self.fetch_home()soup = self._soup(html)candidates = []for a in soup.find_all("a", href=True):text = (a.get_text() or "").strip()href = a["href"].strip()if any(k in text for k in ["签到", "打卡", "每日签到"]):candidates.append(href)continueif re.search(r"(misign|paulsign|checkin|sign)", href, re.I):candidates.append(href)# 去重并补全seen = set()cleaned = []for href in candidates:full = urljoin(BASE_URL, href)if full not in seen:seen.add(full)cleaned.append(full)if cleaned:# 取最像 Discuz 插件的那种for u in cleaned:if "plugin.php" in u or "misign" in u or "paulsign" in u:return ureturn cleaned[0]return ""def signin(self) -> tuple[bool, str]:"""执行签到:1) 尝试自动发现签到入口2) 如果发现失败,则 fallback 常见 Discuz 签到插件 URL3) 自动提取 formhash 并提交"""# 先尝试自动发现入口signin_entry = self.discover_signin_url()if signin_entry:ok, msg = self._try_signin_by_entry(signin_entry)if ok:return True, msg# fallback 常见插件路径(不同站点可能不同)fallbacks = [urljoin(BASE_URL, "plugin.php?id=k_misign:sign"),urljoin(BASE_URL, "k_misign-sign.html"),urljoin(BASE_URL, "plugin.php?id=dsu_paulsign:sign"),urljoin(BASE_URL, "plugin.php?id=dsu_paulsign:sign&operation=qiandao"),]last_msg = ""for u in fallbacks:ok, msg = self._try_signin_by_entry(u)last_msg = msgif ok:return True, msgreturn False, last_msg or "未找到可用的签到入口(可能站点无签到/需要验证码/路径不一样)"def _try_signin_by_entry(self, entry_url: str) -> tuple[bool, str]:"""对某个疑似签到入口尝试:- GET 入口页面拿 formhash- POST 提交(按 Discuz 常见方式)"""try:r = self._get(entry_url, headers={"Referer": BASE_URL})r.encoding = r.apparent_encoding or "utf-8"html = r.text# 若未登录会跳登录页if "mod=logging" in r.url or "登录" in html and "password" in html:return False, f"入口需要登录:{entry_url}"formhash = self._extract_formhash(html)# 有些签到插件直接 GET 一次就完成if ("签到成功" in html) or ("已签到" in html) or ("今日已" in html):return True, f"已完成签到(入口直接返回结果):{entry_url}"if not formhash:# 如果没有 formhash,也可能是接口式签到# 尝试直接 POST event/checkin 之类(尽力而为)ok, msg = self._try_api_style_sign(entry_url, html)return ok, msg# 尝试构造 POSTpost_url = entry_urlif "?" in post_url:post_url += "&"else:post_url += "?"post_url += "operation=qiandao"data = {"formhash": formhash,"qdxq": "kx", # 常见:签到心情(开心/难过等),不同插件字段不同"qdmode": "1", # 常见:签到模式"todaysay": "自动签到", # 常见:签到留言"fastreply": "0",}r2 = self._post(post_url, data=data, headers={"Referer": entry_url})r2.encoding = r2.apparent_encoding or "utf-8"t2 = r2.textif any(k in t2 for k in ["签到成功", "您今日已经签到", "已签到", "今日已签到"]):return True, f"签到成功:{self._short_msg(t2)}"if any(k in t2 for k in ["权限", "未登录", "登录", "验证码", "安全验证"]):return False, f"签到被拦截/需要验证:{self._short_msg(t2)}"# 再 GET 一次入口看状态r3 = self._get(entry_url, headers={"Referer": BASE_URL})r3.encoding = r3.apparent_encoding or "utf-8"t3 = r3.textif any(k in t3 for k in ["已签到", "今日已", "签到成功"]):return True, f"签到成功(复查确认):{entry_url}"return False, f"签到未成功(站点规则/字段不匹配):{entry_url}"except Exception as e:return False, f"签到尝试异常:{entry_url} -> {e}"def _try_api_style_sign(self, entry_url: str, html: str) -> tuple[bool, str]:"""尽力而为:某些站点签到是按钮触发接口(AJAX),页面里可能出现 endpoint。这里简单从 html 里抓取包含 sign/checkin 的接口地址尝试 GET/POST。"""api_candidates = set()# 提取类似 "plugin.php?id=xxx&..." 或 "/sign" 的片段for m in re.findall(r'["\']([^"\']*(?:sign|checkin|misign|paulsign)[^"\']*)["\']', html, flags=re.I):if len(m) < 6:continueif "http" in m:api_candidates.add(m)else:api_candidates.add(urljoin(BASE_URL, m))# 加入 entry_url 本身作为候选api_candidates.add(entry_url)for u in list(api_candidates)[:10]:try:# 先 GETr = self._get(u, headers={"Referer": entry_url})r.encoding = r.apparent_encoding or "utf-8"t = r.textif any(k in t for k in ["签到成功", "已签到", "今日已"]):return True, f"签到成功(API/GET):{u}"# 再 POST 一个空 body 试试r2 = self._post(u, data={}, headers={"Referer": entry_url})r2.encoding = r2.apparent_encoding or "utf-8"t2 = r2.textif any(k in t2 for k in ["签到成功", "已签到", "今日已"]):return True, f"签到成功(API/POST):{u}"except Exception:continuereturn False, "未能通过入口页面推断出可用的签到接口"def _short_msg(self, text: str) -> str:"""把返回内容压缩成短信息便于日志展示"""s = re.sub(r"\s+", " ", text)return s[:120]# ------------------ 主流程 ------------------def run(self):print("=== bbs.scol.com.cn 自动签到脚本启动 ===")ok_login = self.login_if_needed()if not ok_login:print("❌ 登录失败,终止")sys.exit(1)ok, msg = self.signin()if ok:print(f"✅ 签到完成:{msg}")sys.exit(0)else:print(f"❌ 签到失败:{msg}")sys.exit(2)if __name__ == "__main__":ScolAutoSign().run()
解析
该脚本为四川天府社区自动签到脚本,主要功能包括:
自动读取环境变量中的账号信息(或 Cookie),登录四川天府社区(Discuz 论坛常见结构)。
自动在页面中发现"签到/打卡"入口并尝试签到;若找不到则按常见 Discuz 签到插件路径进行兜底尝试。
输出签到结果:成功/已签到/需要验证/失败原因等。
主要方法
login_if_needed→ 判断是否已登录;未登录则用账号密码走 Discuz 登录流程并验证登录态。fetch_home→ 拉取论坛首页 HTML,用于判断登录态与发现签到入口。_extract_formhash→ 从页面里提取 Discuz 常用的formhash,用于表单提交(登录/签到常需要)。discover_signin_url→ 在首页 HTML 中扫描包含"签到/打卡/每日签到"或包含misign/paulsign/sign关键词的链接,自动找到签到入口。signin→ 签到主入口:先用自动发现入口签到;失败则使用常见插件路径进行 fallback 尝试。_try_signin_by_entry→ 针对某个"可能是签到入口"的 URL:GET 拿页面 + 提取 formhash + POST 提交,最后复查是否已签到。_try_api_style_sign→ 当入口页面没有 formhash 时,尝试从 HTML 推断可能的 AJAX 签到接口并用 GET/POST 探测。_inject_cookie→ 将SCOL_COOKIE注入到 session,直接复用登录态(一般更稳定)。_is_logged_in→ 根据页面关键字/退出链接等粗略判断当前是否已登录。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论