import json import time import requests import subprocess import re import shutil import random from pathlib import Path # ================= 配置区域 ================= COOKIE_FILE = Path("./cookies.json") TARGET_SEASON_ID = 7196643 # 必须包含的关键词 MUST_KEYWORDS = [] # 必须排除的关键词 EXCLUDE_KEYWORD = "纯享" BILIUP_PATH = shutil.which("biliup") or "biliup" # =========================================== class BiliCollectionBatchTool: def __init__(self): self.load_cookies() self.session = requests.Session() self.session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://member.bilibili.com/platform/upload-manager/distribution" }) def load_cookies(self): if not COOKIE_FILE.exists(): raise FileNotFoundError(f"找不到 Cookies 文件: {COOKIE_FILE}") with open(COOKIE_FILE, "r", encoding="utf-8") as f: data = json.load(f) self.cookies = {c["name"]: c["value"] for c in data.get("cookie_info", {}).get("cookies", [])} if "cookie_info" in data else data self.csrf = self.cookies.get("bili_jct") def get_existing_bvids(self, season_id): """拉取合集内所有已存在的 BVID,确保去重 100% 准确""" print(f"📡 正在拉取合集 {season_id} 的现有视频数据...") self.session.cookies.update(self.cookies) try: # 1. 获取 Section ID list_url = "https://member.bilibili.com/x2/creative/web/seasons" res_list = self.session.get(list_url, params={"pn": 1, "ps": 50}).json() section_id = None for s in res_list.get("data", {}).get("seasons", []): if s.get("season", {}).get("id") == season_id: sections = s.get("sections", {}).get("sections", []) if sections: section_id = sections[0]["id"] break if not section_id: return None, set() # 2. 获取该小节详细列表 detail_url = "https://member.bilibili.com/x2/creative/web/season/section" res_detail = self.session.get(detail_url, params={"id": section_id}).json() existing = set() if res_detail.get("code") == 0: for ep in res_detail.get("data", {}).get("episodes", []): existing.add(ep.get("bvid")) print(f"📊 查重参考:合集内已有 {len(existing)} 个视频。") return section_id, existing except Exception as e: print(f"❌ 查重逻辑失败: {e}") return None, set() def fetch_filtered_videos(self, existing_set): """ 核心逻辑修改: 1. 包含 王海颖, 唱歌, 录播 2. 不包含 纯享 3. 不在合集 existing_set 中 """ print(f"🔍 扫描符合条件且不含“{EXCLUDE_KEYWORD}”的视频...") try: res = subprocess.run([BILIUP_PATH, "list", "--max-pages", "20"], capture_output=True, text=True, encoding='utf-8') output = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", res.stdout) to_add_bvids = [] for line in output.splitlines(): if line.startswith("BV"): parts = line.split() bvid = parts[0] title = " ".join(parts[1:]) # 判断逻辑 is_match = all(kw in title for kw in MUST_KEYWORDS) is_excluded = EXCLUDE_KEYWORD in title if is_match and not is_excluded: if bvid in existing_set: continue to_add_bvids.append(bvid) return to_add_bvids except Exception as e: print(f"❌ biliup 调用失败: {e}") return [] def get_metadata(self, bv_list): episodes = [] for bvid in bv_list: url = "https://api.bilibili.com/x/web-interface/view" try: res = self.session.get(url, params={"bvid": bvid}).json() if res["code"] == 0: d = res["data"] episodes.append({ "aid": d["aid"], "cid": d["cid"], "title": d["title"], "charging_pay": 0 }) time.sleep(0.3) except: pass return episodes def run(self): # 1. 深度查重 section_id, existing_set = self.get_existing_bvids(TARGET_SEASON_ID) if not section_id: print("❌ 无法解析合集,任务终止。") return # 2. 条件过滤 + 查重剔除 target_bvids = self.fetch_filtered_videos(existing_set) if not target_bvids: print("✨ 扫描完毕:没有符合条件的新视频。") return print(f"💡 过滤后,确认有 {len(target_bvids)} 个视频待加入合集。") # 3. 解析元数据 final_list = self.get_metadata(target_bvids) # 4. 一次性全量提交 if final_list: print(f"🚀 正在发送合并添加请求...") add_url = "https://member.bilibili.com/x2/creative/web/season/section/episodes/add" res = self.session.post(add_url, params={"csrf": self.csrf}, json={ "sectionId": section_id, "episodes": final_list }).json() if res["code"] == 0: print(f"🎉 成功!已补齐 {len(final_list)} 个不含“纯享”的录播视频。") else: print(f"❌ 批量失败: {res['message']} (Code: {res['code']})") else: print("❌ 未能获取有效的视频详情。") if __name__ == "__main__": tool = BiliCollectionBatchTool() tool.run()