1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/55RK8C若失效,可用地址
阿里云:
服务器购买地址
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 timeimport jsonimport tracebackfrom dataclasses import dataclassfrom typing import Optional, List, Tupleimport requestsfrom lxml import etreefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECtry:# 自动管理 chromedriverfrom webdriver_manager.chrome import ChromeDriverManagerfrom selenium.webdriver.chrome.service import ServiceUSE_WDM = Trueexcept Exception:USE_WDM = FalseBASE_URL = "https://bbs.wangjing.cn/"LOGIN_URL_CANDIDATES = ["member.php?mod=logging&action=login","member.php?mod=logging&action=login&mobile=2","member.php?mod=logging&action=login&infloat=yes&handlekey=login",]# 常见Discuz签到插件入口(不保证存在,但值得尝试)SIGN_URL_CANDIDATES = ["plugin.php?id=dsu_paulsign:sign","plugin.php?id=dsu_paulsign:sign&operation=qiandao","plugin.php?id=k_misign:sign","plugin.php?id=qiandao","home.php?mod=task", # 有些论坛签到是任务体系]KEYWORDS = ["签到", "每日签到", "打卡", "签 到", "Check in", "checkin", "sign"]class SignResult:ok: booltitle: strdetail: strraw_url: str = ""def env(name: str, default: str = "") -> str:v = os.getenv(name)return v.strip() if v is not None else defaultdef pushplus_send(token: str, title: str, content: str) -> None:if not token:returntry:requests.get("https://www.pushplus.plus/send",params={"token": token, "title": title, "content": content, "template": "txt"},timeout=10,)except Exception:passclass WangJingBbsSigner:def __init__(self, username: str, password: str, headless: bool = True, timeout: int = 20):self.username = usernameself.password = passwordself.headless = headlessself.timeout = timeoutself.driver = self._build_driver()self.wait = WebDriverWait(self.driver, self.timeout)# ------------------ 核心:浏览器初始化 ------------------def _build_driver(self) -> webdriver.Chrome:chrome_options = Options()if self.headless:chrome_options.add_argument("--headless=new")chrome_options.add_argument("--no-sandbox")chrome_options.add_argument("--disable-dev-shm-usage")chrome_options.add_argument("--disable-gpu")chrome_options.add_argument("--window-size=1280,900")chrome_options.add_argument("--lang=zh-CN")chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")if USE_WDM:service = Service(ChromeDriverManager().install())return webdriver.Chrome(service=service, options=chrome_options)return webdriver.Chrome(options=chrome_options)# ------------------ 工具:安全打开页面 ------------------def open(self, url: str) -> None:if not url.startswith("http"):url = BASE_URL.rstrip("/") + "/" + url.lstrip("/")self.driver.get(url)# ------------------ 工具:判断是否登录 ------------------def is_logged_in(self) -> bool:"""Discuz 常见特征:- 页面里存在"退出/退出登录"- 或存在 "欢迎您回来"/用户头像等"""html = self.driver.page_sourceif re.search(r"(退出|退出登录|注销|Log\s*out)", html, re.I):return True# 一些站点会有"设置""消息"等if re.search(r"(设置|我的|消息|提醒)", html):return Truereturn False# ------------------ 主要方法:登录 ------------------def login(self) -> bool:"""多入口尝试打开登录页 -> 填入账号密码 -> 提交 -> 验证是否登录成功"""# 先打开首页self.open(BASE_URL)time.sleep(2)# 如果已登录,直接返回if self.is_logged_in():return True# 依次尝试登录页last_err = ""for path in LOGIN_URL_CANDIDATES:try:self.open(path)time.sleep(2)# Discuz 常见输入框:username / password 或者表单 placeholderuser_input = self._find_any_input([(By.NAME, "username"),(By.ID, "username"),(By.CSS_SELECTOR, "input[name='username']"),(By.XPATH, "//input[contains(@placeholder,'用户名') or contains(@placeholder,'账号') or contains(@placeholder,'邮箱') or contains(@placeholder,'手机')]"),])pwd_input = self._find_any_input([(By.NAME, "password"),(By.ID, "password"),(By.CSS_SELECTOR, "input[type='password']"),(By.XPATH, "//input[@type='password']"),])if not user_input or not pwd_input:continueuser_input.clear()user_input.send_keys(self.username)pwd_input.clear()pwd_input.send_keys(self.password)# 提交按钮:常见是 <button type=submit> 或 "登录"submit_btn = self._find_any_clickable([(By.CSS_SELECTOR, "button[type='submit']"),(By.CSS_SELECTOR, "input[type='submit']"),(By.XPATH, "//*[self::button or self::a or self::span][contains(.,'登录') or contains(.,'登 录')]"),])if submit_btn:submit_btn.click()else:# 兜底:回车提交pwd_input.submit()time.sleep(3)# 登录后通常跳回首页或弹窗关闭,做一次首页确认self.open(BASE_URL)time.sleep(2)if self.is_logged_in():return Trueexcept Exception as e:last_err = str(e)continueprint(f"❌ 登录失败: {last_err}")return Falsedef _find_any_input(self, locators):for by, value in locators:try:ele = self.wait.until(EC.presence_of_element_located((by, value)))if ele:return eleexcept Exception:continuereturn Nonedef _find_any_clickable(self, locators):for by, value in locators:try:ele = self.wait.until(EC.element_to_be_clickable((by, value)))if ele:return eleexcept Exception:continuereturn None# ------------------ 主要方法:发现签到入口 ------------------def discover_sign_url(self) -> Optional[str]:"""1) 直接尝试常见签到插件URL(成功则返回)2) 首页/个人中心查找"签到/打卡"等链接3) 从页面源码提取疑似插件链接"""# 1) 常见插件URL试探for p in SIGN_URL_CANDIDATES:url = BASE_URL.rstrip("/") + "/" + pif self._url_looks_like_sign_page(url):return url# 2) 在首页找"签到"入口self.open(BASE_URL)time.sleep(2)url = self._scan_page_for_sign_link()if url:return url# 3) 在"任务中心/用户中心"等页面再找for extra in ["home.php?mod=space", "home.php?mod=task", "forum.php"]:try:self.open(extra)time.sleep(2)url = self._scan_page_for_sign_link()if url:return urlexcept Exception:passreturn Nonedef _url_looks_like_sign_page(self, url: str) -> bool:try:self.open(url)time.sleep(2)html = self.driver.page_source# 粗略判定:页面含"签到/已签到/签到成功/签到说明"等if any(k in html for k in ["签到", "已签到", "签到成功", "今日已签到", "补签"]):return Trueexcept Exception:passreturn Falsedef _scan_page_for_sign_link(self) -> Optional[str]:html = self.driver.page_sourcedoc = etree.HTML(html)if doc is None:return None# 先找 a 标签文本包含关键词for kw in KEYWORDS:nodes = doc.xpath(f"//a[contains(normalize-space(.), '{kw}')]/@href")if nodes:return self._abs_url(nodes[0])# 再找 href 中包含 sign/qiandao/dsu_paulsign 等nodes = doc.xpath("//a/@href")for href in nodes or []:if not href:continueif re.search(r"(dsu_paulsign|qiandao|checkin|sign|mission|task)", href, re.I):return self._abs_url(href)return Nonedef _abs_url(self, href: str) -> str:href = href.strip()if href.startswith("http"):return hrefif href.startswith("//"):return "https:" + hrefreturn BASE_URL.rstrip("/") + "/" + href.lstrip("/")# ------------------ 主要方法:执行签到 ------------------def do_sign(self, sign_url: str) -> SignResult:"""打开签到页 -> 尝试点击"签到/立即签到/打卡"按钮 -> 解析结果"""self.open(sign_url)time.sleep(2)# 1) 常见按钮点击(尽量宽松匹配)clicked = Falsebtn = self._find_any_clickable([(By.XPATH, "//*[self::a or self::button or self::span][contains(.,'签到') or contains(.,'打卡') or contains(.,'立即签到')]"),(By.CSS_SELECTOR, "button"),(By.CSS_SELECTOR, "a"),])try:if btn:# 防止点到"签到说明"等非触发按钮:简单过滤txt = (btn.text or "").strip()if any(x in txt for x in ["签到", "打卡", "立即"]):btn.click()clicked = Truetime.sleep(3)except Exception:pass# 2) 结果解析:成功/已签到/失败html = self.driver.page_sourcetitle, detail, ok = self._parse_sign_result(html, clicked, sign_url)return SignResult(ok=ok, title=title, detail=detail, raw_url=sign_url)def _parse_sign_result(self, html_text: str, clicked: bool, url: str) -> Tuple[str, str, bool]:# 常见成功/重复签到提示if re.search(r"(签到成功|打卡成功|已签到|今日已签到|已经签到)", html_text):# 尝试抓一些奖励文案award = self._extract_award_text(html_text)if re.search(r"(已签到|今日已签到|已经签到)", html_text) and not re.search(r"(成功)", html_text):return "今日已签到", award or "页面提示:已签到/无需重复签到", Truereturn "签到成功", award or "页面提示:签到成功", True# 若点了按钮但没匹配到成功字样,可能站点返回提示在弹窗/JS里if clicked:return "可能已触发签到", "已点击签到按钮,但未在页面中识别到明确成功文案,可手动确认一次。", Truereturn "签到失败/未找到入口", f"未识别到签到成功文案,或未找到可点击的签到按钮。URL={url}", Falsedef _extract_award_text(self, html_text: str) -> str:# 尝试提取"积分/金币/威望/红包"等关键信息附近文字patterns = [r"(积分[^<\n]{0,40})",r"(金币[^<\n]{0,40})",r"(威望[^<\n]{0,40})",r"(红包[^<\n]{0,40})",r"(奖励[^<\n]{0,60})",]for p in patterns:m = re.search(p, html_text)if m:return m.group(1)return ""# ------------------ 主流程 ------------------def run(self) -> SignResult:try:if not self.login():return SignResult(False, "登录失败", "账号密码错误/站点登录结构变化/需要验证码", BASE_URL)sign_url = self.discover_sign_url()if not sign_url:return SignResult(False, "未找到签到入口", "站点签到入口可能隐藏在个人中心/任务中心,或需要手动指定签到URL", BASE_URL)return self.do_sign(sign_url)except Exception as e:return SignResult(False, "程序异常", f"{e}\n\n{traceback.format_exc()}", BASE_URL)finally:try:self.driver.quit()except Exception:passdef main():username = env("WJ_USERNAME")password = env("WJ_PASSWORD")headless = env("WJ_HEADLESS", "true").lower() not in ["0", "false", "off"]timeout = int(env("WJ_TIMEOUT", "20"))push_token = env("PUSHPLUS_TOKEN", "")if not username or not password:print("❌ 缺少环境变量 WJ_USERNAME / WJ_PASSWORD")print("✅ 示例:")print(" export WJ_USERNAME='你的账号'")print(" export WJ_PASSWORD='你的密码'")returnsigner = WangJingBbsSigner(username, password, headless=headless, timeout=timeout)result = signer.run()print("=" * 60)print(f"结果:{result.title}")print(f"详情:{result.detail}")print(f"入口:{result.raw_url}")print("=" * 60)# 推送if push_token:pushplus_send(push_token, f"望京网签到 - {result.title}", f"{result.detail}\n\n入口:{result.raw_url}")if __name__ == "__main__":main()
该脚本为望京网自动签到脚本,主要作用包括:
自动登录望京网(兼容 Discuz 常见登录页结构)
自动发现签到入口(多策略:常见插件 URL → 页面按钮文本搜索 → 源码链接关键字提取)
自动执行签到(点击"签到/打卡/立即签到"等按钮)
自动判断结果(识别"签到成功/已签到"等关键字,并尝试提取奖励信息)
可选推送通知(PushPlus)
主要方法
login()
作用:完成登录并验证是否成功。
做法:
打开首页,若已登录则跳过
依次尝试多个常见登录 URL
自动定位账号/密码输入框与"登录"按钮
提交后回到首页检查是否出现"退出/设置/消息"等已登录特征
discover_sign_url()
作用:找到"签到页面/签到入口"的 URL。
做法(按优先级):
直接访问候选插件地址:
plugin.php?id=dsu_paulsign:sign等,判断页面是否像签到页在首页、任务页、个人页扫描 a 标签文本(含"签到/打卡/每日签到"等)
从页面源码的所有链接中提取包含
sign/qiandao/dsu_paulsign/task/mission的 href
do_sign(sign_url)
作用:进入签到页并执行一次签到。
做法:
打开签到页
尝试点击包含"签到/打卡/立即签到"的元素(a/button/span)
等待页面刷新/变化
调用
_parse_sign_result()分析签到结果
_parse_sign_result(html_text, clicked, url)
作用:把页面内容解析成结构化结果(成功/重复/失败)。
规则:
有"签到成功/打卡成功/已签到/今日已签到" → 认为成功(或重复签到)
点击过按钮但没找到关键字 → 返回"可能已触发签到"(提示你人工确认一次)
否则 → 认为失败并给出原因
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论