perf: 抢购流程第一层优化
- timer: 多NTP源取中位数提高精度,新增wait_until_early提前触发 - snatcher: 并发2个tab竞争抢购,reload用commit级别不等渲染 - snatcher: 重试间隔从300ms降到50ms,最大重试5次 - snatcher: 用waitForSelector替代固定sleep,按钮出现即点击 - snatcher: 开售前500ms发起reload,抢占先机 - main.py: 同步所有优化
This commit is contained in:
157
main.py
157
main.py
@@ -1,18 +1,27 @@
|
||||
"""
|
||||
独立抢购脚本(命令行模式)— 优化版
|
||||
用法: python main.py
|
||||
"""
|
||||
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
|
||||
|
||||
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))
|
||||
browser, context = await auth.get_context(
|
||||
p, headless=config.get("headless", False))
|
||||
page = await context.new_page()
|
||||
await stealth_async(page)
|
||||
|
||||
@@ -23,7 +32,7 @@ async def snatch(config):
|
||||
|
||||
# 1. 预热:先打开页面
|
||||
print(f"正在打开商品页面: {target_url}")
|
||||
await page.goto(target_url)
|
||||
await page.goto(target_url, wait_until='networkidle', timeout=20000)
|
||||
|
||||
# 如果未登录,处理登录
|
||||
if not auth.has_auth():
|
||||
@@ -31,83 +40,111 @@ async def snatch(config):
|
||||
await auth.login(target_url)
|
||||
await context.close()
|
||||
await browser.close()
|
||||
browser, context = await auth.get_context(p, headless=config.get("headless", False))
|
||||
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)
|
||||
await page.goto(target_url, wait_until='networkidle',
|
||||
timeout=20000)
|
||||
|
||||
# 2. 等待抢购时间
|
||||
# 2. 等待抢购时间(提前 500ms 触发)
|
||||
snatch_time = config.get("snatch_time")
|
||||
if snatch_time:
|
||||
await timer.wait_until(snatch_time)
|
||||
await timer.wait_until_early(snatch_time, early_ms=500)
|
||||
|
||||
# 3. 抢购核心逻辑
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
# 3. 并发抢购
|
||||
pages = [page]
|
||||
for _ in range(CONCURRENT_TABS - 1):
|
||||
try:
|
||||
# 刷新页面,让预售按钮变为可点击
|
||||
if attempt > 0:
|
||||
await asyncio.sleep(0.3)
|
||||
await page.reload(wait_until='domcontentloaded', timeout=10000)
|
||||
await asyncio.sleep(0.5)
|
||||
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
|
||||
|
||||
# 点击购买按钮(兼容多种文案)
|
||||
buy_btn = None
|
||||
for text in ["立即抢购", "立即购买", "马上抢", "立即秒杀"]:
|
||||
loc = page.get_by_text(text, exact=False)
|
||||
if await loc.count() > 0:
|
||||
buy_btn = loc.first
|
||||
break
|
||||
tasks = [_do_purchase(pg, i) for i, pg in enumerate(pages)]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
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("点击提交订单!抢购请求已发送!")
|
||||
for r in results:
|
||||
print(f"结果: {r}")
|
||||
if isinstance(r, str) and ('已提交' in r or '已发送' in r):
|
||||
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()
|
||||
|
||||
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user