Files
rakuten-api/app/parsers/genre.py
T
2026-07-27 10:34:53 +08:00

104 lines
3.4 KiB
Python

"""分类页/搜索页 __INITIAL_STATE__ → GenreData
分类数据来自 state.data.genreTree.parent_category。它不是完整分类树,而是一条
「从根一路展开到目标分类」的链:每层只保留通往目标的那一个子节点,目标分类自身
则挂着它的全部直接子分类。
不带分类查询时,根节点直接挂着 39 个顶层分类。
"""
from __future__ import annotations
from typing import Any
from app.core import site
from app.core.errors import ScrapeParseError
from app.models.scrape import GenreData, GenreNode
from app.utils.coerce import as_dict, as_int, as_list, as_str
# 站点用 id=0 表示分类树的虚拟根,它不是一个真实分类
ROOT_GENRE_ID = 0
def _genre_url(genre_id: str) -> str:
return f"{site.CATEGORY_BASE_URL}{genre_id}/" if genre_id else ""
def _to_node(raw: dict[str, Any], *, with_count: bool) -> GenreNode:
genre_id = str(as_int(raw.get("id")))
count = raw.get("count")
return GenreNode(
genre_id=genre_id,
name=as_str(raw.get("name")),
item_count=as_int(count) if with_count and count is not None else None,
shortcut=as_str(raw.get("shortcut")),
is_leaf=bool(raw.get("leaf")),
url=_genre_url(genre_id),
)
def _find_path(node: dict[str, Any], genre_id: str) -> list[dict[str, Any]] | None:
"""在分类链中定位目标分类,返回从根到它的节点路径(含自身)"""
if str(as_int(node.get("id"))) == genre_id:
return [node]
for child in as_list(node.get("children")):
if not isinstance(child, dict):
continue
found = _find_path(child, genre_id)
if found is not None:
return [node, *found]
return None
def parse_genres(state: dict[str, Any], *, genre_id: str | None) -> GenreData:
"""解析分类树
Args:
genre_id: 目标分类;None 表示取顶层分类列表
Raises:
ScrapeParseError: 页面里没有分类树,或目标分类不在返回的链上
"""
data = as_dict(as_dict(state.get("state")).get("data"))
root = as_dict(as_dict(data.get("genreTree")).get("parent_category"))
if not root:
raise ScrapeParseError("页面中缺少 genreTree.parent_category 节点")
if genre_id is None:
children = [
_to_node(child, with_count=False)
for child in as_list(root.get("children"))
if isinstance(child, dict)
]
if not children:
raise ScrapeParseError("未能取到顶层分类列表")
return GenreData(children=children)
path = _find_path(root, genre_id)
if path is None:
raise ScrapeParseError(f"分类树中未找到分类 {genre_id}")
target = path[-1]
ancestors = [
_to_node(node, with_count=False)
for node in path[:-1]
if as_int(node.get("id")) != ROOT_GENRE_ID
]
children = [
_to_node(child, with_count=True)
for child in as_list(target.get("children"))
if isinstance(child, dict)
]
genre_info = as_dict(data.get("genreInfo"))
resolved_id = str(as_int(target.get("id")))
return GenreData(
genre_id=resolved_id,
name=as_str(target.get("name")),
full_name=as_str(genre_info.get("fullGenreName")),
description=as_str(genre_info.get("description")),
is_leaf=bool(target.get("leaf")),
url=_genre_url(resolved_id),
ancestors=ancestors,
children=children,
)