diff --git a/modules/anime_etl/sources/anilist_async_client.py b/modules/anime_etl/sources/anilist_async_client.py new file mode 100644 index 0000000..92e8dd2 --- /dev/null +++ b/modules/anime_etl/sources/anilist_async_client.py @@ -0,0 +1,46 @@ +import httpx +import asyncio +from rate_limiter import ANILIST_RATE_LIMITER + +URL = "https://graphql.anilist.co" + +QUERY = """ +query ($search: String, $season: MediaSeason, $seasonYear: Int, $format: MediaFormat, $page: Int, $perPage: Int) { + Page(page: $page, perPage: $perPage) { + media(search: $search, type: ANIME, season: $season, seasonYear: $seasonYear, format: $format) { + id + title { romaji english native } + status + season + seasonYear + # seasonNumber + episodes + duration + popularity + averageScore + coverImage { extraLarge large } + genres + studios(isMain: true) { nodes { id name } } + nextAiringEpisode { episode } + } + } +} +""" + +CLIENT = httpx.AsyncClient(timeout=15.0) + +async def _post(payload: dict) -> dict: + for i in range(5): + await ANILIST_RATE_LIMITER.acquire() + try: + r = await CLIENT.post(URL, json=payload) + r.raise_for_status() + return r.json() + except Exception: + await asyncio.sleep(1 * 2**i) + raise RuntimeError("AniList unreachable") + +async def search_raw(filters: dict) -> list[dict]: + payload = {"query": QUERY, "variables": filters} + data = await _post(payload) + return data.get("data", {}).get("Page", {}).get("media") or [] diff --git a/modules/anime_etl/sources/anilist_source.py b/modules/anime_etl/sources/anilist_source.py new file mode 100644 index 0000000..9307587 --- /dev/null +++ b/modules/anime_etl/sources/anilist_source.py @@ -0,0 +1,35 @@ +from mappers.anilist_filters import to_anilist_filters +from sources.anilist_async_client import search_raw +from normalizers.anilist_normalizer import normalize_media +import asyncio +import pprint + + +class AniListSource: + async def search(self, local_filters: dict) -> list: + ani_filters = to_anilist_filters(local_filters) + raw_list = await search_raw(ani_filters) + return [normalize_media(r) for r in raw_list] + + +async def _demo() -> None: + src = AniListSource() + filters = { + "query": "monogatari", + # "year": 2017, + # "season": "winter", + # "type": "tv", + # "limit": 5, + } + print("Запускаю поиск с фильтрами:", filters) + titles = await src.search(filters) + print("Найдено тайтлов:", len(titles)) + + for t in titles: + # t.title_names — dict[str, list[str]] + # en = (t.title_names.get("en") or [""])[0] + # print("-", en, "|", t.release_year, t.release_season) + pprint.pprint(t) + +if __name__ == "__main__": + asyncio.run(_demo())