python扫码登录dy

import os

import json

import time

import pickle

from selenium import webdriver

from selenium.webdriver.common.by import By

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC

from selenium.webdriver.chrome.service import Service

from selenium.webdriver.chrome.options import Options

from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException

class DouyinAutoLogin:

"""

抖音自动化登录类,支持Cookies免扫码登录和短信验证

复制代码
功能说明:
1. 优先使用保存的Cookies进行自动登录
2. Cookies失效时,自动切换到扫码登录模式
3. 扫码过程中如果触发短信验证,自动处理验证流程
4. 登录成功后保存Cookies供下次使用

使用场景:
- 需要自动化操作抖音网页版
- 避免频繁手动扫码登录
- 处理需要短信验证的登录情况
"""

def __init__(self, cookies_file='douyin_cookies.pkl', headless=False, phone_number=None):
    """
    初始化抖音自动化登录类

    Args:
        cookies_file: Cookies保存文件路径,默认'douyin_cookies.pkl'
                     用于保存和读取登录凭证
        headless: 是否使用无头模式(不显示浏览器界面)
                  True: 后台运行,适合服务器环境
                  False: 显示浏览器界面,便于调试
        phone_number: 手机号(用于自动填充短信验证时的手机号)
                      格式:'138****0000' 或完整手机号
    """
    self.cookies_file = cookies_file  # Cookies存储文件路径
    self.driver = None  # Chrome浏览器驱动实例
    self.wait = None  # WebDriverWait等待实例,用于元素定位
    self.headless = headless  # 是否无头模式
    self.phone_number = phone_number  # 用户手机号
    self.douyin_url = 'https://www.douyin.com'  # 抖音网站URL
    self.max_verify_attempts = 3  # 最大验证码尝试次数

