""" 购物车预售商品抓取服务 通过 Playwright 打开购物车页面,从 DOM 的 item_warp 提取商品信息 """ import asyncio from playwright.async_api import async_playwright from utils.stealth import stealth_async from server.services.auth_service import get_browser_context, has_auth CART_URL = "https://weidian.com/new-cart/index.php" # 提取购物车商品的 JS,与 test_cart.py 保持一致 EXTRACT_JS = """() => { const R = []; const sws = document.querySelectorAll( 'div.shop_info.cart_content div.shop_warp' ); for (const sw of sws) { const sn = (sw.querySelector('.shop_name') || {}).textContent || ''; const iws = sw.querySelectorAll('.item_warp'); for (const iw of iws) { const o = { shop_name: sn.trim(), cart_item_id: iw.id, title: '', sku_name: '', price: '', is_presale: false, countdown_text: '', sale_time: '', presale_type: '' }; const te = iw.querySelector('.item_title'); if (te) o.title = te.textContent.trim(); const sk = iw.querySelector('.item_sku'); if (sk) o.sku_name = sk.textContent.trim(); const pr = iw.querySelector('.item_prices'); if (pr) o.price = pr.textContent.replace(/[^\\d.]/g, ''); const de = iw.querySelector('.item_desc'); if (de) { const dt = de.querySelector('.title'); const dd = de.querySelector('.desc'); const wm = de.querySelector('.warn_msg'); if (dt && /\\u5b9a\\u65f6\\s*\\u5f00\\u552e/.test(dt.textContent)) { o.is_presale = true; const d = dd ? dd.textContent.trim() : ''; const w = wm ? wm.textContent.trim() : ''; if (d.includes('\\u8ddd\\u79bb\\u5f00\\u552e\\u8fd8\\u5269')) { o.presale_type = 'countdown'; o.countdown_text = w; } else if (d.includes('\\u5f00\\u552e\\u65f6\\u95f4')) { o.presale_type = 'scheduled'; o.sale_time = w; } else { o.presale_type = 'unknown'; o.countdown_text = w; } } } R.push(o); } } return R; }""" async def fetch_cart_presale_items(account_id): """ 获取指定账号购物车中的预售商品列表 返回: (success, items_or_msg) """ if not has_auth(account_id): return False, "账号未登录" async with async_playwright() as p: browser, context = await get_browser_context( p, account_id, headless=True ) page = await context.new_page() await stealth_async(page) try: await page.goto( CART_URL, wait_until="networkidle", timeout=20000 ) await asyncio.sleep(3) if "login" in page.url.lower(): await browser.close() return False, "登录态已过期,请重新登录" if "error" in page.url.lower(): await browser.close() return False, "购物车页面加载失败" except Exception as e: await browser.close() return False, f"打开购物车失败: {e}" raw_items = await page.evaluate(EXTRACT_JS) await browser.close() # 只返回预售商品 presale = [it for it in raw_items if it.get("is_presale")] return True, presale