Summary: Script automates login and daily sign-in for 3dfsh.com forum. It reuses login sessions, scans for sign-in buttons, saves screenshots for debugging. Requires setting username/password as environment variables FSH_USERNAME and FSH_PASSWORD. Includes server purchase links and deployment tutorial.
阿里云:
服务器购买地址
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 pathlib import Pathfrom typing import Optional, List, Tuplefrom playwright.sync_api import sync_playwright, Page, BrowserContextBASE_URL = "https://www.3dfsh.com/"STORAGE_STATE = Path("storage_state_3dfsh.json")SCREENSHOT_DIR = Path("screenshots_3dfsh")SCREENSHOT_DIR.mkdir(exist_ok=True)# 环境变量(你需要自行配置)ENV_USER = "FSH_USERNAME"ENV_PASS = "FSH_PASSWORD"def now_ts() -> str:return time.strftime("%Y%m%d_%H%M%S", time.localtime())def log(msg: str):print(f"[{time.strftime('%H:%M:%S')}] {msg}")def get_env_credential() -> Tuple[str, str]:username = os.getenv(ENV_USER, "").strip()password = os.getenv(ENV_PASS, "").strip()if not username or not password:raise RuntimeError(f"缺少账号密码环境变量:{ENV_USER}/{ENV_PASS}\n"f"Windows PowerShell 示例:\n"f" setx {ENV_USER} \"你的账号\"\n"f" setx {ENV_PASS} \"你的密码\"\n"f"macOS/Linux 示例:\n"f" export {ENV_USER}='你的账号'\n"f" export {ENV_PASS}='你的密码'\n")return username, passworddef new_context(pw, headless: bool) -> BrowserContext:browser = pw.chromium.launch(headless=headless)if STORAGE_STATE.exists():log("检测到历史登录态 storage_state,尝试复用免登录")ctx = browser.new_context(storage_state=str(STORAGE_STATE))else:ctx = browser.new_context()ctx.set_default_timeout(30_000)return ctxdef is_logged_in(page: Page) -> bool:# Discuz 常见:已登录会出现"退出/注销/设置/消息"等html = page.content()keywords = ["退出", "注销", "设置", "消息", "提醒"]hit = sum(1 for k in keywords if k in html)return hit >= 2def try_login(page: Page, username: str, password: str) -> bool:"""兼容 Discuz 常见登录入口:- 点击"登录"- 或直接访问 member.php?mod=logging&action=login"""log("开始尝试登录…")page.goto(BASE_URL, wait_until="domcontentloaded")page.wait_for_timeout(1200)if is_logged_in(page):log("当前页面判断:已登录(跳过登录)")return True# 1) 尝试点页面上的"登录"login_clicked = Falsefor sel in ["text=登录","a:has-text('登录')","text=立即登录",]:loc = page.locator(sel)if loc.count() > 0:try:loc.first.click(timeout=3000)login_clicked = Truebreakexcept Exception:pass# 2) 如果点不到,直接走 Discuz 登录页if not login_clicked:login_url = BASE_URL.rstrip("/") + "/member.php?mod=logging&action=login"log(f"未找到明显登录按钮,直达登录页:{login_url}")page.goto(login_url, wait_until="domcontentloaded")page.wait_for_timeout(1200)# Discuz 登录框常见 name:username / passworduser_candidates = ["input[name='username']", "input[name='email']", "input[type='text']"]pass_candidates = ["input[name='password']", "input[type='password']"]user_filled = Falsefor sel in user_candidates:loc = page.locator(sel)if loc.count() > 0:try:loc.first.fill(username)user_filled = Truebreakexcept Exception:passpass_filled = Falsefor sel in pass_candidates:loc = page.locator(sel)if loc.count() > 0:try:loc.first.fill(password)pass_filled = Truebreakexcept Exception:passif not user_filled or not pass_filled:page.screenshot(path=str(SCREENSHOT_DIR / f"login_form_not_found_{now_ts()}.png"))log("未识别到登录输入框,已截图。可能站点结构/编码特殊或需要手动登录一次生成登录态。")return False# 提交按钮:常见是 name=loginsubmit 或者"登录"submitted = Falsefor sel in ["button[name='loginsubmit']","button:has-text('登录')","input[name='loginsubmit']","input[type='submit']","text=登录并继续",]:loc = page.locator(sel)if loc.count() > 0:try:loc.first.click()submitted = Truebreakexcept Exception:passif not submitted:# 兜底:回车提交page.keyboard.press("Enter")page.wait_for_timeout(2500)# 有些站点会跳回首页try:page.goto(BASE_URL, wait_until="domcontentloaded")page.wait_for_timeout(1500)except Exception:passif is_logged_in(page):log("登录成功 ✅")return True# 可能遇到验证码/风控page.screenshot(path=str(SCREENSHOT_DIR / f"login_failed_{now_ts()}.png"))log("登录失败 ❌(可能需要验证码/安全验证),已截图。建议:先用非无头模式手动登录一次。")return Falsedef collect_possible_signin_urls() -> List[str]:"""Discuz 常见签到插件入口(不同站点不一定都装)"""base = BASE_URL.rstrip("/")candidates = [f"{base}/plugin.php?id=dsu_paulsign:sign",f"{base}/plugin.php?id=k_misign:sign",f"{base}/plugin.php?id=dsu_paulsign",f"{base}/plugin.php?id=k_misign",f"{base}/home.php?mod=spacecp&ac=credit&op=base", # 有些站签到在积分页/任务页]return candidatesdef find_signin_link_in_page(page: Page) -> Optional[str]:"""从页面里扫描"签到/打卡"链接"""html = page.content()# 粗扫:找包含"签到"的 hrefmatches = re.findall(r'href="([^"]+)"[^>]*>([^<]{0,20}签到[^<]{0,20})', html)for href, text in matches[:20]:if "javascript:" in href:continue# 补全相对路径if href.startswith("//"):href = "https:" + hrefelif href.startswith("/"):href = BASE_URL.rstrip("/") + hrefelif not href.startswith("http"):href = BASE_URL.rstrip("/") + "/" + href.lstrip("./")log(f"发现页面签到候选链接:{text.strip()} => {href}")return hrefreturn Nonedef try_sign_in(page: Page) -> bool:"""尝试执行签到:- 先尝试常见插件 URL- 再从页面扫描"签到"入口- 最后在签到页尝试点击"签到/打卡/立即签到""""log("开始尝试签到…")# 先打开首页page.goto(BASE_URL, wait_until="domcontentloaded")page.wait_for_timeout(1200)# 1) 先试固定候选入口for url in collect_possible_signin_urls():try:log(f"尝试签到入口:{url}")page.goto(url, wait_until="domcontentloaded")page.wait_for_timeout(1500)if click_sign_button(page):page.screenshot(path=str(SCREENSHOT_DIR / f"signin_ok_{now_ts()}.png"))log("签到执行完成 ✅(已截图)")return Trueexcept Exception:continue# 2) 扫描页面"签到"链接再试page.goto(BASE_URL, wait_until="domcontentloaded")page.wait_for_timeout(1200)link = find_signin_link_in_page(page)if link:try:page.goto(link, wait_until="domcontentloaded")page.wait_for_timeout(1500)if click_sign_button(page):page.screenshot(path=str(SCREENSHOT_DIR / f"signin_ok_{now_ts()}.png"))log("签到执行完成 ✅(已截图)")return Trueexcept Exception:passpage.screenshot(path=str(SCREENSHOT_DIR / f"signin_failed_{now_ts()}.png"))log("未能完成签到 ❌(已截图)。可能:站点无签到/需要手动验证码/入口变化。")return Falsedef click_sign_button(page: Page) -> bool:"""在当前页面尝试点击签到按钮/提交签到表单"""# 一些签到页会有"签到/打卡/立即签到/一键签到"candidates = ["text=签到","text=打卡","text=立即签到","text=一键签到","button:has-text('签到')","a:has-text('签到')","input[type='submit']",]for sel in candidates:loc = page.locator(sel)if loc.count() > 0:try:# 避免误点导航"签到",优先点可见元素loc.first.scroll_into_view_if_needed()loc.first.click(timeout=3000)page.wait_for_timeout(1500)# 简单判断:页面出现"已签到/签到成功/已打卡"html = page.content()success_keywords = ["签到成功", "已签到", "今日已签到", "打卡成功", "已打卡"]if any(k in html for k in success_keywords):log("检测到成功关键词:可能已签到/签到成功")return True# 有些站点点击后无明显提示,也当作尝试成功(给出截图)log(f"已点击可能的签到按钮:{sel}(未检测到明确成功词,仍可能成功)")return Trueexcept Exception:continuereturn Falsedef save_storage(context: BrowserContext):try:context.storage_state(path=str(STORAGE_STATE))log(f"已保存登录态:{STORAGE_STATE}")except Exception as e:log(f"保存登录态失败:{e}")def main():username, password = get_env_credential()headless = os.getenv("HEADLESS", "true").strip().lower() not in ["0", "false", "off"]with sync_playwright() as pw:ctx = new_context(pw, headless=headless)page = ctx.new_page()ok = try_login(page, username, password)if not ok:log("登录未成功,退出。你可以设置 HEADLESS=false 先手动登录一次。")ctx.close()returnsave_storage(ctx)# 执行签到sign_ok = try_sign_in(page)if sign_ok:log("流程结束:签到完成 🎉")else:log("流程结束:签到失败(请结合截图排查)")ctx.close()if __name__ == "__main__":main()
自动登录百姓生活论坛(3dfsh.com)
自动复用历史登录态(
storage_state_3dfsh.json),减少重复登录自动执行"签到/打卡"
优先尝试 Discuz 常见签到插件入口
如果入口变化,则扫描页面中包含"签到"的链接再尝试
结果截图落地(
screenshots_3dfsh/),便于定位"验证码/风控/入口变更"
主要方法
get_env_credential→ 从环境变量读取账号密码(FSH_USERNAME/FSH_PASSWORD),缺失则直接报错提示配置方式new_context→ 创建 Playwright 浏览器上下文;如果有storage_state_3dfsh.json就加载以复用登录态is_logged_in→ 通过页面关键字(退出/注销/设置/消息等)粗略判断当前是否已登录try_login→ 自动完成登录流程:优先点击"登录",不行则直达 Discuz 登录页,填表提交并判断是否登录成功collect_possible_signin_urls→ 生成常见 Discuz 签到插件的候选入口 URL 列表find_signin_link_in_page→ 在页面 HTML 中扫描包含"签到"的链接并返回第一个可用入口try_sign_in→ 统一签到流程:依次尝试候选入口、页面扫描入口,并调用点击逻辑完成签到click_sign_button→ 在签到页面寻找并点击"签到/打卡/立即签到"等按钮,检测"签到成功/已签到"等关键词save_storage→ 将当前登录态保存到storage_state_3dfsh.json,下次运行可免登录main→ 主流程入口:读取配置 → 启动浏览器 → 登录 → 保存登录态 → 签到 → 输出结果并退出
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论