def _init_driver(self):
    """
    初始化Chrome浏览器驱动 - 增强稳定性

    配置说明:
    - 设置各种反检测参数,避免被识别为自动化程序
    - 配置防崩溃参数,提高运行稳定性
    - 设置超时时间和窗口大小
    - 执行JavaScript隐藏自动化特征

    Raises:
        Exception: 浏览器初始化失败时抛出异常
    """
    chrome_options = Options()

    # ========== 无头模式配置 ==========
    if self.headless:
        chrome_options.add_argument('--headless')  # 不显示浏览器窗口

    # ========== 防止自动化检测的关键配置 ==========
    # 禁用"Chrome正在受到自动软件控制"的提示
    chrome_options.add_argument('--disable-blink-features=AutomationControlled')
    # 排除启用自动化相关的开关
    chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])
    # 禁用自动化扩展
    chrome_options.add_experimental_option('useAutomationExtension', False)

    # ========== 防止浏览器崩溃的关键配置 ==========
    chrome_options.add_argument('--no-sandbox')  # 禁用沙箱模式(Linux环境必需)
    chrome_options.add_argument('--disable-dev-shm-usage')  # 解决/dev/shm内存不足问题
    chrome_options.add_argument('--disable-web-security')  # 禁用Web安全策略
    chrome_options.add_argument('--disable-features=VizDisplayCompositor')  # 禁用显示合成器
    chrome_options.add_argument('--disable-features=IsolateOrigins,site-per-process')  # 禁用站点隔离

    # ========== 禁用各种可能导致崩溃的特性 ==========
    chrome_options.add_argument('--disable-background-networking')  # 禁用后台网络
    chrome_options.add_argument('--disable-background-timer-throttling')  # 禁用后台定时器节流
    chrome_options.add_argument('--disable-backgrounding-occluded-windows')  # 禁用后台窗口
    chrome_options.add_argument('--disable-breakpad')  # 禁用崩溃报告
    chrome_options.add_argument('--disable-client-side-phishing-detection')  # 禁用钓鱼检测
    chrome_options.add_argument('--disable-component-extensions-with-background-pages')  # 禁用组件扩展
    chrome_options.add_argument('--disable-default-apps')  # 禁用默认应用
    chrome_options.add_argument('--disable-extensions')  # 禁用扩展
    chrome_options.add_argument('--disable-hang-monitor')  # 禁用挂起监控
    chrome_options.add_argument('--disable-ipc-flooding-protection')  # 禁用IPC洪水保护
    chrome_options.add_argument('--disable-popup-blocking')  # 禁用弹窗拦截
    chrome_options.add_argument('--disable-prompt-on-repost')  # 禁用重复提交提示
    chrome_options.add_argument('--disable-renderer-backgrounding')  # 禁用渲染器后台运行
    chrome_options.add_argument('--disable-sync')  # 禁用同步功能
    chrome_options.add_argument('--force-color-profile=srgb')  # 强制色彩配置文件
    chrome_options.add_argument('--metrics-recording-only')  # 仅记录指标
    chrome_options.add_argument('--no-first-run')  # 跳过首次运行设置
    chrome_options.add_argument('--enable-automation')  # 启用自动化(配合其他反检测使用)
    chrome_options.add_argument('--password-store=basic')  # 使用基础密码存储
    chrome_options.add_argument('--use-mock-keychain')  # 使用模拟钥匙串

    # ========== 内存优化配置 ==========
    chrome_options.add_argument('--max_old_space_size=4096')  # 设置最大内存4096MB
    chrome_options.add_argument('--memory-pressure-off')  # 关闭内存压力检测

    # ========== 禁用自动化提示 ==========
    chrome_options.add_argument('--disable-infobars')  # 禁用信息栏提示
    chrome_options.add_argument('--disable-notifications')  # 禁用网页通知

    # ========== 设置用户代理(模拟真实浏览器) ==========
    chrome_options.add_argument(
        '--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'
    )

    # ========== SSL证书配置 ==========
    chrome_options.add_argument('--ignore-certificate-errors')  # 忽略证书错误
    chrome_options.add_argument('--ignore-ssl-errors')  # 忽略SSL错误

    # ========== GPU和渲染配置 ==========
    chrome_options.add_argument('--disable-gpu')  # 禁用GPU加速
    chrome_options.add_argument('--disable-software-rasterizer')  # 禁用软件光栅化

    # ========== 窗口配置 ==========
    chrome_options.add_argument('--window-size=1280,800')  # 设置窗口大小
    chrome_options.add_argument('--start-maximized')  # 启动时最大化

    # ========== 用户偏好设置 ==========
    prefs = {
        'credentials_enable_service': False,  # 禁用密码保存服务
        'profile.password_manager_enabled': False,  # 禁用密码管理器
        'profile.default_content_setting_values.notifications': 2,  # 禁用通知权限
        'excludeSwitches': ['enable-logging'],  # 禁用日志输出
    }
    chrome_options.add_experimental_option('prefs', prefs)

    try:
        # 创建Service对象,配置进程创建标志
        service = Service()
        # CREATE_NO_WINDOW = 0x08000000,防止弹出控制台窗口(Windows)
        service.creation_flags = 0x08000000

        # 创建Chrome浏览器驱动实例
        self.driver = webdriver.Chrome(
            service=service,
            options=chrome_options
        )

        # 设置页面加载超时时间(秒)
        self.driver.set_page_load_timeout(30)
        # 设置JavaScript执行超时时间(秒)
        self.driver.set_script_timeout(30)

        # 创建WebDriverWait实例,用于显式等待元素出现
        self.wait = WebDriverWait(self.driver, 20)

        # ========== 执行JavaScript隐藏自动化特征 ==========
        # 在每次加载新页面时执行这些脚本,防止被检测
        self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
            'source': '''
                // 隐藏webdriver属性
                Object.defineProperty(navigator, 'webdriver', {
                    get: () => undefined
                });
                // 伪装插件数量(真实浏览器通常有多个插件)
                Object.defineProperty(navigator, 'plugins', {
                    get: () => [1, 2, 3, 4, 5]
                });
                // 设置语言偏好
                Object.defineProperty(navigator, 'languages', {
                    get: () => ['zh-CN', 'zh']
                });
                // 伪装Chrome对象
                window.chrome = {
                    runtime: {}
                };
            '''
        })

        print("[+] 浏览器初始化成功")

    except Exception as e:
        print(f"[-] 浏览器初始化失败: {e}")
        raise  # 抛出异常,让上层处理

