import asyncio import json import os from playwright.async_api import async_playwright from utils.stealth import stealth_async AUTH_DIR = os.path.join(os.path.dirname(__file__), '..', '..', 'data', 'auth') LOGIN_URL = "https://sso.weidian.com/login/index.php" def get_auth_path(account_id): os.makedirs(AUTH_DIR, exist_ok=True) return os.path.join(AUTH_DIR, f'auth_state_{account_id}.json') def has_auth(account_id): path = get_auth_path(account_id) return os.path.exists(path) and os.path.getsize(path) > 10 async def login_with_password(account_id, phone, password): """ 用 Playwright 模拟浏览器登录微店,通过监听 API 响应提取 cookie。 流程: 1. 打开登录页 2. 点击 #login_init_by_login 进入登录表单 3. 点击"账号密码登录" tab 4. 填写手机号、密码 5. 点击 #login_pwd_submit 6. 监听 /user/login 响应,从中提取 cookie 并保存 """ login_result = {'success': False, 'msg': '登录超时', 'cookies': []} p = await async_playwright().start() browser = await p.chromium.launch( headless=True, args=['--disable-gpu', '--no-sandbox'] ) device = p.devices['iPhone 13'] context = await browser.new_context(**device) page = await context.new_page() await stealth_async(page) # 监听登录接口响应 async def on_response(response): if 'user/login' in response.url and response.status == 200: try: data = await response.json() status = data.get('status', {}) if status.get('status_code') == 0: login_result['success'] = True login_result['msg'] = '登录成功' login_result['cookies'] = data.get('result', {}).get('cookie', []) else: login_result['msg'] = f"登录失败: {status.get('status_reason', '未知错误')}" except Exception as e: login_result['msg'] = f"解析登录响应失败: {e}" page.on("response", on_response) try: await page.goto(LOGIN_URL, wait_until='networkidle', timeout=15000) await asyncio.sleep(1) # 点击"登录"进入表单 await page.locator('#login_init_by_login').click(timeout=5000) await asyncio.sleep(1.5) # 点击"账号密码登录" tab try: await page.locator('h4.login_content_h4 span', has_text="账号密码登录").click(timeout=3000) await asyncio.sleep(0.5) except Exception: pass # 填写手机号(逐字输入,触发 JS 事件) phone_input = page.locator('input[placeholder*="手机号"]').first await phone_input.click() await phone_input.fill("") await page.keyboard.type(phone, delay=50) # 填写密码 pwd_input = page.locator('input[placeholder*="登录密码"], input[type="password"]').first await pwd_input.click() await pwd_input.fill("") await page.keyboard.type(password, delay=50) await asyncio.sleep(0.5) # 点击登录 await page.locator('#login_pwd_submit').click(timeout=5000) # 等待 API 响应 await asyncio.sleep(5) if login_result['success'] and login_result['cookies']: # 从 API 响应中提取 cookie,写入 context for c in login_result['cookies']: await context.add_cookies([{ "name": c.get("name", ""), "value": c.get("value", ""), "domain": c.get("domain", ".weidian.com"), "path": c.get("path", "/"), "httpOnly": c.get("httpOnly", False), "secure": c.get("secure", False), "sameSite": "Lax", }]) # 保存完整的 storage_state auth_path = get_auth_path(account_id) await context.storage_state(path=auth_path) return True, "登录成功" return False, login_result['msg'] except Exception as e: return False, f"登录过程出错: {e}" finally: await browser.close() await p.stop() async def login_with_api(account_id, phone, password): """ 通过微店 SSO API 直接登录(备选方案,速度快但更容易触发风控)。 """ import aiohttp login_api = "https://sso.weidian.com/user/login" headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) " "AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", "Referer": "https://sso.weidian.com/login/index.php", "Origin": "https://sso.weidian.com", } payload = {"phone": phone, "password": password, "loginMode": "password"} try: async with aiohttp.ClientSession() as session: async with session.post(login_api, data=payload, headers=headers) as resp: data = await resp.json() status_code = data.get("status", {}).get("status_code", -1) status_reason = data.get("status", {}).get("status_reason", "未知错误") if status_code == 0: api_cookies = data.get("result", {}).get("cookie", []) pw_cookies = [] for c in api_cookies: pw_cookies.append({ "name": c.get("name", ""), "value": c.get("value", ""), "domain": c.get("domain", ".weidian.com"), "path": c.get("path", "/"), "expires": -1, "httpOnly": c.get("httpOnly", False), "secure": c.get("secure", False), "sameSite": "Lax", }) state = {"cookies": pw_cookies, "origins": []} auth_path = get_auth_path(account_id) with open(auth_path, 'w', encoding='utf-8') as f: json.dump(state, f, ensure_ascii=False, indent=2) return True, "API登录成功" else: return False, f"API登录失败: {status_reason}" except Exception as e: return False, f"API登录出错: {e}" async def get_browser_context(playwright_instance, account_id, headless=True): """创建带有已保存登录状态的浏览器上下文""" browser = await playwright_instance.chromium.launch( headless=headless, args=['--disable-gpu', '--no-sandbox'] ) device = playwright_instance.devices['iPhone 13'] auth_path = get_auth_path(account_id) if has_auth(account_id): context = await browser.new_context(**device, storage_state=auth_path) else: context = await browser.new_context(**device) return browser, context