脚本
主要功能:
1.获取页面中的免费种子,在qb列表中对免费种子进行匹配,并进行如下操作:1.免费种子名称最后加上过期时间;2.增加”免费“标签。
2.当种子超过过期时间10分钟,将标签改为”收费“。
1. 准备 Debian 12 环境
sudo apt update
sudo apt install python3 python3-requests python3-bs4
sudo apt install python3-pip -y
pip install qbittorrent-api --break-system-packages
nano set_free_torrents.py
2.QB中在免费期内的种子名称修改为:原名称+过期时间;种子标签修改为:免费
import requests
from bs4 import BeautifulSoup
import re
from datetime import datetime, timedelta
import qbittorrentapi
# ================= 1. 配置区域 =================
HDSKY_COOKIE = 'YOUR_COOKIE_HERE'
HDSKY_URL = 'https://hdsky.me/torrents.php'
QB_HOST = "http://31.xx.xx.xx:8080/"
QB_USER = "user"
QB_PASS = "password"
TARGET_TRACKER = "hdsky.me"
# ============================================
HEADERS = {
'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',
'Cookie': HDSKY_COOKIE
}
def calculate_expiry(start_time_str, alive_time_str, time_left_str):
"""计算优惠到期时间"""
if start_time_str == "未知" or "永久" in time_left_str or "未知" in time_left_str:
return "永久免费"
try:
dt_start = datetime.strptime(start_time_str, "%Y-%m-%d %H:%M:%S")
def parse_duration(t_str):
nums = re.findall(r'(\d+)', t_str)
units = re.findall(r'([年月日周天时分秒])', t_str)
d, h, m, s = 0, 0, 0, 0
for n, u in zip(nums, units):
n = int(n)
if u == '天': d = n
elif u == '周': d = n * 7
elif u == '月': d = n * 30
elif u == '年': d = n * 365
elif u == '时': h = n
elif u == '分': m = n
elif u == '秒': s = n
return timedelta(days=d, hours=h, minutes=m, seconds=s)
td_alive = parse_duration(alive_time_str)
td_left = parse_duration(time_left_str)
dt_expiry = dt_start + td_alive + td_left
return dt_expiry.strftime("%Y-%m-%d %H:%M")
except:
return "计算错误"
def fetch_free_torrents():
"""从 HDSky 抓取免费种子信息"""
print("正在连接 HDSky 抓取免费种子列表...")
free_dict = {}
try:
res = requests.get(HDSKY_URL, headers=HEADERS, timeout=15)
if 'login.php' in res.url:
print("❌ 错误:HDSky Cookie 失效,请更新配置!")
return {}
soup = BeautifulSoup(res.text, 'html.parser')
rows = soup.select('table.torrents tr')[1:]
processed_ids = set()
for row in rows:
row_html = str(row)
title_elem = row.select_one('a[href^="details.php?id="]')
if not title_elem: continue
tid = re.search(r'id=(\d+)', title_elem['href']).group(1)
if tid in processed_ids: continue
if row.select_one('.pro_free, .pro_free2up, .free') or "免费" in row_html:
processed_ids.add(tid)
row_text = row.get_text(separator=' ', strip=True)
time_left = "未知"
m = re.search(r'优惠剩余时间[::]\s*([^\]\)]+)', row_text)
if m: time_left = m.group(1).strip()
alive_time, start_time = "未知", "未知"
tds = row.find_all('td', recursive=False)
if len(tds) >= 4:
alive_time = tds[3].get_text(strip=True)
span = tds[3].find('span')
if span and span.has_attr('title'): start_time = span['title']
expiry = calculate_expiry(start_time, alive_time, time_left)
free_dict[tid] = expiry
print(f"✅ 抓取到免费 ID: {tid} | 到期: {expiry}")
print(f"抓取完毕,共找到 {len(free_dict)} 个免费种子。\n")
return free_dict
except Exception as e:
print(f"抓取发生错误: {e}")
return {}
def main():
free_torrents = fetch_free_torrents()
if not free_torrents: return
print(f"正在连接 qBittorrent: {QB_HOST}")
qbt_client = qbittorrentapi.Client(host=QB_HOST, username=QB_USER, password=QB_PASS)
try:
qbt_client.auth_log_in()
except Exception as e:
print(f"连接 qBittorrent 失败: {e}")
return
torrents = qbt_client.torrents_info()
processed_count = 0
for torrent in torrents:
if TARGET_TRACKER in torrent.get('tracker', '') or TARGET_TRACKER in torrent.get('comment', ''):
props = qbt_client.torrents_properties(torrent_hash=torrent.hash)
comment_value = props.get('comment', '')
match = re.search(r'id=(\d+)', comment_value)
if match:
qb_id = match.group(1)
if qb_id in free_torrents:
expiry_time = free_torrents[qb_id]
if expiry_time != "计算错误":
# ================= 功能 1: 添加“免费”标签 =================
new_tag = "免费"
skip_tag = "收费"
# 获取当前种子拥有的所有标签
current_tags = [t.strip() for t in torrent.get('tags', '').split(',')] if torrent.get('tags') else []
# 判断逻辑:如果不包含“收费”且不包含“免费”,才进行打标
if skip_tag in current_tags:
pass # 静默跳过,不做处理
elif new_tag not in current_tags:
torrent.add_tags(tags=new_tag)
print(f"🏷️ 成功打标: (ID:{qb_id}) -> {new_tag}")
# ================= 功能 2: 修改名称 =================
current_name = torrent.name
# 避免多次运行脚本造成重复叠加(如:电影名称 到期:XX 到期:XX)
if "到期:" not in current_name:
# 格式化名称,加个方括号视觉上更整齐
new_name = f"{current_name} [到期:{expiry_time}]"
try:
qbt_client.torrents_rename(torrent_hash=torrent.hash, name=new_name)
print(f"✏️ 成功重命名: [{new_name}]")
processed_count += 1
except Exception as e:
print(f"⚠️ 重命名失败 [{current_name}]: {e}")
else:
# 如果名字里已经包含了“到期:”,说明之前处理过了,静默跳过
pass
print(f"\n🎉 处理完成!本次为 {processed_count} 个种子更新了名称和标签。")
if __name__ == "__main__":
main()把“到期时间”直接放在种子的名称上,看列表时一目了然,而标签统一使用“免费”进行分类,管理起来确实更清晰。
在 qBittorrent 中,修改种子名称可以通过 API 的 torrents_rename 方法实现。为了防止脚本多次运行导致种子名称被无限叠加(例如:电影 到期:XX 到期:XX),我在代码里加入了一个防呆判断:如果种子的名字里已经包含了“到期”,就会自动跳过重命名。
💡 补充提示:关于 qBittorrent 重命名机制
qBittorrent 的 API 重命名方法(torrents_rename)通常只会修改你在客户端 UI 列表中看到的“任务名称”,而不会去改动你硬盘上已经下载好的实际文件夹或文件名称。这对 PT 做种来说是非常安全的,既方便了你在客户端里分辨过期时间,又不用担心破坏文件的做种状态。
3. 运行与测试
chmod +x set_free_torrents.py
python3 set_free_torrents.py4.根据过期时间设置种子免费状态
nano check_expired_torrents.pyimport re
from datetime import datetime, timedelta, timezone
import qbittorrentapi
# ================= 1. 配置区域 =================
QB_HOST = "http://*.*.*.*:8080/"
QB_USER = "user"
QB_PASS = "password"
TARGET_TRACKER = "hdsky.me"
TARGET_TAG_OLD = "免费"
TARGET_TAG_NEW = "收费"
# 设置缓冲时间(分钟):过期多少分钟后执行操作
BUFFER_MINUTES = 10
# 是否在过期时自动暂停该种子?(强烈建议开启,防扣流量)
# AUTO_PAUSE = True
AUTO_PAUSE = False
# ============================================
def get_beijing_time():
"""获取标准的北京时间 (UTC+8),避免服务器本地时区设置错误导致误判"""
utc_time = datetime.utcnow()
bj_time = utc_time + timedelta(hours=8)
return bj_time
def main():
print(f"正在连接 qBittorrent: {QB_HOST}")
qbt_client = qbittorrentapi.Client(host=QB_HOST, username=QB_USER, password=QB_PASS)
try:
qbt_client.auth_log_in()
print("✅ 登录成功!")
except Exception as e:
print(f"❌ 连接 qBittorrent 失败: {e}")
return
# 直接利用 qB API 的特性,只拉取带有“免费”标签的种子,极大地提升运行速度
print(f"🔍 正在检索带有 [{TARGET_TAG_OLD}] 标签的种子...")
torrents = qbt_client.torrents_info(tag=TARGET_TAG_OLD)
if not torrents:
print("当前没有任何免费种子需要检查。")
return
now_bj = get_beijing_time()
print(f"🕒 当前北京时间: {now_bj.strftime('%Y-%m-%d %H:%M:%S')}")
processed_count = 0
for torrent in torrents:
# 1. 确认是不是 HDSky 的种子
is_hdsky = TARGET_TRACKER in torrent.get('tracker', '') or TARGET_TRACKER in torrent.get('comment', '')
if not is_hdsky:
try:
# 深度检查 tracker 列表
for trk in torrent.trackers:
if TARGET_TRACKER in trk.url:
is_hdsky = True
break
except Exception:
pass
if not is_hdsky:
continue
# 2. 解析种子名称中的到期时间
# 正则匹配形如:[到期:2026-04-19 21:05]
match = re.search(r'\[到期:(\d{4}-\d{2}-\d{2} \d{2}:\d{2})\]', torrent.name)
if match:
expiry_str = match.group(1)
try:
# 将字符串转换为 datetime 对象
expiry_time = datetime.strptime(expiry_str, "%Y-%m-%d %H:%M")
# 计算“实际判定过期的时间点”(到期时间 + 10分钟)
action_time = expiry_time + timedelta(minutes=BUFFER_MINUTES)
# 3. 对比时间
if now_bj >= action_time:
print("-" * 50)
print(f"🚨 发现已过期种子: {torrent.name}")
print(f" 到期时间: {expiry_str} | 已超过缓冲期 ({BUFFER_MINUTES}分钟)")
# 移除旧标签,添加新标签
torrent.remove_tags(tags=TARGET_TAG_OLD)
torrent.add_tags(tags=TARGET_TAG_NEW)
print(f" 🏷️ 标签已更新: [{TARGET_TAG_OLD}] -> [{TARGET_TAG_NEW}]")
# 可选:自动暂停种子以保护流量
if AUTO_PAUSE:
torrent.pause()
print(f" ⏸️ 动作: 已自动暂停该种子!")
processed_count += 1
else:
# 还没到期,打印一下剩余时间(可选,觉得啰嗦可注释掉这两行)
time_left = action_time - now_bj
# print(f"⏳ 仍在免费期内: {torrent.name} (还剩 {time_left})")
except Exception as e:
print(f"⚠️ 解析时间失败 [{torrent.name}]: {e}")
else:
print(f"⚠️ 无法在名称中找到到期时间格式: {torrent.name}")
print("=" * 50)
print(f"🎉 检查完毕!本次共处理了 {processed_count} 个已过期的免费种子。")
if __name__ == "__main__":
main()💡 如何完美使用这两个脚本?
你现在拥有了两个非常强大的自动化脚本:
获取脚本 (
set...py):负责去 HDSky 抓免费种子,并给 qB 里的种子改名加时间、打上“免费”标签。清理脚本 (
check...py,即上面这个):负责定期巡查 qB,发现过期的就改标签为“收费”并暂停。
最完美的姿势是把它们加入 Linux 的定时任务 (crontab):
在终端输入 crontab -e,添加以下两行:
# 每5分钟执行一次“获取并打标免费种子”
*/5 * * * * /usr/bin/python3 /你的实际路径/set_free_torrents.py >> /你的实际路径/set_free.log 2>&1
# 每 5 分钟执行一次“检查并处理过期种子”
*/5 * * * * /usr/bin/python3 /你的实际路径/check_expired_torrents.py >> /你的实际路径/check_expired.log 2>&1