def save_cookies(self):
    """
    保存当前浏览器的Cookies到本地文件

    工作原理:
    1. 从driver获取所有cookies
    2. 使用pickle序列化保存到文件
    3. 下次登录时可以直接加载使用

    注意:Cookies可能包含敏感信息,注意文件安全
    """
    try:
        if not self.driver:
            print("[-] 浏览器驱动不存在")
            return

        # 获取当前浏览器中的所有cookies
        cookies = self.driver.get_cookies()

        # 以二进制写入模式打开文件,使用pickle保存cookies
        with open(self.cookies_file, 'wb') as f:
            pickle.dump(cookies, f)

        print(f"[+] Cookies已保存到: {self.cookies_file}")
    except Exception as e:
        print(f"[-] 保存Cookies失败: {e}")

def load_cookies(self):
    """
    从本地文件加载Cookies到浏览器

    流程:
    1. 检查cookies文件是否存在
    2. 先访问抖音首页(设置domain)
    3. 加载cookies到当前会话
    4. 刷新页面使cookies生效

    Returns:
        bool: True表示加载成功,False表示失败或文件不存在
    """
    # 检查cookies文件是否存在
    if not os.path.exists(self.cookies_file):
        print(f"[-] Cookies文件不存在: {self.cookies_file}")
        return False

    try:
        # 读取保存的cookies
        with open(self.cookies_file, 'rb') as f:
            cookies = pickle.load(f)

        # 必须先访问域名,才能设置该域名的cookies
        self.driver.get(self.douyin_url)
        time.sleep(2)  # 等待页面加载

        # 逐个添加cookies
        for cookie in cookies:
            # 删除domain字段,避免跨域问题
            # 因为当前访问的是douyin.com,不需要指定domain
            if 'domain' in cookie:
                del cookie['domain']
            try:
                self.driver.add_cookie(cookie)
            except Exception as e:
                print(f"[-] 添加Cookie失败: {e}")

        print("[+] Cookies加载成功")
        return True

    except Exception as e:
        print(f"[-] 加载Cookies失败: {e}")
        return False

def _safe_find_element(self, by, selector, timeout=5):
    """
    安全地查找元素,避免因元素不存在而抛出异常

    Args:
        by: 定位方式(By.ID, By.CSS_SELECTOR等)
        selector: 选择器字符串
        timeout: 最大等待时间(秒)

    Returns:
        WebElement或None: 找到元素返回元素对象,否则返回None
    """
    try:
        # 显式等待元素出现
        element = WebDriverWait(self.driver, timeout).until(
            EC.presence_of_element_located((by, selector))
        )
        return element
    except:
        # 元素未找到时返回None,不抛出异常
        return None

def check_login_status(self):
    """
    检查当前是否已登录抖音账号

    检测方法:
    1. 优先检查用户头像元素是否存在(已登录的标志)
    2. 检查是否存在登录按钮(未登录的标志)
    3. 检查URL是否包含login参数

    Returns:
        bool: True表示已登录,False表示未登录
    """
    try:
        # 检查driver是否还存在
        if not self.driver:
            print("[-] 浏览器驱动已断开")
            return False

        # 尝试获取当前URL,检查浏览器是否存活
        try:
            current_url = self.driver.current_url
        except:
            print("[-] 无法获取当前URL,浏览器可能已崩溃")
            return False

        # 方法1:检查用户头像元素(登录后才会显示)
        try:
            avatar = self._safe_find_element(By.CSS_SELECTOR, '[data-e2e="user-avatar"]', timeout=3)
            if avatar:
                print("[+] 已登录状态")
                return True
        except:
            pass

        # 方法2:检查登录按钮(未登录时显示)
        try:
            login_btn = self._safe_find_element(By.CSS_SELECTOR, '[data-e2e="login-button"]', timeout=3)
            if login_btn:
                print("[-] 未登录状态")
                return False
        except:
            pass

        # 方法3:检查URL是否包含登录相关参数
        if 'login' in current_url:
            print("[-] 需要登录")
            return False

        print("[+] 登录状态正常")
        return True

    except Exception as e:
        print(f"[-] 检查登录状态失败: {e}")
        return False

