交易服务收敛为仅 rakuten:移除 rakuma 登录态与下单入口
trading 不再管理 ラクマ 的购物车购买、支付与订单监控。删除 auth_site / auth_session / login_runner / models.AuthSite 中的 rakuma 分支与常量、 account.yaml.example 的 rakuma 段,并清理相关测试。scraping 侧的 ラクマ 抓取 API(/api/rakuma/*)保留不动。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,15 @@
|
||||
而是分进程运行,原因:
|
||||
|
||||
- 抓取是**匿名**的,cookie 只用来过 Akamai 限速,丢了重新预热即可,无状态可言。
|
||||
- 加购下单必须**带账号**,cookie 一旦失效需要重登——乐天与 ラクマ 登录都有
|
||||
reCAPTCHA 与设备验证,全自动不可靠。本服务支持「失效时自动重登 + 撞验证码弹
|
||||
有头浏览器让人工接管」的降级路径(依赖 account.yaml 与 relogin_enabled)。
|
||||
- 加购下单必须**带账号**,cookie 一旦失效需要重登——乐天登录有 reCAPTCHA 与设备
|
||||
验证,全自动不可靠。本服务支持「失效时自动重登 + 撞验证码弹有头浏览器让人工接管」
|
||||
的降级路径(依赖 account.yaml 与 relogin_enabled)。
|
||||
- 这份登录态在整个系统里只能有一份。跟抓取同进程的话,抓取一扩容就会复制出 N 份
|
||||
登录态与 N 个订单轮询,同一个账号被并发操作。
|
||||
|
||||
交易服务只管理乐天市场的购物车购买、支付与订单监控;ラクマ 抓取仍在抓取服务里
|
||||
提供,但不进入交易链路。本模块只持有 rakuten 一个站点的登录态。
|
||||
|
||||
登录态来源是 Playwright 的 storage_state:
|
||||
- 首次登录由 scripts/login.py 完成;
|
||||
- 运行时失效且 relogin_enabled=true 时,本模块自动触发 login_runner.login_one()
|
||||
@@ -75,7 +78,7 @@ class _SiteAuth:
|
||||
|
||||
|
||||
class AuthSession:
|
||||
"""持有两站登录态的会话
|
||||
"""持有登录态的会话
|
||||
|
||||
职责边界:只负责「有没有登录态、cookie 是什么、还有效吗」,
|
||||
具体加购/下单的业务请求由各自的 client 组装后借这里的 HTTP 客户端发出。
|
||||
@@ -90,7 +93,7 @@ class AuthSession:
|
||||
# ---- 生命周期 ----
|
||||
|
||||
async def start(self) -> None:
|
||||
"""为两站创建带登录 cookie 的 HTTP 客户端
|
||||
"""为站点创建带登录 cookie 的 HTTP 客户端
|
||||
|
||||
登录态文件不存在时同样创建客户端(只是没有 cookie),这样 /health 与
|
||||
/api/auth/status 能如实回报「未登录」,而不是整个服务起不来。
|
||||
@@ -175,10 +178,9 @@ class AuthSession:
|
||||
auth = self._require_site(site)
|
||||
async with auth.lock:
|
||||
try:
|
||||
if site == "rakuten":
|
||||
logged_in, detail = await self._check_rakuten(auth)
|
||||
else:
|
||||
logged_in, detail = await self._check_rakuma(auth)
|
||||
if site != "rakuten":
|
||||
raise ValueError(f"未知站点:{site}")
|
||||
logged_in, detail = await self._check_rakuten(auth)
|
||||
except httpx.HTTPError as exc:
|
||||
raise UpstreamRequestError(
|
||||
f"登录态探测请求失败:site={site} err={type(exc).__name__}: {exc}"
|
||||
@@ -201,17 +203,6 @@ class AuthSession:
|
||||
return False, "购物车页显示未登录"
|
||||
return True, "购物车页未出现未登录标记"
|
||||
|
||||
async def _check_rakuma(self, auth: _SiteAuth) -> tuple[bool, str]:
|
||||
"""/mypage 被重定向到 /users/sign_in 即未登录"""
|
||||
response = await auth.client.get(auth_site.PROFILES["rakuma"].probe_url)
|
||||
if response.status_code >= 400:
|
||||
return False, f"mypage 返回 status {response.status_code}"
|
||||
if not auth_site.is_logged_in(
|
||||
"rakuma", final_url=str(response.url), body=response.text
|
||||
):
|
||||
return False, "mypage 被重定向至登录页"
|
||||
return True, "mypage 正常返回"
|
||||
|
||||
async def require_logged_in(self, site: str) -> None:
|
||||
"""确保某站处于登录态,否则抛错
|
||||
|
||||
@@ -338,7 +329,7 @@ class AuthSession:
|
||||
)
|
||||
|
||||
def status_all(self) -> dict[str, dict[str, Any]]:
|
||||
"""两站登录态快照,供健康检查展示"""
|
||||
"""登录态快照,供健康检查展示"""
|
||||
return {name: self.status(name).to_dict() for name in self._sites}
|
||||
|
||||
def _require_site(self, site: str) -> _SiteAuth:
|
||||
|
||||
@@ -176,15 +176,16 @@ def default_account_for(site: str, accounts_by_site: dict[str, list[Account]]) -
|
||||
async def _is_logged_in(page, site: str) -> bool:
|
||||
"""访问 profile.login_url 看落地是否被踢到 SSO
|
||||
|
||||
- rakuten:未登录会被重定向到 login.account.rakuten.com 或 /login 路径
|
||||
- rakuma:未登录会被重定向到 /users/sign_in
|
||||
`site` 目前恒为 `"rakuten"`:未登录时会被重定向到 login.account.rakuten.com
|
||||
或 /login 路径;登录后停在 my.rakuten.co.jp。保留 site 参数形态是为了与
|
||||
SiteAuthProfile 共用签名,方便未来加站点。
|
||||
"""
|
||||
profile = auth_site.profile(site)
|
||||
await page.goto(profile.login_url, wait_until="domcontentloaded", timeout=60_000)
|
||||
final_url = page.url
|
||||
if site == "rakuten":
|
||||
return "login.account.rakuten.com" not in final_url and "/login" not in final_url
|
||||
return auth_site.RAKUMA_SIGN_IN_PATH not in final_url
|
||||
if site != "rakuten":
|
||||
raise ValueError(f"未知站点:{site}")
|
||||
return "login.account.rakuten.com" not in final_url and "/login" not in final_url
|
||||
|
||||
|
||||
# ---- 自动填表(启发式选择器)----
|
||||
@@ -380,7 +381,7 @@ async def cli_main(argv: list[str] | None = None) -> int:
|
||||
"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="按 account.yaml 自动登录乐天/ラクマ")
|
||||
parser = argparse.ArgumentParser(description="按 account.yaml 自动登录乐天市场")
|
||||
parser.add_argument(
|
||||
"--site",
|
||||
choices=[*auth_site.SITES, "all"],
|
||||
|
||||
Reference in New Issue
Block a user