Files
weidian/main.py

151 lines
5.3 KiB
Python
Raw Normal View History

"""
独立抢购脚本命令行模式 优化版
用法: python main.py
"""
2026-02-02 09:27:49 +08:00
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
2026-02-02 09:27:49 +08:00
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))
2026-02-02 09:27:49 +08:00
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)
# 如果未登录,处理登录
2026-02-02 09:27:49 +08:00
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))
2026-02-02 09:27:49 +08:00
page = await context.new_page()
await stealth_async(page)
print("已重新加载登录状态,正在打开商品页面...")
await page.goto(target_url, wait_until='networkidle',
timeout=20000)
2026-02-02 09:27:49 +08:00
# 2. 等待抢购时间(提前 500ms 触发)
2026-02-02 09:27:49 +08:00
snatch_time = config.get("snatch_time")
if snatch_time:
await timer.wait_until_early(snatch_time, early_ms=500)
2026-02-02 09:27:49 +08:00
# 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}: 重试次数用尽"
2026-02-02 09:27:49 +08:00
2026-02-02 09:27:49 +08:00
def load_config():
with open("config.yaml", "r", encoding="utf-8") as f:
return yaml.safe_load(f)
2026-02-02 09:27:49 +08:00
if __name__ == "__main__":
config = load_config()
asyncio.run(snatch(config))