def wait_for_sms_verification(self):
    """
    等待并处理短信验证码流程

    流程:
    1. 检测是否进入短信验证页面
    2. 自动填充手机号(如果提供了)
    3. 等待用户输入验证码
    4. 提交验证码并检查结果
    5. 支持重新发送验证码功能

    Returns:
        bool: True表示验证成功,False表示验证失败
    """
    print("\n" + "=" * 50)
    print("[*] 检测到需要短信验证")
    print("=" * 50)

    # 检查浏览器是否还活着
    if not self._check_driver_alive():
        print("[-] 浏览器已崩溃,无法继续验证")
        return False

    try:
        # 给页面一些加载时间
        time.sleep(3)

        # ========== 步骤1:自动填充手机号 ==========
        try:
            # 查找手机号输入框(多种选择器)
            phone_inputs = self.driver.find_elements(By.CSS_SELECTOR,
                                                     'input[type="tel"], input[placeholder*="手机"], input[placeholder*="电话"]')

            if phone_inputs and self.phone_number:
                # 获取第一个找到的手机号输入框
                phone_input = phone_inputs[0]
                phone_input.clear()  # 清空已有内容

                # 模拟人工输入:逐字符输入,每个字符间隔0.1秒
                for char in self.phone_number:
                    phone_input.send_keys(char)
                    time.sleep(0.1)
                print(f"[+] 已自动填充手机号: {self.phone_number}")
                time.sleep(1)

        except Exception as e:
            print(f"[*] 处理发送验证码时出错: {e}")

        # ========== 步骤2:查找并点击"获取验证码"按钮 ==========
        send_code_btn = self._find_send_code_button()

        if send_code_btn:
            print("[*] 点击获取验证码...")
            # 使用JavaScript点击,避免元素被遮挡的问题
            self.driver.execute_script("arguments[0].click();", send_code_btn)
            time.sleep(3)  # 等待验证码发送
        else:
            print("[*] 未找到获取验证码按钮,可能已经发送")

        # ========== 步骤3:循环处理验证码输入 ==========
        for attempt in range(self.max_verify_attempts):
            print(f"\n[*] 验证码尝试 {attempt + 1}/{self.max_verify_attempts}")
            print("[*] 请查看手机短信,输入6位验证码(输入 'r' 重新发送):")

            # 从控制台获取用户输入的验证码
            verify_code = input("验证码: ").strip()

            # 处理重新发送验证码的请求
            if verify_code.lower() == 'r':
                self._resend_verification_code()  # 重新发送验证码
                continue

            # 验证码格式校验:必须是6位数字
            if not verify_code or len(verify_code) != 6 or not verify_code.isdigit():
                print("[-] 验证码格式不正确,请输入6位数字")
                continue

            # ========== 步骤4:输入验证码并提交 ==========
            if self._input_verification_code(verify_code):
                # 提交验证码
                if self._submit_verification():
                    # 等待验证结果
                    if self._wait_for_verification_result():
                        return True  # 验证成功

            print(f"[-] 验证失败,还剩 {self.max_verify_attempts - attempt - 1} 次机会")

        return False  # 所有尝试都失败

    except WebDriverException as e:
        print(f"[-] 浏览器异常: {e}")
        return False
    except Exception as e:
        print(f"[-] 验证过程出错: {e}")
        return False

def _check_driver_alive(self):
    """
    检查浏览器驱动是否还活着(未崩溃)

    Returns:
        bool: True表示正常,False表示已断开
    """
    try:
        if not self.driver:
            return False
        # 尝试执行一个简单操作来验证驱动是否正常
        self.driver.current_url
        return True
    except:
        return False

