from __future__ import annotations import os import time import re from typing import Optional, Callable from playwright.sync_api import sync_playwright class BaiduUploader: """ 百度网盘上传工具 - 纯净上传版 (移除分享逻辑) """ def __init__(self, chrome_path: str, cookies_dir: str, log_callback: Optional[Callable[[str], None]] = None): self.chrome_path = chrome_path self.cookies_dir = cookies_dir self.log_callback = log_callback self.url = "https://pan.baidu.com/" if not os.path.exists(self.cookies_dir): os.makedirs(self.cookies_dir, exist_ok=True) def _log(self, message: str): print(message) if self.log_callback: self.log_callback(message) def upload_file(self, file_path: str, target_folder_name: str, root_path: str = "精品项目整理") -> bool: """ 上传文件到百度网盘 """ if not os.path.exists(file_path): self._log(f"错误: 本地文件不存在 {file_path}") return False filename = os.path.basename(file_path) # 提取项目编号 project_id = "" match = re.search(r'(【[A-Za-z0-9]+】)', target_folder_name) if match: project_id = match.group(1) try: with sync_playwright() as p: self._log(f"正在启动浏览器...") launch_args = { "user_data_dir": self.cookies_dir, "headless": False, "executable_path": self.chrome_path if self.chrome_path and os.path.exists(self.chrome_path) else None, "viewport": {"width": 1280, "height": 800} } if not launch_args["executable_path"]: launch_args.pop("executable_path") context = p.chromium.launch_persistent_context(**launch_args) context.add_init_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})") page = context.new_page() page.set_default_timeout(60000) # 1. 登录 self._log("检测登录状态...") page.goto(self.url, wait_until="domcontentloaded") if not self._wait_for_login(page): self._log("请手动扫码登录...") page.wait_for_selector("text=全部文件", timeout=180000) # 2. 导航 self._log("登录成功,进入目标路径...") page.get_by_text("全部文件").first.click(force=True) time.sleep(4) if not self._scan_and_enter(page, root_path): self._create_folder(page, root_path) time.sleep(2); self._scan_and_enter(page, root_path) search_key = project_id if project_id else target_folder_name if not self._scan_and_enter(page, search_key, is_fuzzy=True): self._create_folder(page, target_folder_name) time.sleep(2); self._scan_and_enter(page, search_key, is_fuzzy=True) # 3. 查重 if self._check_file_exists_robust(page, filename): self._log(f"云端已存在文件 '{filename}',跳过上传。") time.sleep(2) context.close() return True # 4. 执行上传 self._log(f"开始传输: {filename}") page.set_input_files("input[type=file]", file_path) # 5. 监测列表确认成功 self._log("上传已启动,监测云端状态中...") success = self._wait_for_file_in_list(page, filename) if success: self._log("\n" + "="*30) self._log("百度网盘上传圆满成功!") self._log("="*30 + "\n") time.sleep(3) else: self._log("警告:监测超时,未能在列表中看到文件,请手动核实。") context.close() return success except Exception as e: self._log(f"程序异常: {str(e)}") return False def _wait_for_login(self, page) -> bool: for _ in range(10): if page.get_by_text("全部文件").count() > 0: return True time.sleep(2) return False def _scan_and_enter(self, page, target_name: str, is_fuzzy: bool = False) -> bool: try: page.evaluate("window.scrollTo(0, 300)") found = page.evaluate(f""" (args) => {{ const {{ name, fuzzy }} = args; const els = [...document.querySelectorAll('span[title], a[title], .wp-s-file-list-drag-copy__item-title-text')]; const target = els.find(e => {{ const title = e.getAttribute('title') || e.innerText.trim(); return fuzzy ? title.includes(name) : title === name; }}); if (target) {{ target.click(); const ev = new MouseEvent('dblclick', {{ 'view': window, 'bubbles': true, 'cancelable': true }}); target.dispatchEvent(ev); return true; }} return false; }} """, {"name": target_name, "fuzzy": is_fuzzy}) if found: time.sleep(3); return True return False except: return False def _create_folder(self, page, folder_name: str): try: page.keyboard.press("Escape") btn = page.get_by_text("新建文件夹").first if btn.count() > 0: btn.click(force=True) time.sleep(1.5) page.keyboard.type(folder_name, delay=50) page.keyboard.press("Enter") time.sleep(3) except: pass def _check_file_exists_robust(self, page, filename: str) -> bool: return page.evaluate(f""" (name) => {{ const els = [...document.querySelectorAll('span[title], a[title], .wp-s-file-list-drag-copy__item-title-text')]; return els.some(e => (e.getAttribute('title') || e.innerText.trim()) === name); }} """, filename) def _wait_for_file_in_list(self, page, filename: str) -> bool: start_time = time.time(); last_refresh = time.time() while time.time() - start_time < 1800: if self._check_file_exists_robust(page, filename): content = page.content() # 如果文件在列表里,且页面内容没在报错或重试 if not any(x in content for x in ["正在上传", "传输队列", "0B/s"]): return True if time.time() - last_refresh > 25: try: page.locator(".wp-s-file-list-drag-copy__header-breadcrumb-item").last.click(); time.sleep(3) except: pass last_refresh = time.time() time.sleep(5) return False