此脚本实现红豆社区自动签到,支持登录态复用、失败重试及截图日志。需配置服务器环境与账号信息,仅限学习研究。
阿里云:
服务器购买地址
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 timeimport pathlibimport tracebackfrom dataclasses import dataclassfrom typing import Optionalfrom playwright.sync_api import sync_playwright, TimeoutError as PWTimeoutError# ----------------------------# 可配置项# ----------------------------BASE_URL = "https://hongdou.gxnews.com.cn/"SIGNIN_URL_CANDIDATES = [# 在F12里找到真实签到入口后替换/新增"https://hongdou.gxnews.com.cn/",# "https://hongdou.gxnews.com.cn/plugin.php?id=dsu_paulsign:sign", # 常见论坛签到插件形式(示例)# "https://hongdou.gxnews.com.cn/home.php?mod=task", # 任务中心(示例)]# 把"可能的入口"与"关键按钮/输入框"选择器集中在这里,后续只改这里即可SELECTORS = {# --- 登录入口(示例,需要你按实际页面改) ---"login_entry": "text=登录", # 首页"登录"按钮/链接"username_input": "input[name='username']", # 用户名输入框"password_input": "input[name='password']", # 密码输入框"submit_login": "button[type='submit'], input[type='submit']", # 登录提交按钮# --- 登录态判断(示例,需要你按实际页面改) ---"user_avatar_or_profile": "a:has-text('退出'), a:has-text('我的'), img.avatar", # 登录后可见的元素之一# --- 签到入口/按钮(示例,需要你按实际页面改) ---"signin_entry": "text=签到", # 页面上的"签到"入口"signin_button": "text=立即签到", # 真正的"立即签到"按钮"already_signed_hint": "text=已签到", # 已签到提示}DEFAULT_TIMEOUT_MS = 20_000RETRY_TIMES = 3@dataclassclass RunConfig:username: strpassword: strheadless: bool = Truedata_dir: str = "./user_data"screenshot_dir: str = "./screenshots"class HongDouSignInBot:def __init__(self, cfg: RunConfig):self.cfg = cfgself.screenshot_path = pathlib.Path(cfg.screenshot_dir)self.screenshot_path.mkdir(parents=True, exist_ok=True)self.data_dir = pathlib.Path(cfg.data_dir)self.data_dir.mkdir(parents=True, exist_ok=True)# ----------------------------# 工具方法# ----------------------------def _log(self, msg: str):now = time.strftime("%Y-%m-%d %H:%M:%S")print(f"[{now}] {msg}")def _shot(self, page, name: str):ts = time.strftime("%Y%m%d_%H%M%S")p = self.screenshot_path / f"{ts}_{name}.png"try:page.screenshot(path=str(p), full_page=True)self._log(f"📸 截图保存: {p}")except Exception as e:self._log(f"⚠️ 截图失败: {e}")def _goto(self, page, url: str):self._log(f"🌐 打开页面: {url}")page.goto(url, wait_until="domcontentloaded", timeout=DEFAULT_TIMEOUT_MS)def _click_if_exists(self, page, selector: str, desc: str, timeout_ms: int = 3000) -> bool:try:el = page.locator(selector).firstel.wait_for(state="visible", timeout=timeout_ms)el.click(timeout=timeout_ms)self._log(f"✅ 点击: {desc}")return Trueexcept PWTimeoutError:return Falseexcept Exception as e:self._log(f"⚠️ 点击失败({desc}): {e}")return Falsedef _fill_if_exists(self, page, selector: str, value: str, desc: str, timeout_ms: int = 3000) -> bool:try:el = page.locator(selector).firstel.wait_for(state="visible", timeout=timeout_ms)el.fill(value, timeout=timeout_ms)self._log(f"✅ 填写: {desc}")return Trueexcept PWTimeoutError:return Falseexcept Exception as e:self._log(f"⚠️ 填写失败({desc}): {e}")return False# ----------------------------# 核心流程方法# ----------------------------def ensure_logged_in(self, page) -> bool:"""尝试复用登录态;如果未登录则执行账号密码登录"""self._log("🔐 检查登录状态...")# 若页面已出现"退出/头像/个人中心"等,视为已登录if page.locator(SELECTORS["user_avatar_or_profile"]).count() > 0:self._log("✅ 已检测到登录态(无需重复登录)")return Trueself._log("ℹ️ 未检测到登录态,开始登录流程...")# 点击登录入口clicked = self._click_if_exists(page, SELECTORS["login_entry"], "登录入口")if not clicked:self._log("⚠️ 未找到登录入口按钮/链接,请更新 SELECTORS['login_entry']")return False# 填写账号密码if not self._fill_if_exists(page, SELECTORS["username_input"], self.cfg.username, "用户名/账号"):self._log("⚠️ 未找到用户名输入框,请更新 SELECTORS['username_input']")return Falseif not self._fill_if_exists(page, SELECTORS["password_input"], self.cfg.password, "密码"):self._log("⚠️ 未找到密码输入框,请更新 SELECTORS['password_input']")return False# 点击提交if not self._click_if_exists(page, SELECTORS["submit_login"], "登录提交"):self._log("⚠️ 未找到登录提交按钮,请更新 SELECTORS['submit_login']")return False# 等待登录完成try:page.wait_for_timeout(1500)page.wait_for_load_state("domcontentloaded", timeout=DEFAULT_TIMEOUT_MS)except Exception:passif page.locator(SELECTORS["user_avatar_or_profile"]).count() > 0:self._log("✅ 登录成功")return Trueself._log("❌ 登录可能失败:未检测到登录后标志元素。可能原因:验证码/选择器不匹配/账号密码错误")self._shot(page, "login_failed")return Falsedef open_signin_page(self, page) -> bool:"""打开签到入口:先尝试从首页点击"签到",不行再尝试候选URL"""self._log("🧭 尝试进入签到页面...")# 先尝试在当前页点击"签到"入口if self._click_if_exists(page, SELECTORS["signin_entry"], "签到入口", timeout_ms=4000):try:page.wait_for_load_state("domcontentloaded", timeout=DEFAULT_TIMEOUT_MS)except Exception:passreturn True# 再尝试候选URLfor url in SIGNIN_URL_CANDIDATES:try:self._goto(page, url)# 如果页面里能找到"签到按钮/已签到提示",就认为进入了签到相关页面if (page.locator(SELECTORS["signin_button"]).count() > 0or page.locator(SELECTORS["already_signed_hint"]).count() > 0or page.locator(SELECTORS["signin_entry"]).count() > 0):self._log(f"✅ 命中签到相关页面: {url}")return Trueexcept Exception as e:self._log(f"⚠️ 打开候选签到URL失败: {url} err={e}")self._log("❌ 未能进入签到页面:请确认真实签到入口URL或更新 SELECTORS['signin_entry']")self._shot(page, "signin_entry_not_found")return Falsedef do_signin(self, page) -> str:"""执行签到:识别"已签到" -> 直接返回;否则点击"立即签到""""self._log("🧾 开始执行签到...")# 已签到判断if page.locator(SELECTORS["already_signed_hint"]).count() > 0:self._log("✅ 检测到已签到提示:今日无需重复签到")self._shot(page, "already_signed")return "今日已签到"# 点击签到按钮if self._click_if_exists(page, SELECTORS["signin_button"], "立即签到", timeout_ms=6000):page.wait_for_timeout(1500)# 再次确认是否出现已签到提示if page.locator(SELECTORS["already_signed_hint"]).count() > 0:self._log("🎉 签到成功(出现已签到提示)")self._shot(page, "signed_success")return "签到成功"# 即使没匹配到提示,也截个图给你排查self._shot(page, "signed_clicked")self._log("✅ 已点击签到按钮,但未识别到明确结果提示(可能提示文案/选择器不同)")return "已点击签到按钮(结果待确认)"self._log("❌ 未找到签到按钮:请更新 SELECTORS['signin_button']")self._shot(page, "signin_button_not_found")return "签到失败:未找到签到按钮"def run(self) -> str:"""总入口:启动浏览器 -> 打开首页 -> 确保登录 -> 进入签到页 -> 签到 -> 返回结果"""for attempt in range(1, RETRY_TIMES + 1):self._log(f"🚀 运行开始:第 {attempt}/{RETRY_TIMES} 次尝试")try:with sync_playwright() as p:context = p.chromium.launch_persistent_context(user_data_dir=str(self.data_dir),headless=self.cfg.headless,viewport={"width": 1280, "height": 800},args=["--disable-blink-features=AutomationControlled"],)page = context.new_page()page.set_default_timeout(DEFAULT_TIMEOUT_MS)self._goto(page, BASE_URL)# 1) 登录if not self.ensure_logged_in(page):context.close()return "登录失败(可能选择器不匹配/验证码/账号错误)"# 2) 进入签到页if not self.open_signin_page(page):context.close()return "进入签到页失败(需要补充签到入口URL或更新选择器)"# 3) 执行签到result = self.do_signin(page)context.close()return resultexcept Exception as e:self._log(f"❌ 本次尝试失败: {e}")self._log(traceback.format_exc())if attempt < RETRY_TIMES:self._log("⏳ 稍后重试...")time.sleep(2)return "连续多次失败:请检查网络/选择器/登录是否需要验证码"def load_config() -> RunConfig:username = os.getenv("HONGDOU_USERNAME", "").strip()password = os.getenv("HONGDOU_PASSWORD", "").strip()if not username or not password:raise RuntimeError("缺少环境变量:HONGDOU_USERNAME / HONGDOU_PASSWORD")headless = os.getenv("HEADLESS", "true").strip().lower() not in ("false", "0", "off")data_dir = os.getenv("DATA_DIR", "./user_data").strip()return RunConfig(username=username,password=password,headless=headless,data_dir=data_dir,screenshot_dir="./screenshots",)def main():cfg = load_config()bot = HongDouSignInBot(cfg)result = bot.run()print("\n====== 最终结果 ======")print(result)if __name__ == "__main__":main()
解析
自动打开红豆社区,检测是否已登录(复用
user_data保存的登录态)。如未登录,则自动执行账号密码登录。
进入签到页面/签到入口",点击立即签到完成签到。
支持失败重试、截图留证、日志输出,便于在青龙/服务器环境排查问题。
关键选择器集中在
SELECTORS,站点结构变动时只改一处即可。
主要方法
HongDouSignInBot.run→ 脚本总入口:启动浏览器、打开网站、登录、进入签到页、执行签到并返回结果。HongDouSignInBot.ensure_logged_in→ 检查是否已有登录态;没有则执行账号密码登录并判断登录是否成功。HongDouSignInBot.open_signin_page→ 尝试从页面点击"签到入口";若失败则尝试一组候选签到 URL,直到命中签到相关页面。HongDouSignInBot.do_signin→ 判断是否已签到;未签到则点击"立即签到",并尝试识别签到结果。HongDouSignInBot._goto→ 打开指定 URL 并等待页面基础加载完成。HongDouSignInBot._click_if_exists→ 尝试点击某个元素(带超时/异常处理),用于增强脚本健壮性。HongDouSignInBot._fill_if_exists→ 尝试在输入框填值(带超时/异常处理)。HongDouSignInBot._shot→ 保存当前页面截图,便于排查选择器不匹配、验证码等问题。load_config→ 从环境变量读取账号密码与运行参数,构造运行配置。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论