def _find_send_code_button(self):
    """
    查找"获取验证码"按钮

    使用多种选择器策略,提高查找成功率

    Returns:
        WebElement或None: 找到按钮返回元素,否则返回None
    """
    # 定义多种可能的选择器(优先级从高到低)
    send_code_selectors = [
        (By.XPATH, '//button[contains(text(), "获取验证码")]'),  # 按钮文本
        (By.XPATH, '//span[contains(text(), "获取验证码")]'),  # span文本
        (By.XPATH, '//div[contains(text(), "获取验证码")]'),  # div文本
        (By.CSS_SELECTOR, '[data-e2e="get-code"]'),  # e2e测试属性
        (By.CSS_SELECTOR, '.send-code-btn'),  # class类名
        (By.CSS_SELECTOR, '.get-code-btn'),  # class类名
    ]

    # 遍历所有选择器,找到第一个可见的按钮
    for by, selector in send_code_selectors:
        try:
            element = self.driver.find_element(by, selector)
            if element.is_displayed():  # 确保元素可见
                return element
        except:
            continue  # 当前选择器未找到,继续下一个

    return None

def _resend_verification_code(self):
    """
    重新发送验证码

    在验证码过期或未收到时调用,点击重新发送按钮
    """
    try:
        # 查找重新发送按钮
        send_again_btn = self._safe_find_element(
            By.XPATH,
            '//*[contains(text(), "重新发送") or contains(text(), "再次发送")]',
            timeout=3
        )
        if send_again_btn:
            # 使用JavaScript点击
            self.driver.execute_script("arguments[0].click();", send_again_btn)
            print("[+] 已重新发送验证码")
            time.sleep(2)  # 等待新验证码发送
    except Exception as e:
        print(f"[-] 重新发送失败: {e}")

def _input_verification_code(self, verify_code):
    """
    在验证码输入框中输入验证码

    Args:
        verify_code: 6位数字验证码

    Returns:
        bool: True表示输入成功,False表示失败
    """
    try:
        # 查找验证码输入框(多种可能的属性)
        code_inputs = self.driver.find_elements(By.CSS_SELECTOR,
                                                'input[type="text"]:not([readonly]), input[placeholder*="验证码"], input[placeholder*="code"]')

        if code_inputs:
            code_input = code_inputs[0]  # 使用第一个找到的输入框
            code_input.clear()  # 清空已有内容

            # 模拟人工输入:逐个字符输入
            for char in verify_code:
                code_input.send_keys(char)
                time.sleep(0.1)  # 每个字符间隔0.1秒,模拟人工输入速度

            print(f"[+] 已输入验证码: {verify_code}")
            return True
        else:
            print("[-] 未找到验证码输入框")
            return False

    except Exception as e:
        print(f"[-] 输入验证码时出错: {e}")
        return False

def _submit_verification(self):
    """
    提交验证码(点击确认/登录按钮或按回车)

    Returns:
        bool: True表示提交成功,False表示失败
    """
    try:
        # 定义多种可能的提交按钮选择器
        submit_selectors = [
            (By.XPATH, '//button[contains(text(), "确定")]'),  # 确定按钮
            (By.XPATH, '//button[contains(text(), "提交")]'),  # 提交按钮
            (By.XPATH, '//button[contains(text(), "验证")]'),  # 验证按钮
            (By.XPATH, '//button[contains(text(), "登录")]'),  # 登录按钮
            (By.CSS_SELECTOR, '[data-e2e="submit"]'),  # e2e属性
            (By.CSS_SELECTOR, '.submit-btn'),  # class类名
            (By.CSS_SELECTOR, '.confirm-btn'),  # class类名
        ]

        # 尝试查找并点击提交按钮
        for by, selector in submit_selectors:
            try:
                submit_btn = self.driver.find_element(by, selector)
                if submit_btn.is_displayed():  # 确保按钮可见
                    self.driver.execute_script("arguments[0].click();", submit_btn)
                    print("[+] 已提交验证码")
                    time.sleep(3)  # 等待服务器处理
                    return True
            except:
                continue

        # 如果没有找到按钮,尝试按回车键提交
        from selenium.webdriver.common.keys import Keys
        active_element = self.driver.switch_to.active_element  # 获取当前焦点元素
        active_element.send_keys(Keys.RETURN)  # 发送回车键
        print("[*] 已按回车提交")
        time.sleep(3)
        return True

    except Exception as e:
        print(f"[-] 提交验证码时出错: {e}")
        return False

