PayPay QR码兜底链接处理 + 抽离locator竞赛方法

- 在支付跳转流程中检测PayPay QR码支付页,若存在"こちら"兜底链接则点击进入账号登录环节
- 将 _wait_for_paypay_login_result 中的并发等待逻辑提取为通用 _race_locators 辅助方法
This commit is contained in:
2026-07-27 01:41:35 +08:00
parent d1679cdfff
commit d1dc175d78
+28 -28
View File
@@ -715,6 +715,18 @@ class SurugayaPurchaseTask(BasePurchaseTask):
expiry_timer = (await page.locator(".expiry-timer-wrap .timer .time").inner_text()).strip() expiry_timer = (await page.locator(".expiry-timer-wrap .timer .time").inner_text()).strip()
logger.debug(f"[任务 {self.task_id}] 倒计时:{expiry_timer}") 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 is_submit_btn = False
logger.debug(f"[任务 {self.task_id}] 检测是否直接进入了支付环节") logger.debug(f"[任务 {self.task_id}] 检测是否直接进入了支付环节")
@@ -796,39 +808,27 @@ class SurugayaPurchaseTask(BasePurchaseTask):
logger.error(f"[任务 {self.task_id}] 支付异常:{e}") logger.error(f"[任务 {self.task_id}] 支付异常:{e}")
raise 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): 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}") logger.debug(f"[任务 {self.task_id}] 登录提交后同时等待支付按钮或短信验证码登录页面出现,限时 {timeout_sec}")
submit_btn = page.locator(".submit-button-wrap button.submit-button") submit_btn = page.locator(".submit-button-wrap button.submit-button")
sms_prompt = login_iframe.get_by_text("SMSで届いた認証コードを入力してください").first sms_prompt = login_iframe.get_by_text("SMSで届いた認証コードを入力してください").first
return await self._race_locators({"submit": submit_btn, "sms": sms_prompt}, timeout_sec=timeout_sec)
# 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} 秒)")
async def _wait_for_manual_sms_verification(self, page: Page, login_iframe, *, timeout_sec: float = 180): async def _wait_for_manual_sms_verification(self, page: Page, login_iframe, *, timeout_sec: float = 180):
""" """