from __future__ import annotations import argparse import socket import subprocess import sys import time from pathlib import Path from Douyin import DEFAULT_USER_URL DEFAULT_CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" DEFAULT_BROWSER_PORT = 9223 DEFAULT_PROFILE_DIR = Path(".douyin-chrome-profile") def derive_chrome_app_path(chrome_path: str) -> str: marker = ".app/" if marker not in chrome_path: return chrome_path prefix, _ = chrome_path.split(marker, 1) return f"{prefix}.app" def build_login_command( chrome_path: str, profile_dir: Path, browser_port: int, user_url: str, ) -> list[str]: app_path = derive_chrome_app_path(chrome_path) return [ "open", "-na", app_path, "--args", f"--user-data-dir={profile_dir}", f"--remote-debugging-port={browser_port}", user_url, ] def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="启动可见 Chrome,供抖音手动登录后附着抓取") parser.add_argument("--chrome-path", default=DEFAULT_CHROME_PATH, help="Chrome 可执行文件路径") parser.add_argument( "--profile-dir", default=str(DEFAULT_PROFILE_DIR), help="Chrome 用户数据目录,默认复用项目内固定目录", ) parser.add_argument( "--browser-port", type=int, default=DEFAULT_BROWSER_PORT, help="Chrome 调试端口,默认 9223", ) parser.add_argument("--user-url", default=DEFAULT_USER_URL, help="启动后打开的抖音主页 URL") return parser def launch_browser(command: list[str]) -> subprocess.Popen[str]: return subprocess.Popen(command) def wait_for_browser_debug_port( browser_port: int, timeout_seconds: float = 15.0, interval_seconds: float = 0.25, ) -> None: deadline = time.monotonic() + timeout_seconds while time.monotonic() < deadline: try: with socket.create_connection(("127.0.0.1", browser_port), timeout=1): return except OSError: time.sleep(interval_seconds) raise RuntimeError( f"Chrome 已启动命令,但调试端口 {browser_port} 在限定时间内未就绪。" ) def main(argv: list[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) if args.browser_port <= 0: parser.error("--browser-port 必须大于 0") chrome_path = Path(args.chrome_path) if not chrome_path.exists(): print(f"[ERROR] Chrome 可执行文件不存在: {chrome_path}") return 1 profile_dir = Path(args.profile_dir).resolve() profile_dir.mkdir(parents=True, exist_ok=True) command = build_login_command( chrome_path=str(chrome_path), profile_dir=profile_dir, browser_port=args.browser_port, user_url=args.user_url, ) try: launch_browser(command) except OSError as exc: print(f"[ERROR] 启动 Chrome 失败: {exc}") return 1 try: wait_for_browser_debug_port(args.browser_port) except RuntimeError as exc: print(f"[ERROR] {exc}") return 1 print("[INFO] Chrome 已启动。请在打开的浏览器中完成抖音登录和验证码。") print(f"[INFO] 登录完成后执行: ./.venv/bin/python Douyin.py --browser-port {args.browser_port}") return 0 if __name__ == "__main__": sys.exit(main())