def _wait_for_verification_result(self, timeout=15):
    """
    等待验证结果(成功或失败)

    Args:
        timeout: 最大等待时间(秒)

    Returns:
        bool: True表示验证成功,False表示失败
    """
    start_time = time.time()

    while time.time() - start_time < timeout:
        try:
            # 检查浏览器是否还活着
            if not self._check_driver_alive():
                print("[-] 浏览器已断开连接")
                return False

            # 检查是否登录成功
            if self.check_login_status():
                return True

            # 检查是否有错误提示
            error_elements = self.driver.find_elements(
                By.XPATH,
                '//*[contains(text(), "验证码错误") or contains(text(), "验证码过期") or contains(text(), "错误")]'
            )

            # 如果有错误提示,显示错误信息并返回失败
            for error_elem in error_elements:
                if error_elem.is_displayed():
                    print(f"[-] {error_elem.text}")
                    return False

            time.sleep(1)  # 每秒检查一次

        except Exception:
            pass  # 忽略异常,继续等待

    return False  # 超时未成功

def scan_qr_login(self):
    """
    扫码登录流程(包含短信验证处理)

    流程:
    1. 访问抖音首页
    2. 点击登录按钮
    3. 切换到二维码登录标签
    4. 显示二维码等待用户扫码
    5. 检测扫码过程中是否需要短信验证
    6. 处理短信验证(如果需要)
    7. 登录成功后保存Cookies

    Returns:
        bool: True表示登录成功,False表示失败
    """
    print("[*] 开始扫码登录流程...")

    try:
        # 访问抖音首页
        self.driver.get(self.douyin_url)
        time.sleep(3)

        # 检查页面是否加载成功
        if not self._check_driver_alive():
            print("[-] 页面加载失败")
            return False

        # ========== 步骤1:点击登录按钮 ==========
        try:
            # 等待登录按钮可点击
            login_btn = self.wait.until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-e2e="login-button"]'))
            )
            self.driver.execute_script("arguments[0].click();", login_btn)
            time.sleep(2)  # 等待登录框弹出
        except:
            print("[*] 可能已经显示登录框")

        # ========== 步骤2:切换到二维码登录 ==========
        try:
            # 点击二维码登录标签
            qr_tab = self.wait.until(
                EC.element_to_be_clickable((By.XPATH, '//div[contains(text(), "二维码")]'))
            )
            self.driver.execute_script("arguments[0].click();", qr_tab)
            time.sleep(2)  # 等待二维码加载
        except:
            print("[*] 默认已是二维码登录")

        # ========== 步骤3:显示二维码等待扫码 ==========
        print("\n" + "=" * 50)
        print("[*] 请使用抖音APP扫描二维码")
        print("[*] 提示:扫码过程中请不要关闭浏览器窗口")
        print("=" * 50)

        # 等待扫码的最大时间(秒)
        max_scan_time = 180  # 3分钟
        scan_start_time = time.time()
        sms_verification_triggered = False  # 是否已触发短信验证
        last_check_time = time.time()

        # ========== 步骤4:循环检测扫码状态 ==========
        while time.time() - scan_start_time < max_scan_time:
            try:
                # 定期检查浏览器状态(每5秒检查一次)
                if time.time() - last_check_time > 5:
                    if not self._check_driver_alive():
                        print("[-] 浏览器异常关闭,登录失败")
                        return False
                    last_check_time = time.time()

                # ========== 检测是否需要短信验证 ==========
                if not sms_verification_triggered:
                    if self._is_sms_verification_page():
                        print("\n[*] 检测到需要短信验证")
                        sms_verification_triggered = True

                        # 处理短信验证
                        if self.wait_for_sms_verification():
                            print("[+] 短信验证成功!")
                            time.sleep(2)
                            # 验证成功后检查登录状态
                            if self.check_login_status():
                                self.save_cookies()  # 保存登录状态
                                return True
                        else:
                            print("[-] 短信验证失败")
                            return False

                # ========== 检查是否已经登录成功 ==========
                if self.check_login_status():
                    print("[+] 扫码登录成功!")
                    self.save_cookies()  # 保存cookies供下次使用
                    return True

            except WebDriverException as e:
                print(f"[-] 浏览器异常: {e}")
                return False
            except Exception as e:
                print(f"[-] 检查状态时出错: {e}")

            time.sleep(2)  # 每2秒检查一次

        # 超时未扫码
        print("[-] 扫码登录超时")
        return False

    except WebDriverException as e:
        print(f"[-] 浏览器异常: {e}")
        return False
    except Exception as e:
        print(f"[-] 扫码登录失败: {e}")
        return False

