diff --git a/src/tasks/surugaya.py b/src/tasks/surugaya.py index 236e11c..508accd 100644 --- a/src/tasks/surugaya.py +++ b/src/tasks/surugaya.py @@ -715,6 +715,18 @@ class SurugayaPurchaseTask(BasePurchaseTask): expiry_timer = (await page.locator(".expiry-timer-wrap .timer .time").inner_text()).strip() logger.debug(f"[任务 {self.task_id}] 倒计时:{expiry_timer}") + # 页面可能默认展示"PayPayアプリでQRコードをスキャンして支払い",需点击兜底链接才能进入账号登录/验证码环节 + logger.debug(f"[任务 {self.task_id}] 检测是否存在QR码支付页的兜底链接...") + qr_fallback_link = page.get_by_role("link", name="こちら") + try: + await qr_fallback_link.wait_for(state="visible", timeout=3000) + logger.debug(f"[任务 {self.task_id}] 检测到PayPay QR码支付页,点击'スキャンできない場合はこちら'兜底链接") + await page.wait_for_timeout(random.randint(500, 1000)) + await qr_fallback_link.click() + logger.debug(f"[任务 {self.task_id}] 已点击QR码兜底链接") + except PlaywrightTimeoutError: + logger.debug(f"[任务 {self.task_id}] 未检测到QR码支付页兜底链接,跳过") + # 判断是否直接进入了支付环节 is_submit_btn = False logger.debug(f"[任务 {self.task_id}] 检测是否直接进入了支付环节") @@ -796,39 +808,27 @@ class SurugayaPurchaseTask(BasePurchaseTask): logger.error(f"[任务 {self.task_id}] 支付异常:{e}") raise e + async def _race_locators(self, locators: dict, *, timeout_sec: float = 10.0) -> str: + """并发等待多个 locator,谁先可见就返回对应的 key,全部超时则抛异常""" + try: + async with asyncio.timeout(timeout_sec): + tasks = {key: asyncio.create_task(locator.wait_for(state="visible")) for key, locator in locators.items()} + done, pending = await asyncio.wait(tasks.values(), return_when=asyncio.FIRST_COMPLETED) + for p_task in pending: + p_task.cancel() + first_completed = done.pop() + for key, task in tasks.items(): + if task is first_completed: + return key + except TimeoutError: + raise Exception(f"等待 {list(locators.keys())} 之一超时(限时 {timeout_sec} 秒)") + async def _wait_for_paypay_login_result(self, page: Page, login_iframe, *, timeout_sec: float = 10.0): """同时等待支付按钮或短信验证码登录页面出现(异步竞赛版)""" logger.debug(f"[任务 {self.task_id}] 登录提交后同时等待支付按钮或短信验证码登录页面出现,限时 {timeout_sec} 秒") submit_btn = page.locator(".submit-button-wrap button.submit-button") sms_prompt = login_iframe.get_by_text("SMSで届いた認証コードを入力してください").first - - # 1. 使用 Python 标准的 asyncio.timeout 控制总时间 - try: - async with asyncio.timeout(timeout_sec): - - # 2. 创建两个并发等待任务 - task_submit = asyncio.create_task(submit_btn.wait_for(state="visible")) - task_sms = asyncio.create_task(sms_prompt.wait_for(state="visible")) - - # 3. 展开速度竞赛 - done, pending = await asyncio.wait( - {task_submit, task_sms}, - return_when=asyncio.FIRST_COMPLETED - ) - - # 4. 谁先到终点,就取消另一个并返回对应结果 - for p_task in pending: - p_task.cancel() - - first_completed = done.pop() - if first_completed == task_submit: - return "submit" - elif first_completed == task_sms: - return "sms" - - except TimeoutError: - # 当 asyncio.timeout 超时,会自动抛出内置的 TimeoutError - raise Exception(f"登录后等待支付按钮或短信验证码超时(限时 {timeout_sec} 秒)") + return await self._race_locators({"submit": submit_btn, "sms": sms_prompt}, timeout_sec=timeout_sec) async def _wait_for_manual_sms_verification(self, page: Page, login_iframe, *, timeout_sec: float = 180): """