#!/usr/bin/env python # -*- coding: utf-8 -*- """ 抖音Cookie自动提取器 使用Playwright自动登录并提取Cookie """ import asyncio import json import os import sys import yaml from pathlib import Path from typing import Dict, Optional import time try: from playwright.async_api import async_playwright, Browser, Page from rich.console import Console from rich.prompt import Prompt, Confirm from rich.panel import Panel from rich import print as rprint except ImportError: print("请安装必要的依赖: pip install playwright rich pyyaml") print("并运行: playwright install chromium") sys.exit(1) console = Console() class CookieExtractor: """Cookie提取器""" def __init__(self, config_path: str = "config_simple.yml"): self.config_path = config_path self.cookies = {} async def extract_cookies(self, headless: bool = False) -> Dict: """提取Cookie Args: headless: 是否无头模式运行 """ console.print(Panel.fit( "[bold cyan]抖音Cookie自动提取器[/bold cyan]\n" "[dim]将自动打开浏览器,请在浏览器中完成登录[/dim]", border_style="cyan" )) async with async_playwright() as p: # 启动浏览器 browser = await p.chromium.launch( headless=headless, args=['--disable-blink-features=AutomationControlled'] ) # 创建上下文(模拟真实浏览器) context = await browser.new_context( viewport={'width': 1280, 'height': 720}, 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' ) # 添加初始化脚本(隐藏自动化特征) await context.add_init_script(""" Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); """) # 创建页面 page = await context.new_page() try: # 访问抖音登录页 console.print("\n[cyan]正在打开抖音登录页面...[/cyan]") await page.goto('https://www.douyin.com', wait_until='networkidle') # 等待用户登录 console.print("\n[yellow]请在浏览器中完成登录操作[/yellow]") console.print("[dim]登录方式:[/dim]") console.print(" 1. 扫码登录(推荐)") console.print(" 2. 手机号登录") console.print(" 3. 第三方账号登录") # 等待登录成功的标志 logged_in = await self._wait_for_login(page) if logged_in: console.print("\n[green]✅ 登录成功!正在提取Cookie...[/green]") # 提取Cookie cookies = await context.cookies() # 转换为字典格式 cookie_dict = {} cookie_string = "" for cookie in cookies: cookie_dict[cookie['name']] = cookie['value'] cookie_string += f"{cookie['name']}={cookie['value']}; " self.cookies = cookie_dict # 显示重要Cookie console.print("\n[cyan]提取到的关键Cookie:[/cyan]") important_cookies = ['sessionid', 'sessionid_ss', 'ttwid', 'passport_csrf_token', 'msToken'] for name in important_cookies: if name in cookie_dict: value = cookie_dict[name] console.print(f" • {name}: {value[:20]}..." if len(value) > 20 else f" • {name}: {value}") # 保存Cookie if Confirm.ask("\n是否保存Cookie到配置文件?"): self._save_cookies(cookie_dict) console.print("[green]✅ Cookie已保存到配置文件[/green]") # 保存完整Cookie字符串到文件 with open('cookies.txt', 'w', encoding='utf-8') as f: f.write(cookie_string.strip()) console.print("[green]✅ 完整Cookie已保存到 cookies.txt[/green]") return cookie_dict else: console.print("\n[red]❌ 登录超时或失败[/red]") return {} except Exception as e: console.print(f"\n[red]❌ 提取Cookie失败: {e}[/red]") return {} finally: await browser.close() async def _wait_for_login(self, page: Page, timeout: int = 300) -> bool: """等待用户登录 Args: page: 页面对象 timeout: 超时时间(秒) """ start_time = time.time() while time.time() - start_time < timeout: # 检查是否已登录(多种判断方式) try: # 方式1:检查是否有用户头像 avatar = await page.query_selector('div[class*="avatar"]') if avatar: await asyncio.sleep(2) # 等待Cookie完全加载 return True # 方式2:检查URL是否包含用户ID current_url = page.url if '/user/' in current_url: await asyncio.sleep(2) return True # 方式3:检查是否有特定的登录后元素 user_menu = await page.query_selector('[class*="user-info"]') if user_menu: await asyncio.sleep(2) return True except: pass await asyncio.sleep(2) # 显示等待进度 elapsed = int(time.time() - start_time) remaining = timeout - elapsed console.print(f"\r[dim]等待登录中... ({remaining}秒后超时)[/dim]", end="") return False def _save_cookies(self, cookies: Dict): """保存Cookie到配置文件""" # 读取现有配置 if os.path.exists(self.config_path): with open(self.config_path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) or {} else: config = {} # 更新Cookie配置 config['cookies'] = cookies # 保存配置 with open(self.config_path, 'w', encoding='utf-8') as f: yaml.dump(config, f, allow_unicode=True, default_flow_style=False) async def quick_extract(self) -> Dict: """快速提取(使用已登录的浏览器会话)""" console.print("\n[cyan]尝试从已打开的浏览器提取Cookie...[/cyan]") console.print("[dim]请确保您已在浏览器中登录抖音[/dim]") # 这里可以使用CDP连接到已打开的浏览器 # 需要浏览器以调试模式启动 console.print("\n[yellow]请按以下步骤操作:[/yellow]") console.print("1. 关闭所有Chrome浏览器") console.print("2. 使用调试模式启动Chrome:") console.print(" Windows: chrome.exe --remote-debugging-port=9222") console.print(" Mac: /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222") console.print("3. 在打开的浏览器中登录抖音") console.print("4. 按Enter继续...") input() try: async with async_playwright() as p: # 连接到已打开的浏览器 browser = await p.chromium.connect_over_cdp("http://localhost:9222") contexts = browser.contexts if contexts: context = contexts[0] pages = context.pages # 查找抖音页面 douyin_page = None for page in pages: if 'douyin.com' in page.url: douyin_page = page break if douyin_page: # 提取Cookie cookies = await context.cookies() cookie_dict = {} for cookie in cookies: if 'douyin.com' in cookie.get('domain', ''): cookie_dict[cookie['name']] = cookie['value'] if cookie_dict: console.print("[green]✅ 成功提取Cookie![/green]") self._save_cookies(cookie_dict) return cookie_dict else: console.print("[red]未找到抖音Cookie[/red]") else: console.print("[red]未找到抖音页面,请先访问douyin.com[/red]") else: console.print("[red]未找到浏览器上下文[/red]") except Exception as e: console.print(f"[red]连接浏览器失败: {e}[/red]") console.print("[yellow]请确保浏览器以调试模式启动[/yellow]") return {} async def main(): """主函数""" extractor = CookieExtractor() console.print("\n[cyan]请选择提取方式:[/cyan]") console.print("1. 自动登录提取(推荐)") console.print("2. 从已登录浏览器提取") console.print("3. 手动输入Cookie") choice = Prompt.ask("请选择", choices=["1", "2", "3"], default="1") if choice == "1": # 自动登录提取 headless = not Confirm.ask("是否显示浏览器界面?", default=True) cookies = await extractor.extract_cookies(headless=headless) elif choice == "2": # 从已登录浏览器提取 cookies = await extractor.quick_extract() else: # 手动输入 console.print("\n[cyan]请输入Cookie字符串:[/cyan]") console.print("[dim]格式: name1=value1; name2=value2; ...[/dim]") cookie_string = Prompt.ask("Cookie") cookies = {} for item in cookie_string.split(';'): if '=' in item: key, value = item.strip().split('=', 1) cookies[key] = value if cookies: extractor._save_cookies(cookies) console.print("[green]✅ Cookie已保存[/green]") if cookies: console.print("\n[green]✅ Cookie提取完成![/green]") console.print("[dim]您现在可以运行下载器了:[/dim]") console.print("python3 downloader.py -c config_simple.yml") else: console.print("\n[red]❌ 未能提取Cookie[/red]") if __name__ == '__main__': try: asyncio.run(main()) except KeyboardInterrupt: console.print("\n[yellow]用户取消操作[/yellow]") except Exception as e: console.print(f"\n[red]程序异常: {e}[/red]")