Summary: This script automates daily sign-in for Huilongguan community sites. It includes server setup links, deployment steps, and Python code for login and sign-in via keyword detection. Screenshots and retries aid troubleshooting. Use requires environment variables for credentials.
阿里云:
服务器购买地址
https://t.aliyun.com/U/siC86h若失效,可用地址
https://www.aliyun.com/benefit?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 pathlibfrom datetime import datetimefrom typing import Optional, List, Tuplefrom playwright.sync_api import sync_playwright, TimeoutError as PWTimeoutErrorBASE_URLS = ["https://www.hlgnet.com/","http://bbs.hlgnet.com/",]KEYWORDS_SIGN = ["签到", "打卡", "每日签到", "签 到", "Check in", "checkin"]KEYWORDS_LOGIN = ["登录", "登录/注册", "登陆"]KEYWORDS_LOGOUT = ["退出", "退出登录", "注销"]ART_DIR = pathlib.Path("hlgnet_artifacts")ART_DIR.mkdir(exist_ok=True)def now_str() -> str:return datetime.now().strftime("%Y-%m-%d %H:%M:%S")def save_screenshot(page, name: str) -> str:fp = ART_DIR / f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{name}.png"page.screenshot(path=str(fp), full_page=True)return str(fp)def wait_network_idle(page, timeout_ms: int = 15000) -> None:# Playwright 的 networkidle 对某些站点可能不稳定,因此做一个温和等待try:page.wait_for_load_state("domcontentloaded", timeout=timeout_ms)except PWTimeoutError:passtime.sleep(1.0)def open_site(page) -> str:"""依次尝试打开可访问的URL,返回最终使用的URL"""last_err = Nonefor url in BASE_URLS:try:page.goto(url, wait_until="domcontentloaded", timeout=20000)wait_network_idle(page)return urlexcept Exception as e:last_err = eraise RuntimeError(f"站点不可访问:{last_err}")def is_logged_in(page) -> bool:"""通过页面上是否出现"退出/注销"等关键词粗略判断已登录"""text = page.inner_text("body")[:20000]return any(k in text for k in KEYWORDS_LOGOUT)def try_click_by_keywords(page, keywords: List[str], max_click: int = 1) -> bool:"""在页面内搜索包含关键词的可点击元素并点击- 优先找 a/button- 找到后尝试滚动到可见区域点击"""clicked = 0for kw in keywords:# a / button / role=button / input[type=submit] 的组合检索candidates = [f"a:has-text('{kw}')",f"button:has-text('{kw}')",f"[role='button']:has-text('{kw}')",]for sel in candidates:loc = page.locator(sel).firsttry:if loc.count() > 0:loc.scroll_into_view_if_needed(timeout=3000)loc.click(timeout=3000)clicked += 1wait_network_idle(page)if clicked >= max_click:return Trueexcept Exception:continuereturn clicked > 0def try_login(page, username: str, password: str) -> bool:"""尝试登录(通用策略):1) 点击"登录/登陆"2) 寻找账号/密码输入框3) 提交表单"""# 已登录则跳过if is_logged_in(page):return True# 尝试打开登录页/弹窗try_click_by_keywords(page, KEYWORDS_LOGIN, max_click=1)# 常见的输入框策略:text/email/tel + password# 账号输入框可能是 name=username / email / phone 等account_selectors = ["input[name='username']","input[name='user']","input[name='account']","input[name='email']","input[name='mobile']","input[type='text']","input[type='email']","input[type='tel']",]pass_selectors = ["input[name='password']","input[name='pass']","input[type='password']",]submit_selectors = ["button:has-text('登录')","button:has-text('登陆')","input[type='submit']","button[type='submit']",]# 填账号account_filled = Falsefor sel in account_selectors:loc = page.locator(sel).firsttry:if loc.count() > 0 and loc.is_visible():loc.click()loc.fill(username)account_filled = Truebreakexcept Exception:continue# 填密码pass_filled = Falsefor sel in pass_selectors:loc = page.locator(sel).firsttry:if loc.count() > 0 and loc.is_visible():loc.click()loc.fill(password)pass_filled = Truebreakexcept Exception:continueif not (account_filled and pass_filled):return False# 提交submitted = Falsefor sel in submit_selectors:loc = page.locator(sel).firsttry:if loc.count() > 0 and loc.is_visible():loc.click(timeout=5000)submitted = Truebreakexcept Exception:continuewait_network_idle(page)# 验证是否登录成功return submitted and is_logged_in(page)def do_sign(page) -> Tuple[bool, str]:"""执行签到:- 在当前页面尝试寻找"签到/打卡/每日签到"等入口点击- 如果跳转到新页面,再次尝试点击"签到"按钮"""# 第一次在首页/当前页找入口ok = try_click_by_keywords(page, KEYWORDS_SIGN, max_click=1)if not ok:return False, "未找到签到入口(页面未出现"签到/打卡/每日签到"等字样)"# 进入签到页后,还有一个"立即签到/签到领取/确认签到"ok2 = try_click_by_keywords(page, ["立即签到", "确认签到", "签到领取", "签到", "打卡"], max_click=1)if ok2:return True, "已点击签到按钮(请以页面实际结果为准)"# 如果第二步没找到,点入口即完成return True, "已点击签到入口(入口可能已完成签到或需要在新页面手动确认)"def run(max_retry: int = 2, headless: bool = True) -> None:user = os.getenv("HLG_USER", "").strip()pwd = os.getenv("HLG_PASS", "").strip()if not user or not pwd:raise RuntimeError("缺少环境变量:HLG_USER / HLG_PASS(账号密码)")with sync_playwright() as p:browser = p.chromium.launch(headless=headless)context = browser.new_context(locale="zh-CN")page = context.new_page()last_error: Optional[str] = Nonefor attempt in range(1, max_retry + 2):try:print(f"[{now_str()}] 第 {attempt} 次尝试:打开站点")used_url = open_site(page)print(f"[{now_str()}] 已打开:{used_url}")print(f"[{now_str()}] 尝试登录")logged = try_login(page, user, pwd)if not logged:shot = save_screenshot(page, "login_failed")raise RuntimeError(f"登录流程未成功(可能是站点登录结构不同/出现验证码/输入框未命中)。截图:{shot}")print(f"[{now_str()}] 登录状态:OK")print(f"[{now_str()}] 开始执行签到")ok, msg = do_sign(page)shot = save_screenshot(page, "after_sign")print(f"[{now_str()}] 签到结果:{ok} - {msg}")print(f"[{now_str()}] 截图:{shot}")returnexcept Exception as e:last_error = str(e)print(f"[{now_str()}] ❌ 本次失败:{last_error}")try:shot = save_screenshot(page, "error")print(f"[{now_str()}] 错误截图:{shot}")except Exception:passtime.sleep(2)raise RuntimeError(f"多次重试仍失败:{last_error}")if __name__ == "__main__":# headless=True:无头模式(适合服务器/青龙)# 若要调试选择器,把 headless 改为 Falserun(max_retry=2, headless=True)
该脚本为回龙观自动签到脚本,主要作用包括:
自动打开回龙观社区网站(优先 https,失败则尝试 bbs 子站 http)。
自动登录(使用环境变量提供的账号密码)。
自动寻找"签到/打卡/每日签到"入口并点击,尽量不依赖固定签到 URL。
保存截图与日志,便于你定位是登录页结构变化、入口文字变化、还是验证码拦截导致失败。
失败自动重试(默认最多 3 次尝试)。
主要方法
now_str:生成当前时间字符串,用于日志打印。
save_screenshot:保存当前页面截图到本地
hlgnet_artifacts/,用于排障。wait_network_idle:等待页面基础加载完成并额外停顿,降低动态渲染导致的找不到元素问题。
open_site:按顺序尝试打开可访问的站点入口 URL,返回最终成功打开的地址。
is_logged_in:通过页面是否出现"退出/注销"等文本,粗略判断是否已登录。
try_click_by_keywords:在页面里按关键词搜索可点击元素(a/button/role=button)并点击,用于"登录/签到"这种入口不固定的场景。
try_login:通用登录流程:点击登录入口 → 填账号密码 → 提交 → 再用
is_logged_in验证。do_sign:执行签到流程:先点"签到入口",若进入签到页再点"立即签到/确认签到"等按钮。
run:脚本总入口,串联"打开站点 → 登录 → 签到 → 截图 → 重试"的完整流程。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论