import requests import time import json import random from pathlib import Path # ================= 配置区域 ================= COOKIE_FILE = Path("./cookies.json") TARGET_SEASON_ID = 7196643 # 目标合集 ID ASCENDING_ORDER = True # True: 最早发布的在前面 (1, 2, 3...) # =========================================== def extract_cookie_from_list(cookie_list): """从列表结构中提取 SESSDATA 和 bili_jct""" sessdata = "" bili_jct = "" for item in cookie_list: if item.get("name") == "SESSDATA": sessdata = item.get("value") elif item.get("name") == "bili_jct": bili_jct = item.get("value") return sessdata, bili_jct def load_cookies(file_path): """智能从 json 文件加载 cookies""" if not file_path.exists(): print(f"[!] 错误: 找不到文件 {file_path}") exit(1) try: with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) sessdata = "" bili_jct = "" if isinstance(data, list): sessdata, bili_jct = extract_cookie_from_list(data) elif isinstance(data, dict): if "cookie_info" in data and "cookies" in data["cookie_info"]: sessdata, bili_jct = extract_cookie_from_list(data["cookie_info"]["cookies"]) elif "cookies" in data and isinstance(data["cookies"], list): sessdata, bili_jct = extract_cookie_from_list(data["cookies"]) else: sessdata = data.get("SESSDATA", "") bili_jct = data.get("bili_jct", "") if not sessdata or not bili_jct: print("[!] 错误: cookies.json 中未找到 SESSDATA 或 bili_jct") exit(1) return sessdata, bili_jct except Exception as e: print(f"[!] 解析 cookies.json 失败: {e}") exit(1) # 初始化 Cookie SESSDATA, BILI_JCT = load_cookies(COOKIE_FILE) print(f"[*] SESSDATA 读取成功: {SESSDATA[:4]}...{SESSDATA[-4:]}") print(f"[*] bili_jct 读取成功: {BILI_JCT[:4]}...{BILI_JCT[-4:]}") 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": f"SESSDATA={SESSDATA}; bili_jct={BILI_JCT}", "Content-Type": "application/json" } def get_section_id_by_season(season_id): """查找合集对应的小节ID""" print(f"[*] 正在查找合集 ID {season_id} 的小节信息...") url = "https://member.bilibili.com/x2/creative/web/seasons" page = 1 while True: params = {"pn": page, "ps": 30, "order": "", "sort": ""} resp = requests.get(url, params=params, headers=HEADERS) data = resp.json() if data["code"] != 0: print(f"[!] 获取合集列表失败: {data['message']}") if data["code"] == -101: print("[!] 提示: 账号未登录,请检查 cookies.json") return None seasons_list = data["data"]["seasons"] if not seasons_list: break for s_obj in seasons_list: s_info = s_obj["season"] if s_info["id"] == season_id: title = s_info["title"] print(f"[*] 找到合集: 《{title}》") if "sections" in s_obj and "sections" in s_obj["sections"]: first_section = s_obj["sections"]["sections"][0] sec_id = first_section["id"] sec_title = first_section["title"] print(f"[*] 锁定小节: [{sec_title}] (Section ID: {sec_id})") return sec_id else: print("[!] 该合集下没有发现小节结构。") return None page += 1 time.sleep(0.5) print(f"[!] 未在您的账号中找到 Season ID: {season_id}") return None def get_video_publish_time(bvid): """ 获取视频发布时间 [FIXED] 增加了 headers 参数,防止 B 站拦截请求 """ url = "https://api.bilibili.com/x/web-interface/view" params = {"bvid": bvid} try: # !!!关键修改:这里必须带上 headers !!! resp = requests.get(url, params=params, headers=HEADERS) data = resp.json() if data["code"] == 0: return data["data"]["pubdate"], data["data"]["title"] else: # 打印具体错误原因 print(f"\n[!] 获取视频 {bvid} 失败: code={data['code']}, msg={data['message']}") return 0, "Unknown" except Exception as e: print(f"\n[!] 请求异常: {e}") return 0, "Unknown" def sort_videos(section_id): # 1. 获取小节内视频 url_get = "https://member.bilibili.com/x2/creative/web/season/section" resp = requests.get(url_get, params={"id": section_id}, headers=HEADERS) res_json = resp.json() if res_json["code"] != 0: print(f"[!] API 错误: {res_json['message']}") return section_info = res_json["data"]["section"] episodes = res_json["data"]["episodes"] if not episodes: print("[!] 合集内无视频。") return total = len(episodes) print(f"[*] 获取到 {total} 个视频,开始查询发布时间...") video_list = [] success_count = 0 for idx, ep in enumerate(episodes): # 随机延迟 0.2 ~ 0.5 秒,比固定延迟更安全 time.sleep(random.uniform(0.2, 0.5)) bvid = ep["bvid"] pubdate, title = get_video_publish_time(bvid) # 简单的进度显示 date_str = "Fail/Unknown" if pubdate != 0: date_str = time.strftime('%Y-%m-%d', time.localtime(pubdate)) success_count += 1 print(f" [{idx+1}/{total}] {title[:15]:<15} -> {date_str}") video_list.append({ "id": ep["id"], "title": ep["title"] if title == "Unknown" else title, # 优先使用 API 查到的全名 "pubdate": pubdate }) if success_count == 0: print("[!] 错误: 所有视频时间查询均失败,终止排序以免数据混乱。") return # 2. 排序 print("[*] 正在计算排序顺序...") video_list.sort(key=lambda x: x['pubdate'], reverse=not ASCENDING_ORDER) # 3. 提交 print("[*] 正在提交新的排序列表...") sorts_payload = [{"id": v["id"], "sort": i+1} for i, v in enumerate(video_list)] payload = { "section": { "id": section_info["id"], "seasonId": section_info["seasonId"], "title": section_info["title"], "type": section_info["type"] }, "sorts": sorts_payload } url_edit = f"https://member.bilibili.com/x2/creative/web/season/section/edit?csrf={BILI_JCT}" try: resp_submit = requests.post(url_edit, json=payload, headers=HEADERS) result = resp_submit.json() if result["code"] == 0: print(f"\n[SUCCESS] 合集《{section_info['title']}》排序更新成功!") else: print(f"\n[FAIL] 更新失败: {result['message']}") except Exception as e: print(f"\n[!] 提交时发生网络错误: {e}") if __name__ == "__main__": print("--- Bilibili 合集自动排序工具 (v2.0 fixed) ---") target_section_id = get_section_id_by_season(TARGET_SEASON_ID) if target_section_id: sort_videos(target_section_id)