大家好! ![]()
今天给大家分享一个我编写的 Python 脚本,它可以自动化处理 Freecloud 网站(https://freecloud.ltd/)的日常任务,包括 自动登录、检查订阅剩余天数、在即将到期时自动续费,并将结果通过 Telegram Bot 推送给你。
如果你有多个 Freecloud 账号需要管理,或者只是想“一劳永逸”
,这个脚本可以为你节省大量时间和精力。
功能特点
多账号支持:轻松管理多个 Freecloud 账号。
自动登录:脚本会自动处理登录过程,包括填写用户名、密码和计算验证码。
智能Cloudflare验证:自动检测并尝试通过 Cloudflare 的“真人验证”步骤。
账户信息提取:登录后自动导航到服务器页面,提取订阅到期时间和剩余天数。
自动续费:当检测到订阅剩余天数少于或等于3天时,脚本会自动尝试进行续费。
Telegram实时通知:无论是成功续费、续费失败,还是普通的账户状态检查,都会通过 Telegram Bot 发送格式清晰的通知。
错误处理与快照:在遇到登录失败或元素找不到等问题时,会自动保存当前页面的快照(HTML文件),方便你进行调试。
会话保持:通过连接到本地已打开的浏览器实例(端口 9222),可以避免重复扫描二维码登录,并保持登录状态。
准备工作
在运行脚本之前,你需要进行一些准备工作。
1. 安装必要的 Python 库
本脚本依赖 DrissionPage 和 requests 库。打开你的终端或命令行工具,运行以下命令来安装它们:
pip install DrissionPage requests
2. 配置浏览器 (重要!)
为了让脚本能够接管你的浏览器,你需要以“调试模式”启动一个浏览器实例(推荐使用 Chrome 或 Edge)。
Windows 用户:
找到你的 Chrome 浏览器快捷方式,右键点击“属性”,在“目标”字段的末尾添加以下参数(注意在exe路径后先加一个空格):
--remote-debugging-port=9222
修改后的目标路径看起来像这样:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
macOS 用户:
打开“终端”并运行以下命令:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
运行后,会打开一个新的浏览器窗口。请不要关闭这个窗口,后续的自动化操作将在这个窗口中执行。
3. 配置 Telegram Bot
你需要一个属于自己的 Telegram Bot 来接收通知。
- 在 Telegram 中搜索
BotFather。 - 发送
/newbot命令创建一个新的机器人,你将获得一个 Token。 - 搜索你刚刚创建的机器人的名字,并给它发送一条消息。
- 你需要获取你的 Chat ID。最简单的方法是把你收到的机器人 Token 填入以下 URL,并在浏览器中打开它:
https://api.telegram.org/bot<你的TOKEN>/getUpdates
在返回的 JSON 数据中,找到"chat":{"id": <你的CHAT_ID>}。
代码详解
这是脚本的核心逻辑分解。
- Telegram 通知函数 (
send_telegram_message): 这个函数负责将格式化的消息发送到你指定的 Telegram 聊天。 - 登出函数 (
logout_if_needed): 在每次处理新账号之前,脚本会检查当前是否已登录。如果检测到登录状态(通过寻找“退出”按钮),它会先执行登出操作,确保一个干净的登录环境。 - 核心处理函数 (
login_and_process): 这是脚本最关键的部分:- Cloudflare 验证:循环检测页面上是否有“确认您是真人”的按钮,如果有就点击,直到获取到
cf_clearancecookie 为止。 - 表单填写:自动输入用户名和密码。
- 验证码计算:脚本会自动从页面提取如
(5 + 3)这样的数学表达式,并计算出结果填入验证码输入框。 - 信息提取:登录成功后,导航到服务器列表页面,并精确地提取出包含到期时间和剩余天数的信息。
- 续费逻辑:检查剩余天数。如果小于等于3天,它会自动寻找并点击“续费”和“确认续费”按钮。
- 发送通知:根据操作结果(常规检查、续费成功、续费失败),构建不同内容的消息并发送。
- Cloudflare 验证:循环检测页面上是否有“确认您是真人”的按钮,如果有就点击,直到获取到
- 主函数 (
main):- 连接浏览器:使用
ChromiumOptions().set_local_port(9222)来接管你之前在调试模式下打开的浏览器。 - 账号列表:在这里你需要填入你的 Freecloud 用户名和密码。
- 循环处理:遍历账号列表,对每个账号依次执行登出检查和登录处理流程。
- 连接浏览器:使用
如何使用
-
将下面的完整代码保存为一个 Python 文件,例如
login.py。 -
修改配置:
- 在脚本中找到
TELEGRAM_BOT_TOKEN和TELEGRAM_CHAT_ID,替换成你在准备工作中获取到的值。 - 找到
accounts列表,将示例中的用户名和密码修改为你自己的 Freecloud 账号信息。你可以添加任意多个账号。
# Telegram Bot Configuration TELEGRAM_BOT_TOKEN = '你的_BOT_TOKEN' TELEGRAM_CHAT_ID = '你的_CHAT_ID' # ... accounts = [ {'username': '你的用户名1', 'password': '你的密码1'}, {'username': '你的用户名2', 'password': '你的密码2'} ] - 在脚本中找到
-
运行脚本:
- 确保你已经在调试模式下打开了一个浏览器窗口。
- 打开终端,进入到你保存脚本的目录,运行脚本:
python login.py
现在,脚本就会自动开始执行所有操作了。你可以在终端看到实时的日志输出,同时你的 Telegram 也会收到相应的通知。
完整代码
点击展开/折叠代码
from DrissionPage import Chromium, ChromiumOptions
from DrissionPage.errors import ElementNotFoundError
import re
import platform
import time
import requests
from datetime import datetime
# Telegram Bot Configuration
# 在这里替换为你自己的 Bot Token 和 Chat ID
TELEGRAM_BOT_TOKEN = '你的_BOT_TOKEN'
TELEGRAM_CHAT_ID = '你的_CHAT_ID'
def send_telegram_message(message):
"""Sends a message to a Telegram user or group."""
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
payload = {
'chat_id': TELEGRAM_CHAT_ID,
'text': message,
'parse_mode': 'Markdown'
}
try:
response = requests.post(url, json=payload)
response.raise_for_status()
print("Telegram message sent successfully!")
except requests.exceptions.RequestException as e:
print(f"Failed to send Telegram message: {e}")
def logout_if_needed(tab):
"""Navigates to the login page and logs out if a session is active."""
print("导航至登录页面并检查/执行登出操作...")
tab.get('https://freecloud.ltd/login')
tab.wait.load_start() # Wait for the page to load or redirect
try:
# Check if the logout button exists. If it does, we are logged in.
tab.ele('xpath:/html/body/header/div/div[1]/a[2]', timeout=5)
print("检测到已登录,正在执行退出操作...")
# Navigate to the homepage first before logging out.
try:
homepage_button = tab.ele('xpath://*[@id="header"]/ul[2]/li[1]/a')
homepage_button.click()
tab.wait.load_start()
time.sleep(2) # Add delay
print("已返回官网。")
except ElementNotFoundError:
print("无法找到官网按钮,将直接尝试退出。")
# Re-find and click the logout button.
tab.ele('xpath:/html/body/header/div/div[1]/a[2]', timeout=10).click()
print("已退出登录。")
tab.wait.load_start()
time.sleep(2) # Add delay
# After logging out, explicitly navigate back to the login page for a clean start.
tab.get('https://freecloud.ltd/login')
print("已返回登录页面。")
except ElementNotFoundError:
# If the logout button is not found, it means we are already logged out.
print("未检测到登录状态,可以安全地继续登录。")
# The page should be the login page at this point.
pass
def login_and_process(tab, username, password):
"""Logs in with the given credentials and processes the account information."""
try:
# --- 填写登录表单 ---
print(f"--- 开始处理账号: {username} ---")
# --- 处理Cloudflare验证 ---
print("正在检查Cloudflare验证...")
value = '确认您是真人' if platform.system().lower() == 'windows' else 'Verify you are human'
for i in range(20):
if tab.ele(f'x://input[@value="{value}"]', timeout=3):
print(f"第 {i+1} 次尝试, 点击 '确认您是真人'...")
tab.ele(f'x://input[@value="{value}"]').click()
tab.wait(2)
cookies = tab.cookies()
if any(cookie['name'] == 'cf_clearance' for cookie in cookies):
print("成功获取 cf_clearance cookie。")
break
else:
print("无法获取 cf_clearance cookie,继续尝试登录。")
print("正在填写登录表单...")
# 清除并输入用户名
user_input = tab.ele('xpath://*[@id="PASS"]/div[1]/div/input', timeout=60)
user_input.clear()
user_input.input(username)
# 清除并输入密码
pass_input = tab.ele('xpath://*[@id="PASS"]/div[2]/div/input')
pass_input.clear()
pass_input.input(password)
# 提取并计算验证码
captcha_ele = tab.ele('xpath=//*[@id="content"]/div/div/section/form/div[1]/div[2]/div/input')
captcha_text = captcha_ele.attr('placeholder')
match_captcha = re.search(r'(\d+\s*[\+\-\*]\s*\d+)', captcha_text)
if not match_captcha:
raise ValueError("无法从文本中解析出验证码。")
math_expr = match_captcha.group(1)
captcha_result = eval(math_expr)
captcha_ele.input(str(captcha_result))
# 点击登录按钮
print("正在点击登录按钮...")
tab.ele('xpath://*[@id="content"]/div/div/section/form/div[3]/button').click()
time.sleep(2) # Add delay
print("登录成功!")
# --- 选择服务器并提取信息 ---
print("正在选择服务器...")
tab.ele('xpath://*[@id="nav"]/ul/li[3]/a/span', timeout=20).click()
print("正在提取信息...")
tab.wait.load_start()
info_element = tab.ele('xpath:/html/body/section/section/section/form[2]/div/div/table/tbody/tr/td[9]', timeout=20)
info_text = info_element.text
print(f"提取的信息: {info_text}")
# Parse the expiry date and remaining days
match = re.search(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s*((\d+)天后)', info_text)
if match:
expiry_date_str = match.group(1)
remaining_days = int(match.group(2))
# Format the base message
message = f"*Freecloud Account: {username}*\n\n" \
f"Expiry Date: `{expiry_date_str}`\n" \
f"Remaining Days: `{remaining_days}`"
# Check for renewal condition
if remaining_days <= 3:
try:
print("Subscription expiring soon. Attempting to auto-renew...")
# 查找并点击续订按钮
table_row = info_element.parent()
renew_button = table_row.ele('text:续费', timeout=10)
renew_button.click()
print("已点击 '续费' 按钮。")
# 等待并点击确认续订按钮
print("正在等待续订确认框...")
submit_button = tab.ele('#submitRenew', timeout=20)
submit_button.click()
print("已点击 '点击续费' 按钮。")
time.sleep(3) # 等待操作完成
# If successful, create a success message
message = f"*Freecloud Auto-Renewal SUCCESS: {username}*\n\n" \
f"Successfully renewed subscription.\n\n" \
f"Previous Expiry Date: `{expiry_date_str}`\n" \
f"Previous Remaining Days: `{remaining_days}`"
except ElementNotFoundError as e:
print(f"Auto-renewal failed for {username}: {e}")
# If renewal fails, append a warning to the original message
message += f"\n\n*警告: 账号 {username} 的订阅即将过期。*"
message += f"\n*自动续订尝试失败*: {e}"
send_telegram_message(message)
else:
error_message = f"Could not parse expiry date and remaining days for {username} from the extracted text: `{info_text}`"
print(error_message)
send_telegram_message(error_message)
print(f"--- 账号: {username} 处理完毕 ---")
except ElementNotFoundError as e:
print(f"错误 (账号: {username}): {e}")
print("可能原因:Cloudflare 验证失败、网络超时或页面结构已更改。")
snapshot_path = f'login_snapshot_{username}.html'
tab.save(path=snapshot_path)
print(f"已保存页面快照至 {snapshot_path}")
print(f"当前页面 URL: {tab.url}")
def main():
"""Main function to run the automation for all accounts."""
# 启动或接管浏览器
co = ChromiumOptions().set_local_port(9222)
page = Chromium(co)
tab = page.get_tab()
# 在这里替换为你自己的账号信息
accounts = [
{'username': '你的用户名1', 'password': '你的密码1'},
{'username': '你的用户名2', 'password': '你的密码2'}
]
for account in accounts:
logout_if_needed(tab)
login_and_process(tab, account['username'], account['password'])
print("所有账号处理完毕。")
page.quit()
print("浏览器已关闭。")
if __name__ == "__main__":
main()
注意事项
- 页面结构变化:如果 Freecloud 网站的页面结构(HTML)发生改变,可能会导致脚本中的元素选择器(如 XPath)失效。届时需要根据新的页面结构更新代码。如果脚本出错,检查保存的
login_snapshot_...html文件会很有帮助。 - 网络问题:不稳定的网络可能会导致超时错误。脚本内置了一些等待时间,但你可能需要根据自己的网络情况进行调整。
- 安全性:请妥善保管你的
TELEGRAM_BOT_TOKEN和 Freecloud 账号密码,不要泄露给他人。
希望这个脚本能对大家有所帮助!如果你有任何问题或建议,欢迎在下方留言讨论。 ![]()