""" 独立抢购脚本(命令行模式)— 优化版 用法: python main.py """ import asyncio import yaml from playwright.async_api import async_playwright from utils.stealth import stealth_async from utils.auth import Authenticator from utils.timer import PrecisionTimer BUY_TEXTS = ["立即抢购", "立即购买", "马上抢", "立即秒杀"] MAX_RETRIES = 5 CONCURRENT_TABS = 2 async def snatch(config): auth = Authenticator(config.get("auth_file", "auth_state.json")) timer = PrecisionTimer() timer.sync_time() async with async_playwright() as p: browser, context = await auth.get_context( p, headless=config.get("headless", False)) page = await context.new_page() await stealth_async(page) target_url = config.get("target_url") if not target_url: print("错误: 未配置 target_url") return # 1. 预热:先打开页面 print(f"正在打开商品页面: {target_url}") await page.goto(target_url, wait_until='networkidle', timeout=20000) # 如果未登录,处理登录 if not auth.has_auth(): print("未发现登录状态,请手动操作并将状态保存...") await auth.login(target_url) await context.close() await browser.close() browser, context = await auth.get_context( p, headless=config.get("headless", False)) page = await context.new_page() await stealth_async(page) print("已重新加载登录状态,正在打开商品页面...") await page.goto(target_url, wait_until='networkidle', timeout=20000) # 2. 等待抢购时间(提前 500ms 触发) snatch_time = config.get("snatch_time") if snatch_time: await timer.wait_until_early(snatch_time, early_ms=500) # 3. 并发抢购 pages = [page] for _ in range(CONCURRENT_TABS - 1): try: p2 = await context.new_page() await stealth_async(p2) await p2.goto(target_url, wait_until='commit', timeout=10000) pages.append(p2) except Exception: pass tasks = [_do_purchase(pg, i) for i, pg in enumerate(pages)] results = await asyncio.gather(*tasks, return_exceptions=True) for r in results: print(f"结果: {r}") if isinstance(r, str) and ('已提交' in r or '已发送' in r): print("✅ 抢购成功!") break await asyncio.sleep(10) await browser.close() async def _do_purchase(page, tab_index=0): """极速购买流程""" for attempt in range(MAX_RETRIES): try: await page.reload(wait_until='commit', timeout=8000) await asyncio.sleep(0.3) # 点击购买按钮 buy_btn = None for text in BUY_TEXTS: loc = page.get_by_text(text, exact=False) try: await loc.first.wait_for(state="visible", timeout=1500) buy_btn = loc.first break except Exception: continue if not buy_btn: if attempt < MAX_RETRIES - 1: print(f"tab{tab_index} 第{attempt+1}次未找到按钮,重试...") await asyncio.sleep(0.05) continue return f"tab{tab_index}: 未找到购买按钮" await buy_btn.click(timeout=2000) print(f"tab{tab_index}: 点击购买按钮") # 处理 SKU 弹窗 try: confirm_btn = page.get_by_text("确定", exact=True) await confirm_btn.first.wait_for(state="visible", timeout=1500) sku_sel = ('.sku-item:not(.disabled), ' '.sku_item:not(.disabled), ' '[class*="sku"] [class*="item"]' ':not([class*="disabled"])') sku_items = page.locator(sku_sel) if await sku_items.count() > 0: await sku_items.first.click() print(f"tab{tab_index}: 自动选择 SKU") await asyncio.sleep(0.1) await confirm_btn.first.click(timeout=2000) print(f"tab{tab_index}: 点击确定") except Exception: pass # 提交订单 submit_btn = page.get_by_text("提交订单") await submit_btn.wait_for(state="visible", timeout=6000) await submit_btn.click() return f"tab{tab_index}: 抢购请求已提交" except Exception as e: if attempt < MAX_RETRIES - 1: print(f"tab{tab_index} 第{attempt+1}次失败: {e},重试...") await asyncio.sleep(0.05) else: return f"tab{tab_index}: 抢购失败: {e}" return f"tab{tab_index}: 重试次数用尽" def load_config(): with open("config.yaml", "r", encoding="utf-8") as f: return yaml.safe_load(f) if __name__ == "__main__": config = load_config() asyncio.run(snatch(config))