399 lines
13 KiB
Python
399 lines
13 KiB
Python
|
|
"""
|
|||
|
|
Cookie注入测试脚本
|
|||
|
|
使用Playwright注入Cookie并验证其有效性
|
|||
|
|
支持跳转到创作者中心或小红书首页
|
|||
|
|
"""
|
|||
|
|
import asyncio
|
|||
|
|
import sys
|
|||
|
|
import json
|
|||
|
|
from pathlib import Path
|
|||
|
|
from playwright.async_api import async_playwright
|
|||
|
|
from typing import Optional, List, Dict, Any
|
|||
|
|
|
|||
|
|
|
|||
|
|
class CookieInjector:
|
|||
|
|
"""Cookie注入器"""
|
|||
|
|
|
|||
|
|
def __init__(self, headless: bool = False):
|
|||
|
|
"""
|
|||
|
|
初始化Cookie注入器
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
headless: 是否使用无头模式,False可以看到浏览器界面
|
|||
|
|
"""
|
|||
|
|
self.headless = headless
|
|||
|
|
self.playwright = None
|
|||
|
|
self.browser = None
|
|||
|
|
self.context = None
|
|||
|
|
self.page = None
|
|||
|
|
|
|||
|
|
async def init_browser(self):
|
|||
|
|
"""初始化浏览器"""
|
|||
|
|
try:
|
|||
|
|
print("正在启动浏览器...")
|
|||
|
|
|
|||
|
|
# Windows环境下设置事件循环策略
|
|||
|
|
if sys.platform == 'win32':
|
|||
|
|
try:
|
|||
|
|
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"警告: 设置事件循环策略失败: {str(e)}")
|
|||
|
|
|
|||
|
|
self.playwright = await async_playwright().start()
|
|||
|
|
|
|||
|
|
# 启动浏览器
|
|||
|
|
self.browser = await self.playwright.chromium.launch(
|
|||
|
|
headless=self.headless,
|
|||
|
|
args=['--disable-blink-features=AutomationControlled']
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 创建浏览器上下文
|
|||
|
|
self.context = await self.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'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 创建新页面
|
|||
|
|
self.page = await self.context.new_page()
|
|||
|
|
|
|||
|
|
print("浏览器初始化成功")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"浏览器初始化失败: {str(e)}")
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
async def inject_cookies(self, cookies: List[Dict[str, Any]]) -> bool:
|
|||
|
|
"""
|
|||
|
|
注入Cookie
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
cookies: Cookie列表
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
是否注入成功
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if not self.context:
|
|||
|
|
await self.init_browser()
|
|||
|
|
|
|||
|
|
print(f"正在注入 {len(cookies)} 个Cookie...")
|
|||
|
|
|
|||
|
|
# 注入Cookie到浏览器上下文
|
|||
|
|
await self.context.add_cookies(cookies)
|
|||
|
|
|
|||
|
|
print("Cookie注入成功")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Cookie注入失败: {str(e)}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
async def verify_and_navigate(self, target_page: str = 'creator') -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
验证Cookie并跳转到指定页面
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
target_page: 目标页面类型 ('creator' 或 'home')
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
验证结果字典
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if not self.page:
|
|||
|
|
return {"success": False, "error": "浏览器未初始化"}
|
|||
|
|
|
|||
|
|
# 确定目标URL
|
|||
|
|
urls = {
|
|||
|
|
'creator': 'https://creator.xiaohongshu.com',
|
|||
|
|
'home': 'https://www.xiaohongshu.com'
|
|||
|
|
}
|
|||
|
|
target_url = urls.get(target_page, urls['creator'])
|
|||
|
|
page_name = '创作者中心' if target_page == 'creator' else '小红书首页'
|
|||
|
|
|
|||
|
|
print(f"\n正在访问{page_name}: {target_url}")
|
|||
|
|
|
|||
|
|
# 访问目标页面
|
|||
|
|
await self.page.goto(target_url, wait_until='networkidle', timeout=30000)
|
|||
|
|
await asyncio.sleep(2) # 等待页面完全加载
|
|||
|
|
|
|||
|
|
# 获取当前URL和标题
|
|||
|
|
current_url = self.page.url
|
|||
|
|
title = await self.page.title()
|
|||
|
|
|
|||
|
|
print(f"当前URL: {current_url}")
|
|||
|
|
print(f"页面标题: {title}")
|
|||
|
|
|
|||
|
|
# 检查是否被重定向到登录页
|
|||
|
|
is_logged_in = 'login' not in current_url.lower()
|
|||
|
|
|
|||
|
|
if is_logged_in:
|
|||
|
|
print("Cookie验证成功,已登录状态")
|
|||
|
|
|
|||
|
|
# 尝试获取用户信息
|
|||
|
|
try:
|
|||
|
|
# 等待用户相关元素出现(如头像、用户名等)
|
|||
|
|
await self.page.wait_for_selector('[class*="avatar"], [class*="user"]', timeout=5000)
|
|||
|
|
print("检测到用户信息元素,确认登录成功")
|
|||
|
|
except Exception:
|
|||
|
|
print("未检测到明显的用户信息元素,但未跳转到登录页")
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"message": f"Cookie有效,已成功访问{page_name}",
|
|||
|
|
"url": current_url,
|
|||
|
|
"title": title,
|
|||
|
|
"logged_in": True
|
|||
|
|
}
|
|||
|
|
else:
|
|||
|
|
print("Cookie可能已失效,页面跳转到登录页")
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": "Cookie已失效或无效,页面跳转到登录页",
|
|||
|
|
"url": current_url,
|
|||
|
|
"title": title,
|
|||
|
|
"logged_in": False
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"验证过程异常: {str(e)}")
|
|||
|
|
import traceback
|
|||
|
|
traceback.print_exc()
|
|||
|
|
return {
|
|||
|
|
"success": False,
|
|||
|
|
"error": f"验证过程异常: {str(e)}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def keep_browser_open(self, duration: int = 60):
|
|||
|
|
"""
|
|||
|
|
保持浏览器打开一段时间,方便观察
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
duration: 保持打开的秒数,0表示永久打开直到手动关闭
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if duration == 0:
|
|||
|
|
print("\n浏览器将保持打开,按 Ctrl+C 关闭...")
|
|||
|
|
try:
|
|||
|
|
while True:
|
|||
|
|
await asyncio.sleep(1)
|
|||
|
|
except KeyboardInterrupt:
|
|||
|
|
print("\n用户中断,准备关闭浏览器...")
|
|||
|
|
else:
|
|||
|
|
print(f"\n浏览器将保持打开 {duration} 秒...")
|
|||
|
|
await asyncio.sleep(duration)
|
|||
|
|
print("时间到,准备关闭浏览器...")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"保持浏览器异常: {str(e)}")
|
|||
|
|
|
|||
|
|
async def close_browser(self):
|
|||
|
|
"""关闭浏览器"""
|
|||
|
|
try:
|
|||
|
|
print("\n正在关闭浏览器...")
|
|||
|
|
if self.page:
|
|||
|
|
await self.page.close()
|
|||
|
|
if self.context:
|
|||
|
|
await self.context.close()
|
|||
|
|
if self.browser:
|
|||
|
|
await self.browser.close()
|
|||
|
|
if self.playwright:
|
|||
|
|
await self.playwright.stop()
|
|||
|
|
print("浏览器已关闭")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"关闭浏览器异常: {str(e)}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def load_cookies_from_file(file_path: str) -> Optional[List[Dict[str, Any]]]:
|
|||
|
|
"""
|
|||
|
|
从文件加载Cookie
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
file_path: Cookie文件路径
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Cookie列表,失败返回None
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
cookie_file = Path(file_path)
|
|||
|
|
if not cookie_file.exists():
|
|||
|
|
print(f"Cookie文件不存在: {file_path}")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
with open(cookie_file, 'r', encoding='utf-8') as f:
|
|||
|
|
cookies = json.load(f)
|
|||
|
|
|
|||
|
|
if not isinstance(cookies, list):
|
|||
|
|
print("Cookie格式错误:必须是数组")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
if len(cookies) == 0:
|
|||
|
|
print("Cookie数组为空")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# 验证每个Cookie必须有name和value
|
|||
|
|
for cookie in cookies:
|
|||
|
|
if not cookie.get('name') or not cookie.get('value'):
|
|||
|
|
print(f"Cookie格式错误:缺少name或value字段")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
print(f"成功加载 {len(cookies)} 个Cookie")
|
|||
|
|
return cookies
|
|||
|
|
|
|||
|
|
except json.JSONDecodeError as e:
|
|||
|
|
print(f"Cookie文件JSON解析失败: {str(e)}")
|
|||
|
|
return None
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"加载Cookie文件失败: {str(e)}")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_cookie_inject(
|
|||
|
|
cookies_source: str,
|
|||
|
|
target_page: str = 'creator',
|
|||
|
|
headless: bool = False,
|
|||
|
|
keep_open: int = 0
|
|||
|
|
):
|
|||
|
|
"""
|
|||
|
|
测试Cookie注入
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
cookies_source: Cookie来源(文件路径或JSON字符串)
|
|||
|
|
target_page: 目标页面 ('creator' 或 'home')
|
|||
|
|
headless: 是否使用无头模式
|
|||
|
|
keep_open: 保持浏览器打开的秒数(0表示永久打开)
|
|||
|
|
"""
|
|||
|
|
print("="*60)
|
|||
|
|
print("Cookie注入并验证测试")
|
|||
|
|
print("="*60)
|
|||
|
|
|
|||
|
|
# 加载Cookie
|
|||
|
|
cookies = None
|
|||
|
|
|
|||
|
|
# 尝试作为文件路径加载
|
|||
|
|
if Path(cookies_source).exists():
|
|||
|
|
print(f"\n从文件加载Cookie: {cookies_source}")
|
|||
|
|
cookies = load_cookies_from_file(cookies_source)
|
|||
|
|
else:
|
|||
|
|
# 尝试作为JSON字符串解析
|
|||
|
|
try:
|
|||
|
|
print("\n尝试解析Cookie JSON字符串...")
|
|||
|
|
cookies = json.loads(cookies_source)
|
|||
|
|
if isinstance(cookies, list) and len(cookies) > 0:
|
|||
|
|
print(f"成功解析 {len(cookies)} 个Cookie")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Cookie解析失败: {str(e)}")
|
|||
|
|
|
|||
|
|
if not cookies:
|
|||
|
|
print("\n加载Cookie失败,请检查输入")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 创建注入器
|
|||
|
|
injector = CookieInjector(headless=headless)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# 初始化浏览器
|
|||
|
|
await injector.init_browser()
|
|||
|
|
|
|||
|
|
# 注入Cookie
|
|||
|
|
inject_success = await injector.inject_cookies(cookies)
|
|||
|
|
|
|||
|
|
if not inject_success:
|
|||
|
|
print("\nCookie注入失败")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 验证并跳转
|
|||
|
|
result = await injector.verify_and_navigate(target_page)
|
|||
|
|
|
|||
|
|
print("\n" + "="*60)
|
|||
|
|
print("验证结果")
|
|||
|
|
print("="*60)
|
|||
|
|
|
|||
|
|
if result.get('success'):
|
|||
|
|
print(f"状态: 成功")
|
|||
|
|
print(f"消息: {result.get('message')}")
|
|||
|
|
print(f"URL: {result.get('url')}")
|
|||
|
|
print(f"标题: {result.get('title')}")
|
|||
|
|
print(f"登录状态: {'已登录' if result.get('logged_in') else '未登录'}")
|
|||
|
|
else:
|
|||
|
|
print(f"状态: 失败")
|
|||
|
|
print(f"错误: {result.get('error')}")
|
|||
|
|
if result.get('url'):
|
|||
|
|
print(f"当前URL: {result.get('url')}")
|
|||
|
|
|
|||
|
|
# 保持浏览器打开
|
|||
|
|
if keep_open >= 0:
|
|||
|
|
await injector.keep_browser_open(keep_open)
|
|||
|
|
|
|||
|
|
except KeyboardInterrupt:
|
|||
|
|
print("\n\n用户中断测试")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"\n测试过程异常: {str(e)}")
|
|||
|
|
import traceback
|
|||
|
|
traceback.print_exc()
|
|||
|
|
finally:
|
|||
|
|
await injector.close_browser()
|
|||
|
|
|
|||
|
|
print("\n" + "="*60)
|
|||
|
|
print("测试完成")
|
|||
|
|
print("="*60)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def main():
|
|||
|
|
"""主函数"""
|
|||
|
|
print("="*60)
|
|||
|
|
print("小红书Cookie注入测试工具")
|
|||
|
|
print("="*60)
|
|||
|
|
|
|||
|
|
print("\n功能说明:")
|
|||
|
|
print("1. 注入Cookie到浏览器")
|
|||
|
|
print("2. 验证Cookie有效性")
|
|||
|
|
print("3. 跳转到指定页面(创作者中心/小红书首页)")
|
|||
|
|
|
|||
|
|
print("\n" + "="*60)
|
|||
|
|
|
|||
|
|
# 输入Cookie来源
|
|||
|
|
print("\n请输入Cookie来源:")
|
|||
|
|
print("1. 输入Cookie文件路径(如: cookies.json)")
|
|||
|
|
print("2. 直接粘贴JSON格式的Cookie")
|
|||
|
|
|
|||
|
|
cookies_source = input("\nCookie来源: ").strip()
|
|||
|
|
|
|||
|
|
if not cookies_source:
|
|||
|
|
print("Cookie来源不能为空")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 选择目标页面
|
|||
|
|
print("\n请选择目标页面:")
|
|||
|
|
print("1. 创作者中心(creator.xiaohongshu.com)")
|
|||
|
|
print("2. 小红书首页(www.xiaohongshu.com)")
|
|||
|
|
|
|||
|
|
page_choice = input("\n选择 (1 或 2, 默认为 1): ").strip()
|
|||
|
|
target_page = 'home' if page_choice == '2' else 'creator'
|
|||
|
|
|
|||
|
|
# 选择浏览器模式
|
|||
|
|
headless_choice = input("\n是否使用无头模式?(y/n, 默认为 n): ").strip().lower()
|
|||
|
|
headless = headless_choice == 'y'
|
|||
|
|
|
|||
|
|
# 选择保持打开时间
|
|||
|
|
keep_open_input = input("\n保持浏览器打开时间(秒,0表示直到手动关闭,默认60): ").strip()
|
|||
|
|
try:
|
|||
|
|
keep_open = int(keep_open_input) if keep_open_input else 60
|
|||
|
|
except ValueError:
|
|||
|
|
keep_open = 60
|
|||
|
|
|
|||
|
|
# 执行测试
|
|||
|
|
await test_cookie_inject(
|
|||
|
|
cookies_source=cookies_source,
|
|||
|
|
target_page=target_page,
|
|||
|
|
headless=headless,
|
|||
|
|
keep_open=keep_open
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# Windows环境下设置事件循环策略
|
|||
|
|
if sys.platform == 'win32':
|
|||
|
|
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|||
|
|
|
|||
|
|
# 运行测试
|
|||
|
|
asyncio.run(main())
|