本脚本用于自动化登录化龙巷论坛并完成每日签到。用户需配置服务器及青龙面板,填入账户信息后运行。脚本采用多策略定位签到入口,登录失败或签到异常时会自动截图,便于排查验证码或路径问题。仅供学习测试。
阿里云:
服务器购买地址
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 sysimport timefrom datetime import datetimefrom pathlib import Pathfrom playwright.sync_api import sync_playwright, TimeoutError as PWTimeoutErrorBASE_URL = "http://bbs.hualongxiang.com"OUT_DIR = Path("hlx_artifacts")OUT_DIR.mkdir(exist_ok=True)def env_bool(name: str, default: bool = True) -> bool:val = os.getenv(name, str(default)).strip().lower()return val not in ["0", "false", "off", "no"]def now_str() -> str:return datetime.now().strftime("%Y%m%d_%H%M%S")class HualongxiangSignBot:def __init__(self, username: str, password: str, headless: bool = True, timeout_s: int = 30):self.username = usernameself.password = passwordself.headless = headlessself.timeout_ms = timeout_s * 1000self.pw = Noneself.browser = Noneself.context = Noneself.page = None# -------------------- 核心流程 --------------------def run(self) -> None:try:self.start()self.login()ok, msg = self.sign_in()self.log(f"[RESULT] ok={ok} msg={msg}")except Exception as e:self.log(f"[FATAL] {type(e).__name__}: {e}")self.screenshot("fatal")raisefinally:self.close()# -------------------- 浏览器管理 --------------------def start(self) -> None:self.pw = sync_playwright().start()self.browser = self.pw.chromium.launch(headless=self.headless)self.context = self.browser.new_context(viewport={"width": 1280, "height": 800},user_agent=("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/123.0.0.0 Safari/537.36"),locale="zh-CN",)self.page = self.context.new_page()self.page.set_default_timeout(self.timeout_ms)self.log("[INIT] browser started")def close(self) -> None:try:if self.context:self.context.close()if self.browser:self.browser.close()if self.pw:self.pw.stop()except Exception:pass# -------------------- 登录 --------------------def login(self) -> None:"""Discuz 常见登录入口:- /member.php?mod=logging&action=login- /member.php?mod=logging&action=login&infloat=yes&handlekey=login"""login_urls = [f"{BASE_URL}/member.php?mod=logging&action=login",f"{BASE_URL}/member.php?mod=logging&action=login&infloat=yes&handlekey=login",]for url in login_urls:self.log(f"[LOGIN] open: {url}")self.page.goto(url, wait_until="domcontentloaded")# 如果已经登录,直接返回if self.is_logged_in():self.log("[LOGIN] already logged in")return# 尝试填写表单(兼容不同 input name)user_selectors = ['input[name="username"]','input[name="email"]','input[name="account"]','input[name="loginfield"]','input[placeholder*="用户名"]','input[placeholder*="邮箱"]','input[placeholder*="手机"]',]pass_selectors = ['input[name="password"]','input[type="password"]',]user_el = self.first_visible(user_selectors)pwd_el = self.first_visible(pass_selectors)if not user_el or not pwd_el:self.log("[LOGIN] cannot find login inputs on this page, try next url")continueuser_el.fill(self.username)pwd_el.fill(self.password)# 常见登录按钮btn_selectors = ['button[name="loginsubmit"]','button#loginsubmit','input[name="loginsubmit"]','button:has-text("登录")','input[type="submit"]',]btn = self.first_visible(btn_selectors)if not btn:self.log("[LOGIN] cannot find submit button, try press Enter")pwd_el.press("Enter")else:btn.click()# 等待跳转/刷新self.page.wait_for_timeout(1500)if self.is_logged_in():self.log("[LOGIN] success")return# 若出现验证码/风控,截图提示self.log("[LOGIN] not logged in yet; maybe captcha/extra step")self.screenshot("login_failed_try")# 所有登录入口都失败self.screenshot("login_failed")raise RuntimeError("登录失败:未能完成登录(可能是验证码/风控/站点结构不同)")def is_logged_in(self) -> bool:html = self.page.content()# Discuz 登录后常见:退出/设置/提醒/用户名区域keywords = ["退出", "设置", "提醒", "我的", "欢迎您", "个人中心"]hit = sum(1 for k in keywords if k in html)# 同时排除明显的"登录/注册"强提示if "登录" in html and "密码" in html and hit <= 1:return Falsereturn hit >= 2# -------------------- 签到 --------------------def sign_in(self) -> tuple[bool, str]:"""多策略签到:1) 直接访问常见签到插件路径(dsu_paulsign / k_misign 等)2) 在页面中寻找"签到/打卡/签 到"等按钮并点击"""# 常见 Discuz 签到插件入口(不同站点可能不同)sign_urls = [f"{BASE_URL}/plugin.php?id=dsu_paulsign:sign",f"{BASE_URL}/plugin.php?id=k_misign:sign",f"{BASE_URL}/plugin.php?id=dsu_paulsign:sign&operation=qiandao",f"{BASE_URL}/plugin.php?id=dsu_paulsign:sign&infloat=1",f"{BASE_URL}/plugin.php?id=dsu_paulsign:sign&operation=signin",]for url in sign_urls:try:self.log(f"[SIGN] try url: {url}")self.page.goto(url, wait_until="domcontentloaded")self.page.wait_for_timeout(800)# 已签到特征词(不同插件不同)html = self.page.content()if any(x in html for x in ["已经签到", "今日已签", "已打卡", "签到过了", "您今天已经签到"]):self.screenshot("already_signed")return True, "今日已签到"# 寻找"签到"按钮并点击(表单提交)btn = self.first_visible(['button:has-text("签到")','a:has-text("签到")','input[value*="签到"]','button:has-text("打卡")','a:has-text("打卡")','button:has-text("签 到")','a:has-text("签 到")',])if btn:btn.click()self.page.wait_for_timeout(1200)html2 = self.page.content()if any(x in html2 for x in ["签到成功", "已签到", "获得", "奖励", "积分", "经验"]):self.screenshot("sign_success")return True, "签到成功(已命中插件页)"# 某些插件需要提交 formhash# 兜底:如果页面里有 formhash,尝试提交包含 formhash 的表单formhash = self.extract_formhash()if formhash:self.log(f"[SIGN] found formhash={formhash}, try submit")self.try_submit_with_formhash(formhash)self.page.wait_for_timeout(1200)html3 = self.page.content()if any(x in html3 for x in ["签到成功", "已签到", "获得", "奖励", "积分", "经验"]):self.screenshot("sign_success_formhash")return True, "签到成功(formhash 提交)"except PWTimeoutError:self.log("[SIGN] timeout, try next")continueexcept Exception as e:self.log(f"[SIGN] error: {e}, try next")continue# 2) 从首页找入口(有些站点把"签到"挂在顶部导航)self.log("[SIGN] fallback: scan from home page")self.page.goto(BASE_URL, wait_until="domcontentloaded")self.page.wait_for_timeout(800)nav_btn = self.first_visible(['a:has-text("签到")','a:has-text("打卡")','a:has-text("每日签到")','a:has-text("签 到")','button:has-text("签到")',])if nav_btn:nav_btn.click()self.page.wait_for_timeout(1200)# 再找一次签到按钮btn2 = self.first_visible(['button:has-text("签到")','a:has-text("签到")','input[value*="签到"]','button:has-text("打卡")','a:has-text("打卡")',])if btn2:btn2.click()self.page.wait_for_timeout(1200)html4 = self.page.content()if any(x in html4 for x in ["签到成功", "已签到", "获得", "奖励", "积分", "经验"]):self.screenshot("sign_success_fallback")return True, "签到成功(首页入口兜底)"self.screenshot("sign_failed")return False, "未找到有效签到入口(可能站点没有签到/插件路径不同/需要验证码)"def extract_formhash(self) -> str | None:html = self.page.content()m = re.search(r'name="formhash"\s+value="([^"]+)"', html)if m:return m.group(1)return Nonedef try_submit_with_formhash(self, formhash: str) -> None:# 最粗暴兜底:页面内直接执行 JS 提交第一个表单# 或者点击可能的提交按钮btn = self.first_visible(['button[type="submit"]','input[type="submit"]','button:has-text("提交")','button:has-text("确定")','a:has-text("签到")',])if btn:btn.click()return# 再兜底:直接用 JS submitself.page.evaluate("""() => {const forms = document.querySelectorAll('form');if (forms && forms.length) { forms[0].submit(); }}""")# -------------------- 工具方法 --------------------def first_visible(self, selectors: list[str]):for s in selectors:try:el = self.page.locator(s).firstif el.count() > 0 and el.is_visible():return elexcept Exception:continuereturn Nonedef screenshot(self, tag: str) -> None:try:path = OUT_DIR / f"{tag}_{now_str()}.png"self.page.screenshot(path=str(path), full_page=True)self.log(f"[SHOT] {path}")except Exception:passdef log(self, msg: str) -> None:print(f"{datetime.now().strftime('%H:%M:%S')} {msg}")def main():username = os.getenv("HLX_USERNAME", "").strip()password = os.getenv("HLX_PASSWORD", "").strip()if not username or not password:print("❌ 缺少环境变量:HLX_USERNAME / HLX_PASSWORD")sys.exit(1)headless = env_bool("HLX_HEADLESS", True)timeout_s = int(os.getenv("HLX_TIMEOUT", "30"))bot = HualongxiangSignBot(username, password, headless=headless, timeout_s=timeout_s)bot.run()if __name__ == "__main__":main()
自动化登录化龙巷论坛账号
自动尝试多个常见论坛签到入口并完成签到
如果站点签到入口不固定,则回到首页扫描"签到/打卡"入口再执行
全程输出日志,并在关键失败点保存截图,便于你快速定位是"验证码/路径不对/按钮文案不同"
主要方法
run:脚本总入口,串起"启动浏览器 → 登录 → 签到 → 输出结果 → 关闭资源"全过程。start:启动 Playwright 浏览器、创建上下文与页面,并设置超时时间与 UA。close:释放浏览器资源,避免青龙/cron 跑多次后残留进程。login:打开常见登录页,自动识别用户名/密码输入框并提交登录;失败时截图。is_logged_in:通过页面关键字粗判断是否已登录(用于避免重复登录、判断登录成功与否)。sign_in:签到主流程,按"插件路径直达 → 页面点击 → formhash 提交 → 首页入口兜底"多策略完成签到。extract_formhash:从 HTML 中提取 Discuz 常见的formhash(很多操作都需要它)。try_submit_with_formhash:当页面存在 formhash 但找不到明显按钮时,尝试点击提交按钮或直接 JS 提交表单。first_visible:在多个 selector 里找出第一个可见元素(增强兼容性)。screenshot:保存全页截图到hlx_artifacts/,用于排查验证码/按钮变更/跳转失败等问题。log:统一输出带时间戳的日志信息。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论