def _is_sms_verification_page(self):
    """
    检查当前页面是否是短信验证页面

    通过查找页面中的特定元素来判断:
    - 短信验证文字
    - 验证码输入框
    - 手机验证相关文本

    Returns:
        bool: True表示是短信验证页面,False表示不是
    """
    # 定义短信验证页面的特征元素
    sms_indicators = [
        (By.XPATH, '//div[contains(text(), "短信验证")]'),  # 短信验证文字
        (By.XPATH, '//span[contains(text(), "验证码")]'),  # 验证码文字
        (By.XPATH, '//div[contains(text(), "手机验证")]'),  # 手机验证文字
        (By.CSS_SELECTOR, 'input[placeholder*="验证码"]'),  # 验证码输入框
    ]

    # 遍历所有特征,只要找到一个就认为是短信验证页面
    for by, selector in sms_indicators:
        try:
            element = self.driver.find_element(by, selector)
            if element.is_displayed():
                return True
        except:
            continue

    return False

def login(self):
    """
    主登录方法(智能选择登录方式)

    登录策略:
    1. 优先尝试使用保存的Cookies自动登录
    2. Cookies失效或不存在时,切换到扫码登录
    3. 扫码登录成功后保存新的Cookies

    Returns:
        bool: True表示登录成功,False表示失败
    """
    try:
        # 初始化浏览器驱动
        self._init_driver()

        # ========== 策略1:尝试使用Cookies登录 ==========
        if self.load_cookies():
            # 检查Cookies是否有效
            if self.check_login_status():
                print("[+] 使用Cookies登录成功!")
                return True
            else:
                print("[*] Cookies已过期,需要重新扫码登录")
        else:
            print("[*] 未找到有效Cookies,开始扫码登录")

        # ========== 策略2:扫码登录 ==========
        return self.scan_qr_login()

    except Exception as e:
        print(f"[-] 登录过程出错: {e}")
        return False

def get_user_info(self):
    """
    获取当前登录用户的信息

    功能:
    1. 进入抖音首页
    2. 点击头像进入个人主页
    3. 提取昵称和抖音号

    Returns:
        dict: 包含用户信息的字典 {'nickname': '昵称', 'douyin_id': '抖音号'}
        None: 获取失败时返回None
    """
    try:
        # 检查浏览器是否正常运行
        if not self._check_driver_alive():
            print("[-] 浏览器未运行")
            return None

        # 访问抖音首页
        self.driver.get(self.douyin_url)
        time.sleep(3)

        # 点击用户头像,进入个人主页
        avatar = self.wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-e2e="user-avatar"]'))
        )
        self.driver.execute_script("arguments[0].click();", avatar)
        time.sleep(3)  # 等待个人主页加载

        # ========== 提取用户昵称 ==========
        try:
            nickname = self.driver.find_element(By.CSS_SELECTOR, '[data-e2e="user-info-name"]').text
        except:
            nickname = "未获取到"

        # ========== 提取抖音号 ==========
        try:
            douyin_id = self.driver.find_element(By.CSS_SELECTOR, '[data-e2e="user-info-id"]').text
        except:
            douyin_id = "未获取到"

        # 组织用户信息字典
        user_info = {
            'nickname': nickname,
            'douyin_id': douyin_id,
        }

        print(f"[+] 用户信息: {user_info}")
        return user_info

    except Exception as e:
        print(f"[-] 获取用户信息失败: {e}")
        return None

