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 -*-import osimport reimport timeimport tracebackfrom dataclasses import dataclassfrom typing import Optional, List, Tupleimport requestsfrom lxml import etreefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECtry:from webdriver_manager.chrome import ChromeDriverManagerfrom selenium.webdriver.chrome.service import ServiceUSE_WDM = Trueexcept Exception:USE_WDM = FalseBASE_URL = "https://bbs.scol.com.cn/"LOGIN_URL_CANDIDATES = ["member.php?mod=logging&action=login","member.php?mod=logging&action=login&mobile=2","member.php?mod=logging&action=login&infloat=yes&handlekey=login",]# 常见签到入口候选(Discuz常见)SIGN_URL_CANDIDATES = ["plugin.php?id=dsu_paulsign:sign","plugin.php?id=dsu_paulsign:sign&operation=qiandao","plugin.php?id=k_misign:sign","plugin.php?id=qiandao","home.php?mod=task","home.php?mod=space","forum.php",]# 页面关键词(用于扫描链接文本)KEYWORDS = ["签到", "每日签到", "打卡", "签 到", "Check in", "checkin", "sign", "任务"]class SignResult:ok: booltitle: strdetail: strraw_url: str = ""def env(name: str, default: str = "") -> str:v = os.getenv(name)return v.strip() if v is not None else defaultdef pushplus_send(token: str, title: str, content: str) -> None:if not token:returntry:requests.get("https://www.pushplus.plus/send",params={"token": token, "title": title, "content": content, "template": "txt"},timeout=10,)except Exception:passclass ScolBbsSigner:def __init__(self, username: str, password: str, headless: bool = True, timeout: int = 20):self.username = usernameself.password = passwordself.headless = headlessself.timeout = timeoutself.driver = self._build_driver()self.wait = WebDriverWait(self.driver, self.timeout)# ------------------ 核心:浏览器初始化 ------------------def _build_driver(self) -> webdriver.Chrome:chrome_options = Options()if self.headless:chrome_options.add_argument("--headless=new")chrome_options.add_argument("--no-sandbox")chrome_options.add_argument("--disable-dev-shm-usage")chrome_options.add_argument("--disable-gpu")chrome_options.add_argument("--window-size=1280,900")chrome_options.add_argument("--lang=zh-CN")chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")if USE_WDM:service = Service(ChromeDriverManager().install())return webdriver.Chrome(service=service, options=chrome_options)return webdriver.Chrome(options=chrome_options)# ------------------ 工具:安全打开页面 ------------------def open(self, url: str) -> None:if not url.startswith("http"):url = BASE_URL.rstrip("/") + "/" + url.lstrip("/")self.driver.get(url)# ------------------ 工具:是否已登录 ------------------def is_logged_in(self) -> bool:html = self.driver.page_source# Discuz 常见:退出/注销if re.search(r"(退出|退出登录|注销|Log\s*out)", html, re.I):return True# 常见:设置/消息/提醒/个人资料if re.search(r"(设置|消息|提醒|个人资料|空间)", html):return Truereturn False# ------------------ 工具:找输入框 ------------------def _find_any_input(self, locators):for by, value in locators:try:ele = self.wait.until(EC.presence_of_element_located((by, value)))if ele:return eleexcept Exception:continuereturn None# ------------------ 工具:找可点击元素 ------------------def _find_any_clickable(self, locators):for by, value in locators:try:ele = self.wait.until(EC.element_to_be_clickable((by, value)))if ele:return eleexcept Exception:continuereturn None# ------------------ 主要方法:登录 ------------------def login(self) -> bool:"""多入口尝试登录页 -> 填账号密码 -> 点击登录 -> 回首页验证是否登录成功"""self.open(BASE_URL)time.sleep(2)if self.is_logged_in():return Truelast_err = ""for path in LOGIN_URL_CANDIDATES:try:self.open(path)time.sleep(2)user_input = self._find_any_input([(By.NAME, "username"),(By.ID, "username"),(By.CSS_SELECTOR, "input[name='username']"),(By.XPATH, "//input[contains(@placeholder,'用户名') or contains(@placeholder,'账号') or contains(@placeholder,'邮箱') or contains(@placeholder,'手机')]"),])pwd_input = self._find_any_input([(By.NAME, "password"),(By.ID, "password"),(By.CSS_SELECTOR, "input[type='password']"),(By.XPATH, "//input[@type='password']"),])if not user_input or not pwd_input:continueuser_input.clear()user_input.send_keys(self.username)pwd_input.clear()pwd_input.send_keys(self.password)submit_btn = self._find_any_clickable([(By.CSS_SELECTOR, "button[type='submit']"),(By.CSS_SELECTOR, "input[type='submit']"),(By.XPATH, "//*[self::button or self::a or self::span][contains(.,'登录') or contains(.,'登 录')]"),])if submit_btn:submit_btn.click()else:pwd_input.submit()time.sleep(3)self.open(BASE_URL)time.sleep(2)if self.is_logged_in():return Trueexcept Exception as e:last_err = str(e)continueprint(f"❌ 登录失败: {last_err}")return False# ------------------ 主要方法:发现签到入口 ------------------def discover_sign_url(self) -> Optional[str]:"""1) 常见签到插件URL试探(dsu_paulsign/k_misign/task)2) 首页/任务中心/空间扫描"签到/打卡"等链接3) 源码提取疑似签到链接"""# 1) 插件URL试探for p in SIGN_URL_CANDIDATES:url = BASE_URL.rstrip("/") + "/" + pif self._url_looks_like_sign_page(url):return url# 2) 扫描页面for page in [BASE_URL, "home.php?mod=task", "home.php?mod=space", "forum.php"]:try:self.open(page)time.sleep(2)u = self._scan_page_for_sign_link()if u:return uexcept Exception:passreturn Nonedef _url_looks_like_sign_page(self, url: str) -> bool:try:self.open(url)time.sleep(2)html = self.driver.page_sourceif any(k in html for k in ["签到", "已签到", "签到成功", "今日已签到", "补签", "任务"]):return Trueexcept Exception:passreturn Falsedef _scan_page_for_sign_link(self) -> Optional[str]:html = self.driver.page_sourcedoc = etree.HTML(html)if doc is None:return None# 2.1 找链接文本包含关键词for kw in KEYWORDS:nodes = doc.xpath(f"//a[contains(normalize-space(.), '{kw}')]/@href")if nodes:return self._abs_url(nodes[0])# 2.2 找 href 含关键字hrefs = doc.xpath("//a/@href") or []for href in hrefs:if not href:continueif re.search(r"(dsu_paulsign|k_misign|qiandao|checkin|sign|task|mission)", href, re.I):return self._abs_url(href)return Nonedef _abs_url(self, href: str) -> str:href = href.strip()if href.startswith("http"):return hrefif href.startswith("//"):return "https:" + hrefreturn BASE_URL.rstrip("/") + "/" + href.lstrip("/")# ------------------ 主要方法:执行签到 ------------------def do_sign(self, sign_url: str) -> SignResult:"""打开签到页 -> 点击签到按钮 -> 解析结果"""self.open(sign_url)time.sleep(2)clicked = False# 优先点明确"签到/打卡/立即签到"的按钮/链接btn = self._find_any_clickable([(By.XPATH, "//*[self::a or self::button or self::span][contains(.,'签到') or contains(.,'打卡') or contains(.,'立即签到')]"),])if btn:try:btn.click()clicked = Truetime.sleep(3)except Exception:passhtml = self.driver.page_sourcetitle, detail, ok = self._parse_sign_result(html, clicked, sign_url)return SignResult(ok=ok, title=title, detail=detail, raw_url=sign_url)def _parse_sign_result(self, html_text: str, clicked: bool, url: str) -> Tuple[str, str, bool]:# 成功/已签到关键词if re.search(r"(签到成功|打卡成功)", html_text):award = self._extract_award_text(html_text)return "签到成功", award or "页面提示:签到成功", Trueif re.search(r"(今日已签到|已经签到|已签到)", html_text):award = self._extract_award_text(html_text)return "今日已签到", award or "页面提示:今日已签到/无需重复签到", True# 点了按钮但未识别到明确字样if clicked:return "可能已触发签到", "已点击签到按钮,但未识别到明确成功文案(可能是弹窗提示/JS渲染)。建议手动确认一次。", Truereturn "签到失败/未找到入口", f"未识别到签到成功/已签到文案,也未能稳定点击签到按钮。URL={url}", Falsedef _extract_award_text(self, html_text: str) -> str:# 提取积分/金币/威望等附近文本patterns = [r"(积分[^<\n]{0,40})",r"(金币[^<\n]{0,40})",r"(威望[^<\n]{0,40})",r"(经验[^<\n]{0,40})",r"(奖励[^<\n]{0,60})",]for p in patterns:m = re.search(p, html_text)if m:return m.group(1)return ""# ------------------ 主流程 ------------------def run(self) -> SignResult:try:if not self.login():return SignResult(False, "登录失败", "账号密码错误/站点结构变化/需要验证码等导致无法自动登录", BASE_URL)sign_url = self.discover_sign_url()if not sign_url:return SignResult(False, "未找到签到入口", "签到入口可能隐藏在用户中心/任务中心,或需要指定具体签到URL", BASE_URL)return self.do_sign(sign_url)except Exception as e:return SignResult(False, "程序异常", f"{e}\n\n{traceback.format_exc()}", BASE_URL)finally:try:self.driver.quit()except Exception:passdef main():username = env("SCOL_USERNAME")password = env("SCOL_PASSWORD")headless = env("SCOL_HEADLESS", "true").lower() not in ["0", "false", "off"]timeout = int(env("SCOL_TIMEOUT", "20"))push_token = env("PUSHPLUS_TOKEN", "")if not username or not password:print("❌ 缺少环境变量 SCOL_USERNAME / SCOL_PASSWORD")print("✅ 示例:")print(" export SCOL_USERNAME='你的账号'")print(" export SCOL_PASSWORD='你的密码'")returnsigner = ScolBbsSigner(username, password, headless=headless, timeout=timeout)result = signer.run()print("=" * 60)print(f"结果:{result.title}")print(f"详情:{result.detail}")print(f"入口:{result.raw_url}")print("=" * 60)# 推送if push_token:pushplus_send(push_token, f"四川天府论坛签到 - {result.title}", f"{result.detail}\n\n入口:{result.raw_url}")if __name__ == "__main__":main()
该脚本为天府论坛自动签到脚本,主要作用包括:
自动登录四川天府论坛(兼容常见 Discuz 登录页结构)
自动探测签到入口(优先常见插件 URL,失败再从页面"文本+链接"自动搜索)
自动执行签到(点击"签到/打卡/立即签到"按钮)
自动识别结果(成功/已签到/失败,并尝试提取积分等奖励信息)
可选通知推送(PushPlus)
主要方法
1)login()
作用:完成自动登录并判断是否登录成功
关键点:
多个登录入口 URL 逐个尝试
多种定位方式查找账号/密码输入框
提交后返回首页,用"退出/设置/消息"等特征判断是否成功登录
2)discover_sign_url()
作用:找到论坛"签到页面"地址
策略:
直接访问常见签到插件 URL(如
dsu_paulsign/k_misign/task)进入首页/任务中心/空间页,扫描链接文字包含"签到/打卡"等
若文本找不到,再从所有链接里匹配关键字(href 中含
sign/qiandao/task)
3)do_sign(sign_url)
作用:进入签到页并执行签到点击
策略:
优先点击包含"签到/打卡/立即签到"的元素(a/button/span)
点击后等待页面更新
调用
_parse_sign_result()解析结果
4)_parse_sign_result(html_text, clicked, url)
作用:把页面返回内容归类为:
签到成功(含"签到成功/打卡成功")
今日已签到(含"已签到/今日已签到/已经签到")
可能已触发(点了按钮但页面没出现明确文字,多数是弹窗/JS 渲染)
失败(都不满足)
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论