侧边栏壁纸
博主头像
天马行空 博主等级

凡是过往,皆为序章

  • 累计撰写 632 篇文章
  • 累计创建 11 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
pt

天空标注种子是否在Free期内

sortie
2026-04-20 / 0 评论 / 0 点赞 / 9 阅读 / 0 字

脚本

主要功能:

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.py

4.根据过期时间设置种子免费状态

nano check_expired_torrents.py
import 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()

💡 如何完美使用这两个脚本?

你现在拥有了两个非常强大的自动化脚本:

  1. 获取脚本 (set...py):负责去 HDSky 抓免费种子,并给 qB 里的种子改名加时间、打上“免费”标签。

  2. 清理脚本 (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

pt
博主关闭了所有页面的评论