feat: Web管理系统 + Docker支持

- 多账号管理(异步登录、状态轮询)
- 购物车预售商品同步(倒计时/定时开售)
- 定时抢购(自动刷新、SKU选择、重试机制)
- 账号隔离调度(同账号顺序、跨账号并行)
- Web面板(任务分组、实时倒计时、批量操作)
- Dockerfile + docker-compose
This commit is contained in:
2026-03-18 13:38:17 +08:00
parent 7aea2ca2a8
commit 822a4636c0
28 changed files with 1966 additions and 66 deletions

92
main.py
View File

@@ -24,20 +24,16 @@ async def snatch(config):
# 1. 预热:先打开页面
print(f"正在打开商品页面: {target_url}")
await page.goto(target_url)
# 如果未登录,可能需要在这里处理扫码
# 如果未登录,处理登录
if not auth.has_auth():
print("未发现登录状态,请手动操作并将状态保存...")
# 注意login 会启动一个新的浏览器窗口
await auth.login(target_url)
# 登录完成后,我们需要关闭当前空的 context 并重新加载带有 cookie 的 context
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)
@@ -46,42 +42,68 @@ async def snatch(config):
if snatch_time:
await timer.wait_until(snatch_time)
# 3. 抢购核心逻辑 (H5 流程)
# 注意:这里的选择器需要根据微店 H5 实际页面结构进行微调
# 这里演示一般的微店抢购流程:点击购买 -> 选择规格 -> 确定 -> 提交订单
try:
# 刷新页面以获取最新状态(可选,视具体页面倒计时逻辑而定)
# await page.reload()
# 点击“立即购买”按钮
# 微店 H5 常见的购买按钮类名类似于 .buy-btn, .footer-buy
# 我们尝试使用 text 匹配
buy_button = page.get_by_text("立即购买")
await buy_button.click()
print("点击立即购买")
# 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)
# 处理 SKU 选择(如果弹出 SKU 选择框
# 这里简单起见,如果弹出了规格选择,点击第一个选项并确定
# 实际需要根据 config 中的 sku_id 进行精准点击
confirm_btn = page.get_by_text("确定")
if await confirm_btn.is_visible():
await confirm_btn.click()
print("点击确定SKU")
# 点击购买按钮(兼容多种文案
buy_btn = None
for text in ["立即抢购", "立即购买", "马上抢", "立即秒杀"]:
loc = page.get_by_text(text, exact=False)
if await loc.count() > 0:
buy_btn = loc.first
break
# 进入确认订单页面后,点击“提交订单”
# 提交订单按钮通常在底部,文字为“提交订单”
submit_btn = page.get_by_text("提交订单")
await submit_btn.wait_for(state="visible", timeout=5000)
await submit_btn.click()
print("点击提交订单!抢购请求已发送!")
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}")
except Exception as e:
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)