social_media_auto_publisher/auth/xiaohongshu_auth.py
2025-11-12 00:28:07 +08:00

276 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
小红书认证实现
"""
import asyncio
from typing import Optional
from playwright.async_api import Page
from .base_auth import BaseAuth
from ..core.models import PlatformType, AccountInfo
from ..config.platform_config import get_platform_url, get_selectors, get_wait_times
from ..utils.logger import get_platform_logger
from ..utils.exceptions import LoginFailedError, AuthenticationError
class XiaoHongShuAuth(BaseAuth):
"""小红书认证实现"""
def __init__(self):
super().__init__(PlatformType.XIAOHONGSHU)
self.login_url = get_platform_url(PlatformType.XIAOHONGSHU, "login")
self.logger = get_platform_logger("xiaohongshu")
async def login(self, account_info: AccountInfo, headless: bool = False) -> bool:
"""
执行小红书登录流程
Args:
account_info: 账号信息
headless: 是否使用无头模式
Returns:
登录是否成功
"""
try:
self.logger.info(f"开始小红书登录: {account_info.username}")
# 创建页面
page = await self.create_page(account_info, headless)
# 尝试加载现有Cookie
cookies_loaded = await self.load_cookies(page, account_info)
if cookies_loaded:
# 验证Cookie是否有效
if await self._validate_existing_session(page):
self.logger.info("使用现有Cookie登录成功")
return True
else:
self.logger.info("Cookie已失效需要重新登录")
# 执行扫码登录
success = await self._perform_qr_login(page, account_info)
if success:
# 保存新的Cookie
await self.save_cookies(page, account_info)
self.logger.success("小红书登录完成")
else:
self.logger.failure("小红书登录失败")
# 关闭页面
await page.close()
return success
except Exception as e:
self.logger.error(f"小红书登录异常: {e}")
raise LoginFailedError(f"小红书登录失败: {e}", platform="xiaohongshu")
async def check_login_status(self, page: Page) -> bool:
"""
检查小红书登录状态
Args:
page: Playwright页面对象
Returns:
是否已登录
"""
try:
selectors = get_selectors(PlatformType.XIAOHONGSHU, "login", "login_success_indicators")
for selector in selectors:
try:
element = await page.wait_for_selector(selector, timeout=3000)
if element and await element.is_visible():
self.logger.debug(f"检测到登录状态指示器: {selector}")
return True
except:
continue
# 检查URL是否包含登录相关页面
current_url = page.url
if "login" not in current_url.lower() and "auth" not in current_url.lower():
# 尝试检测用户信息元素
user_selectors = [
"[data-testid='user-info']",
".user-name",
".avatar img",
"[class*='user'] [class*='avatar']"
]
for selector in user_selectors:
try:
element = await page.wait_for_selector(selector, timeout=2000)
if element and await element.is_visible():
self.logger.debug(f"检测到用户信息元素: {selector}")
return True
except:
continue
return False
except Exception as e:
self.logger.warning(f"登录状态检查失败: {e}")
return False
async def _validate_existing_session(self, page: Page) -> bool:
"""
验证现有会话是否有效
Args:
page: Playwright页面对象
Returns:
会话是否有效
"""
try:
# 导航到创作者中心首页
await self.safe_goto(page, self.login_url)
await asyncio.sleep(3)
# 检查是否需要登录
if await self.check_login_status(page):
self.logger.debug("现有会话有效")
return True
else:
self.logger.debug("现有会话无效,需要重新登录")
return False
except Exception as e:
self.logger.warning(f"会话验证失败: {e}")
return False
async def _perform_qr_login(self, page: Page, account_info: AccountInfo) -> bool:
"""
执行扫码登录流程
Args:
page: Playwright页面对象
account_info: 账号信息
Returns:
登录是否成功
"""
try:
self.logger.info("开始扫码登录流程")
# 导航到登录页面
if not await self.safe_goto(page, self.login_url):
raise LoginFailedError("无法访问登录页面")
# 等待二维码加载
qr_selectors = get_selectors(PlatformType.XIAOHONGSHU, "login", "qr_code")
qr_code_loaded = False
for selector in qr_selectors:
if await self.wait_for_element(page, selector, timeout=15000):
qr_code_loaded = True
self.logger.info("二维码已加载请使用小红书App扫码")
break
if not qr_code_loaded:
# 尝试其他可能的二维码选择器
alternative_selectors = [
"img[src*='qr']",
"canvas",
".qrcode",
"[class*='qr']",
"[id*='qr']"
]
for selector in alternative_selectors:
if await self.wait_for_element(page, selector, timeout=5000):
qr_code_loaded = True
self.logger.info(f"检测到二维码元素: {selector}")
break
if not qr_code_loaded:
self.logger.warning("未检测到二维码,可能已经登录")
# 检查是否已经登录
if await self.check_login_status(page):
return True
else:
raise LoginFailedError("二维码加载失败")
# 等待用户扫码登录
wait_times = get_wait_times(PlatformType.XIAOHONGSHU, "login")
login_timeout = wait_times.get("login_timeout", 300)
success = await self.wait_for_login_success(page, timeout=login_timeout)
if success:
self.logger.info("扫码登录成功")
return True
else:
raise LoginFailedError("扫码登录超时")
except Exception as e:
self.logger.error(f"扫码登录失败: {e}")
return False
async def force_relogin(self, account_info: AccountInfo, headless: bool = False) -> bool:
"""
强制重新登录
Args:
account_info: 账号信息
headless: 是否使用无头模式
Returns:
登录是否成功
"""
try:
self.logger.info("强制重新登录")
# 删除现有Cookie
self.delete_cookies(account_info)
# 执行登录
return await self.login(account_info, headless)
except Exception as e:
self.logger.error(f"强制重新登录失败: {e}")
return False
async def test_login(self, account_info: AccountInfo) -> bool:
"""
测试登录状态
Args:
account_info: 账号信息
Returns:
登录是否有效
"""
try:
self.logger.info("测试登录状态")
# 创建页面
page = await self.create_page(account_info, headless=True)
# 加载Cookie
cookies_loaded = await self.load_cookies(page, account_info)
if not cookies_loaded:
self.logger.info("没有找到Cookie文件")
await page.close()
return False
# 验证登录状态
login_valid = await self._validate_existing_session(page)
await page.close()
if login_valid:
self.logger.info("登录状态有效")
return True
else:
self.logger.info("登录状态无效")
return False
except Exception as e:
self.logger.error(f"登录状态测试失败: {e}")
return False