Files
pruchase_jhw/src/tasks/surugaya_news_monitor.py
T
2026-06-13 20:23:49 +08:00

99 lines
3.8 KiB
Python

import json
import time
from typing import Any
from loguru import logger
from src.tasks import surugaya_auth
from src.tasks.base import BasePurchaseTask
from src.config.accounts import get_account_config
from src.parsers.surugaya_news import collect_all_news
from src.task_queue.redis_manager import get_redis_client
# 「お知らせ一覧」页面地址。
NEWS_URL = "https://www.suruga-ya.jp/pcmypage/action_news"
class SurugayaNewsMonitorTask(BasePurchaseTask):
"""
骏河屋「お知らせ一覧」通知抓取任务。
入参约定:
- topic: surugaya_news
- purchase_account: 抓取通知所属账号
- payload: 无额外字段
行为:打开 action_news 页面(必要时自动登录),解析首页最新通知,
按 data_id 去重写入 Redis Hash(field=data_id),供中心 API 读取。
"""
async def execute(self) -> dict[str, Any]:
self.report_event = "surugaya_news"
self.is_report = False
logger.debug(f"[任务 {self.task_id}] 骏河屋通知抓取工作流已启动。")
if not self.page:
raise ValueError("页面对象未初始化,无法执行通知抓取任务。")
page = self.page
await page.goto(NEWS_URL, wait_until="domcontentloaded", timeout=30000)
# 登录兜底:长时间运行登录态可能过期,未登录则自动登录后重新进入。
if not await self._ensure_login(page):
raise RuntimeError(f"账号 '{self.account_id}' 登录失败,无法抓取通知。")
news_items = await collect_all_news(page)
logger.info(f"[任务 {self.task_id}] 账号 '{self.account_id}' 翻页解析到 {len(news_items)} 条通知。")
written = await self._save_news_to_redis(news_items)
return {
"account_id": self.account_id,
"parsed_count": len(news_items),
"written_count": written,
}
async def _notify_task_failed(self) -> None:
"""通知抓取是每 5 分钟运行的常规轮询,单次失败下轮即重试,不发失败告警邮件,
避免登录抖动等偶发问题造成告警刷屏(失败现场截图仍由基类保留)。"""
logger.warning(
f"[任务 {self.task_id}] 通知抓取失败(不告警,等待下一轮重试): {self.error_message}"
)
async def _ensure_login(self, page) -> bool:
log_prefix = f"[任务 {self.task_id}] "
if await surugaya_auth.check_login_status(page, log_prefix=log_prefix):
return True
logger.warning(f"{log_prefix}账号 '{self.account_id}' 未登录,执行登录流程。")
account_config = get_account_config("surugaya", self.account_id)
if not account_config:
raise ValueError(f"未找到骏河屋账号配置: {self.account_id}")
await surugaya_auth.surugaya_login(page, account_config, log_prefix=log_prefix)
await page.goto(NEWS_URL, wait_until="domcontentloaded", timeout=30000)
return await surugaya_auth.check_login_status(page, log_prefix=log_prefix)
async def _save_news_to_redis(self, news_items: list[dict[str, Any]]) -> int:
"""按 data_id 作为 field 写入 Redis Hash;返回写入条数。"""
if not news_items:
return 0
redis_client = get_redis_client()
hash_key = self.settings.redis_news_hash_key
fetched_at = int(time.time())
mapping = {
item["data_id"]: json.dumps(
{**item, "account_id": self.account_id, "fetched_at": fetched_at},
ensure_ascii=False,
)
for item in news_items
}
await redis_client.hset(hash_key, mapping=mapping)
logger.info(
f"[任务 {self.task_id}] 已将 {len(mapping)} 条通知写入 Redis Hash '{hash_key}'"
)
return len(mapping)