Files
weidian/main.py
Jeason 822a4636c0 feat: Web管理系统 + Docker支持
- 多账号管理(异步登录、状态轮询)
- 购物车预售商品同步(倒计时/定时开售)
- 定时抢购(自动刷新、SKU选择、重试机制)
- 账号隔离调度(同账号顺序、跨账号并行)
- Web面板(任务分组、实时倒计时、批量操作)
- Dockerfile + docker-compose
2026-03-18 13:38:17 +08:00

114 lines
4.4 KiB
Python
Raw 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
import yaml
import time
from playwright.async_api import async_playwright
from utils.stealth import stealth_async
from utils.auth import Authenticator
from utils.timer import PrecisionTimer
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)
# 如果未登录,处理登录
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)
# 2. 等待抢购时间
snatch_time = config.get("snatch_time")
if snatch_time:
await timer.wait_until(snatch_time)
# 3. 抢购核心逻辑
max_retries = 3
for attempt in range(max_retries):
try:
# 刷新页面,让预售按钮变为可点击
if attempt > 0:
await asyncio.sleep(0.3)
await page.reload(wait_until='domcontentloaded', timeout=10000)
await asyncio.sleep(0.5)
# 点击购买按钮(兼容多种文案)
buy_btn = None
for text in ["立即抢购", "立即购买", "马上抢", "立即秒杀"]:
loc = page.get_by_text(text, exact=False)
if await loc.count() > 0:
buy_btn = loc.first
break
if not buy_btn:
if attempt < max_retries - 1:
print(f"{attempt+1}次未找到购买按钮,重试...")
continue
print("错误: 未找到购买按钮")
break
await buy_btn.click(timeout=3000)
print("点击购买按钮")
# 处理 SKU 选择(如果弹出规格选择框)
await asyncio.sleep(0.5)
try:
confirm_btn = page.get_by_text("确定", exact=True)
if await confirm_btn.count() > 0 and await confirm_btn.first.is_visible():
# 自动选择第一个可用的 SKU 选项
sku_items = page.locator('.sku-item:not(.disabled), .sku_item:not(.disabled), [class*="sku"] [class*="item"]:not([class*="disabled"])')
if await sku_items.count() > 0:
await sku_items.first.click()
print("自动选择 SKU")
await asyncio.sleep(0.3)
await confirm_btn.first.click(timeout=3000)
print("点击确定SKU")
except Exception:
pass
# 提交订单
submit_btn = page.get_by_text("提交订单")
await submit_btn.wait_for(state="visible", timeout=8000)
await submit_btn.click()
print("点击提交订单!抢购请求已发送!")
break
except Exception as e:
if attempt < max_retries - 1:
print(f"{attempt+1}次尝试失败: {e},重试...")
else:
print(f"抢购失败: {e}")
# 保持浏览器打开一段时间查看结果
await asyncio.sleep(10)
await browser.close()
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))