92 lines
3.6 KiB
Python
92 lines
3.6 KiB
Python
|
|
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("未发现登录状态,请手动操作并将状态保存...")
|
|||
|
|
# 注意: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)
|
|||
|
|
|
|||
|
|
# 2. 等待抢购时间
|
|||
|
|
snatch_time = config.get("snatch_time")
|
|||
|
|
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("点击立即购买")
|
|||
|
|
|
|||
|
|
# 处理 SKU 选择(如果弹出 SKU 选择框)
|
|||
|
|
# 这里简单起见,如果弹出了规格选择,点击第一个选项并确定
|
|||
|
|
# 实际需要根据 config 中的 sku_id 进行精准点击
|
|||
|
|
confirm_btn = page.get_by_text("确定")
|
|||
|
|
if await confirm_btn.is_visible():
|
|||
|
|
await confirm_btn.click()
|
|||
|
|
print("点击确定(SKU)")
|
|||
|
|
|
|||
|
|
# 进入确认订单页面后,点击“提交订单”
|
|||
|
|
# 提交订单按钮通常在底部,文字为“提交订单”
|
|||
|
|
submit_btn = page.get_by_text("提交订单")
|
|||
|
|
await submit_btn.wait_for(state="visible", timeout=5000)
|
|||
|
|
await submit_btn.click()
|
|||
|
|
print("点击提交订单!抢购请求已发送!")
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
config = load_config()
|
|||
|
|
asyncio.run(snatch(config))
|