Script for automated daily sign-in on forums. Includes server purchase links (Aliyun, Tencent, Huawei), deployment tutorial for Qinglong Panel, and Python code using Playwright to auto-login and sign-in via multiple detection methods.
阿里云:
服务器购买地址
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 timefrom datetime import datetimefrom pathlib import Pathfrom playwright.sync_api import sync_playwright, TimeoutError as PWTimeoutErrorSCREENSHOT_DIR = Path("./screenshots")SCREENSHOT_DIR.mkdir(exist_ok=True)DEFAULT_BASE_URL = os.getenv("BJCUICAN_BASE_URL", "http://www.bjcuican.com/").strip()USERNAME = os.getenv("BJCUICAN_USERNAME", "").strip()PASSWORD = os.getenv("BJCUICAN_PASSWORD", "").strip()HEADLESS = os.getenv("BJCUICAN_HEADLESS", "true").strip().lower() not in ("0", "false", "off")# 常见 Discuz / 论坛签到插件路径(按常见生态做"探测列表")SIGNIN_CANDIDATE_PATHS = [# Discuz 常见签到插件"plugin.php?id=dsu_paulsign:sign","plugin.php?id=k_misign:sign","plugin.php?id=dd_sign", # 有些站点用类似命名"plugin.php?id=dc_signin:signin","plugin.php?id=qiandao","plugin.php?id=sign",# 一些站点直接提供的签到页"home.php?mod=spacecp&ac=credit&op=base","home.php?mod=spacecp&ac=credit",]# 登录页面候选(不同站点可能跳转不同)LOGIN_CANDIDATE_PATHS = ["member.php?mod=logging&action=login","forum.php","",]def now_str():return datetime.now().strftime("%Y-%m-%d %H:%M:%S")def shot_path(prefix: str):ts = datetime.now().strftime("%Y%m%d_%H%M%S")return SCREENSHOT_DIR / f"{prefix}_{ts}.png"def join_url(base: str, path: str) -> str:base = base.rstrip("/")path = path.lstrip("/")return f"{base}/{path}" if path else f"{base}/"def log(msg: str):print(f"[{now_str()}] {msg}")def is_logged_in_by_dom(page) -> bool:"""用 DOM 特征判断是否已登录(通用策略)- 若页面存在"退出/登出/注销/设置"等文案,通常已登录- 或存在 Discuz 常见的 uid / 用户菜单等"""html = page.content()keywords = ["退出", "登出", "注销", "设置", "个人中心", "消息", "提醒"]if any(k in html for k in keywords):return True# Discuz 常见:顶部用户菜单可能含 uid= 或 space-uid-if re.search(r"space-uid-|uid=\d+", html):return Truereturn Falsedef try_login(page) -> bool:"""账号密码登录(尽量适配 Discuz 风格登录页)"""if not USERNAME or not PASSWORD:raise RuntimeError("未设置环境变量 BJCUICAN_USERNAME / BJCUICAN_PASSWORD")# 尝试打开登录页last_err = Nonefor lp in LOGIN_CANDIDATE_PATHS:url = join_url(DEFAULT_BASE_URL, lp)try:log(f"打开页面用于登录检测: {url}")page.goto(url, wait_until="domcontentloaded", timeout=30000)page.wait_for_timeout(800)if is_logged_in_by_dom(page):log("检测到已登录状态(跳过登录)")return True# 尝试点击"登录/立即登录"等入口for text in ["登录", "立即登录", "账号登录"]:loc = page.get_by_text(text).firstif loc.count() > 0:try:loc.click(timeout=1200)page.wait_for_timeout(800)breakexcept Exception:pass# 常见登录输入框选择器(Discuz/通用)user_selectors = ["input[name='username']","input[name='loginfield']","input[name='email']","input[type='text']",]pass_selectors = ["input[name='password']","input[type='password']",]user_filled = Falsefor sel in user_selectors:if page.locator(sel).count() > 0:try:page.locator(sel).first.fill(USERNAME, timeout=1500)user_filled = Truebreakexcept Exception:passpass_filled = Falsefor sel in pass_selectors:if page.locator(sel).count() > 0:try:page.locator(sel).first.fill(PASSWORD, timeout=1500)pass_filled = Truebreakexcept Exception:passif not (user_filled and pass_filled):last_err = "未找到可填写的账号/密码输入框"continue# 登录按钮:常见 name=loginsubmit / 文案"登录"btn_selectors = ["button[name='loginsubmit']","button[type='submit']","input[name='loginsubmit']","input[type='submit']",]clicked = Falsefor sel in btn_selectors:if page.locator(sel).count() > 0:try:page.locator(sel).first.click(timeout=2000)clicked = Truebreakexcept Exception:passif not clicked:# 文案按钮兜底for t in ["登录", "登 录", "立即登录"]:if page.get_by_text(t).count() > 0:try:page.get_by_text(t).first.click(timeout=2000)clicked = Truebreakexcept Exception:passif not clicked:last_err = "未找到登录按钮"continuepage.wait_for_timeout(2000)# 等一等跳转try:page.wait_for_load_state("networkidle", timeout=15000)except Exception:passif is_logged_in_by_dom(page):log("登录成功 ✅")return True# 可能有验证码/二次验证last_err = "登录未成功(可能需要验证码/滑块/短信验证)"except Exception as e:last_err = str(e)log(f"登录失败 ❌:{last_err}")page.screenshot(path=str(shot_path("login_failed")), full_page=True)return Falsedef try_signin_by_known_paths(page) -> bool:"""依次尝试常见签到路径,命中即完成"""for path in SIGNIN_CANDIDATE_PATHS:url = join_url(DEFAULT_BASE_URL, path)log(f"尝试签到路径: {url}")try:page.goto(url, wait_until="domcontentloaded", timeout=30000)page.wait_for_timeout(1200)html = page.content()# 成功/已签到常见提示(按论坛常见做兜底)ok_keywords = ["签到成功", "已签到", "今日已签到", "打卡成功", "您已签到", "已经签到"]if any(k in html for k in ok_keywords):log(f"命中签到结果关键词 ✅:{[k for k in ok_keywords if k in html][0]}")return True# 页面上有"签到/打卡/立即签到"按钮就点一次试试for t in ["签到", "立即签到", "打卡", "马上签到", "一键签到"]:loc = page.get_by_text(t).firstif loc.count() > 0:try:loc.click(timeout=2500)page.wait_for_timeout(1500)html2 = page.content()if any(k in html2 for k in ok_keywords):log(f"点击按钮后签到成功 ✅:{[k for k in ok_keywords if k in html2][0]}")return Trueexcept Exception:passexcept PWTimeoutError:log("页面加载超时,继续尝试下一个路径…")except Exception as e:log(f"尝试该路径异常:{e}")return Falsedef try_signin_by_search_entry(page) -> bool:"""在首页/论坛页搜索"签到/打卡/每日签到"入口点击"""entry_keywords = ["签到", "打卡", "每日签到", "签到领奖", "签到领"]home = join_url(DEFAULT_BASE_URL, "")log(f"入口兜底:打开首页搜索签到入口: {home}")page.goto(home, wait_until="domcontentloaded", timeout=30000)page.wait_for_timeout(1200)for kw in entry_keywords:try:loc = page.get_by_text(kw).firstif loc.count() > 0:log(f"发现入口文字「{kw}」,尝试点击…")loc.click(timeout=2500)page.wait_for_timeout(1500)try:page.wait_for_load_state("networkidle", timeout=12000)except Exception:passhtml2 = page.content()ok_keywords = ["签到成功", "已签到", "今日已签到", "打卡成功", "您已签到", "已经签到"]if any(k in html2 for k in ok_keywords):log(f"入口点击后签到成功 ✅:{[k for k in ok_keywords if k in html2][0]}")return True# 进入页面后再找"立即签到"for t in ["立即签到", "签到", "打卡", "马上签到"]:if page.get_by_text(t).count() > 0:page.get_by_text(t).first.click(timeout=2500)page.wait_for_timeout(1200)html3 = page.content()if any(k in html3 for k in ok_keywords):log(f"二次点击后签到成功 ✅:{[k for k in ok_keywords if k in html3][0]}")return Trueexcept Exception:passreturn Falsedef run(max_retry: int = 3) -> int:"""主流程:登录 -> 签到(多路径/入口兜底)-> 失败截图"""with sync_playwright() as p:browser = p.chromium.launch(headless=HEADLESS)context = browser.new_context(viewport={"width": 1400, "height": 900})page = context.new_page()for attempt in range(1, max_retry + 1):log(f"开始执行(第 {attempt}/{max_retry} 次)")try:ok_login = try_login(page)if not ok_login:log("登录失败,终止本次尝试")continue# 1) 先探测常见签到路径if try_signin_by_known_paths(page):log("签到流程完成 ✅")browser.close()return 0# 2) 再做页面入口兜底if try_signin_by_search_entry(page):log("签到流程完成 ✅")browser.close()return 0log("未能识别签到成功信息(可能站点签到入口/文案不同)")page.screenshot(path=str(shot_path("signin_unknown")), full_page=True)except Exception as e:log(f"本次执行异常:{e}")page.screenshot(path=str(shot_path("signin_error")), full_page=True)# 重试等待if attempt < max_retry:log("等待 3 秒后重试…")time.sleep(3)browser.close()return 1if __name__ == "__main__":code = run(max_retry=3)raise SystemExit(code)
自动登录璀璨论坛(bjcuican.com)
自动执行每日签到(优先尝试常见签到插件 URL;失败则在页面搜索"签到/打卡"入口点击)
失败自动截图
主要方法
try_login:打开登录页,自动填写账号密码并点击登录;若检测到已登录则直接跳过。is_logged_in_by_dom:通过页面 DOM/关键字判断当前是否处于登录状态(例如是否出现"退出/个人中心"等)。try_signin_by_known_paths:按内置"常见签到插件路径列表"逐个访问并尝试签到,命中成功关键词则结束。try_signin_by_search_entry:在首页/论坛页查找"签到/打卡/每日签到"等入口文字并点击,作为兜底策略。run:主流程控制(重试、调用登录/签到、失败截图、退出码返回)。shot_path:生成失败截图文件名并保存到./screenshots/。join_url:把base_url和相对路径拼成可访问的完整 URL。log:统一输出带时间戳的日志,方便排查。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论