2026年1月29日星期四

Forum Auto-Sign Script : Server Setup and Code Guide

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.

1.购买服务器

阿里云:

服务器购买地址

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=201905

2.部署教程

2024年最新青龙面板跑脚本教程(一)持续更新中

3.代码如下

# -*- coding: utf-8 -*-
import osimport reimport timefrom datetime import datetimefrom pathlib import Path
from playwright.sync_api import sync_playwright, TimeoutError as PWTimeoutError

SCREENSHOT_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 True    return False

def try_login(page) -> bool:    """    账号密码登录(尽量适配 Discuz 风格登录页)    """    if not USERNAME or not PASSWORD:        raise RuntimeError("未设置环境变量 BJCUICAN_USERNAME / BJCUICAN_PASSWORD")
    # 尝试打开登录页    last_err = None    for 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).first                if loc.count() > 0:                    try:                        loc.click(timeout=1200)                        page.wait_for_timeout(800)                        break                    except 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 = False            for sel in user_selectors:                if page.locator(sel).count() > 0:                    try:                        page.locator(sel).first.fill(USERNAME, timeout=1500)                        user_filled = True                        break                    except Exception:                        pass
            pass_filled = False            for sel in pass_selectors:                if page.locator(sel).count() > 0:                    try:                        page.locator(sel).first.fill(PASSWORD, timeout=1500)                        pass_filled = True                        break                    except Exception:                        pass
            if 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 = False            for sel in btn_selectors:                if page.locator(sel).count() > 0:                    try:                        page.locator(sel).first.click(timeout=2000)                        clicked = True                        break                    except Exception:                        pass
            if not clicked:                # 文案按钮兜底                for t in ["登录""登 录""立即登录"]:                    if page.get_by_text(t).count() > 0:                        try:                            page.get_by_text(t).first.click(timeout=2000)                            clicked = True                            break                        except Exception:                            pass
            if not clicked:                last_err = "未找到登录按钮"                continue
            page.wait_for_timeout(2000)            # 等一等跳转            try:                page.wait_for_load_state("networkidle", timeout=15000)            except Exception:                pass
            if 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 False

def 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).first                if 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 True                    except Exception:                        pass
        except PWTimeoutError:            log("页面加载超时,继续尝试下一个路径…")        except Exception as e:            log(f"尝试该路径异常:{e}")
    return False

def 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).first            if 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:                    pass
                html2 = 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 True        except Exception:            pass
    return False

def 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 0
                log("未能识别签到成功信息(可能站点签到入口/文案不同)")                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 1

if __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:统一输出带时间戳的日志,方便排查。


注意

本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。


历史脚本txt文件获取>>
服务器搭建,人工服务咨询>>

没有评论:

发表评论

Forum Auto-Sign Script : Server Setup and Code Guide

Script for automated daily sign-in on forums. Includes server purchase links (Aliyun, Tencent, Huawei), deployment tutorial for Qinglong Pan...