def close(self):
    """
    关闭浏览器并释放资源

    注意事项:
    - 在程序结束前务必调用此方法
    - 关闭后无法再使用driver对象
    """
    try:
        if self.driver:
            self.driver.quit()  # 关闭所有窗口并结束进程
            print("[+] 浏览器已关闭")
    except:
        pass  # 忽略关闭时的异常

def main():

"""

主函数示例 - 演示如何使用DouyinAutoLogin类

复制代码
使用步骤:
1. 创建DouyinAutoLogin实例
2. 调用login()方法登录
3. 登录成功后获取用户信息
4. 等待用户操作后关闭浏览器
"""
douyin = None

try:
    # 创建抖音登录实例
    # 参数说明:
    # - cookies_file: 保存cookies的文件名
    # - headless: 是否无头模式(False表示显示浏览器界面)
    # - phone_number: 你的手机号(用于自动填充)
    douyin = DouyinAutoLogin(
        cookies_file='douyin_cookies.pkl',
        headless=False,
        phone_number='134********'  # 请替换为你的实际手机号
    )

    # 执行登录操作
    if douyin.login():
        print("\n" + "=" * 50)
        print("登录成功!")
        print("=" * 50 + "\n")

        # 获取并显示用户信息
        user_info = douyin.get_user_info()

        # 保持浏览器窗口打开,等待用户按Enter键关闭
        print("\n[*] 浏览器将保持打开状态,按Enter键退出...")
        input()  # 等待用户输入
    else:
        print("登录失败!")

except KeyboardInterrupt:
    # 处理用户按Ctrl+C中断的情况
    print("\n[*] 用户中断程序")
except Exception as e:
    # 打印完整的错误堆栈信息,便于调试
    print(f"程序运行出错: {e}")
    import traceback
    traceback.print_exc()

finally:
    # 无论成功还是失败,都要关闭浏览器释放资源
    if douyin:
        douyin.close()

程序入口

if name == "main ":

main()

相关推荐
我是唐青枫2 小时前
C#.NET gRPC 深入解析:Proto 定义、流式调用与服务间通信取舍
开发语言·c#·.net
JJay.2 小时前
Kotlin 高阶函数学习指南
android·开发语言·kotlin
bazhange2 小时前
python如何像matlab一样使用向量化替代for循环
开发语言·python·matlab
jinanwuhuaguo2 小时前
截止到4月8日,OpenClaw 2026年4月更新深度解读剖析:从“能力回归”到“信任内建”的范式跃迁
android·开发语言·人工智能·深度学习·kotlin
froginwe112 小时前
CSS 创建:从基础到高级
开发语言
人工干智能2 小时前
科普:python中你写的模块找不到了——`ModuleNotFoundError`
服务器·python
unicrom_深圳市由你创科技2 小时前
做虚拟示波器这种实时波形显示的上位机,用什么语言?
c++·python·c#
小敬爱吃饭2 小时前
Ragflow Docker部署及问题解决方案(界面为Welcome to nginx,ragflow上传文件失败,Docker中的ragflow-cpu-1一直重启)
人工智能·python·nginx·docker·语言模型·容器·数据挖掘
无限进步_2 小时前
【C++】电话号码的字母组合:从有限处理到通用解法
开发语言·c++·ide·windows·git·github·visual studio