Приведены поля, которые гарантированно возвращаются в API интерфейсе библиотеки. В некоторых источниках могут присутствовать дополнительные поля или атрибуты для использования во внутренних методах.
-
Например, в
anilibriaиanimevostдополнительные поля из API хранятся вdata. Вanimego.Animeесть сырой несериализованныйraw_jsonдля извлечения дополнительных метаданных. -
В некоторых источниках на полях могут присутствовать "заглушки" для поддержания консистентности API интерфейса. Например, в модуле
anicli_api.source.jutsu.Episodeуже можно получить прямые ссылки на видео (Video объект), но для поддержания полиморфизма, необходимо возвращать объектSourceи только потомVideo
- url: str - URL на тайтл
- title: str - имя найденного тайтла
- thumbnail: str - изображение
- url: str - URL на тайтл
- title: str - имя найденного тайтла
- thumbnail: str - изображение
- title: str - имя тайтла (на русском)
- thumbnail: str - изображение
- description: Optional[str] - описание тайтла. может вернуть пустую строку или None
- title: str - имя эпизода (Если источник его не хранит, то будет Серия или Serie)
- ordinal: int - номер эпизода
- url: str - ссылка на источник
- title: str - даббер или имя источника
Объект Video, полученный из Source.get_video/Source.a_get_video
имеет следующую структуру:
- type - тип видео (m3u8, mp4, mpd, audio)
- quality - разрешение видео (0, 144, 240, 360, 480, 720, 1080)
- url - прямая ссылка на видео
- headers - заголовки требуемые для получения потока. Если возвращает пустой словарь - заголовки не нужны
Настройка логгера идет через ключ anicli-api
import logging
logger = logging.getLogger('anicli-api')Наборы вспомогательных модулей
Опциональная зависимость для извлечения cookie данных из браузера для последующего использования в httpx. Реализована при помощи библиотеки rookiepy
pip install anicli-api[browser-cookies]import httpx
from anicli_api.tools.cookies import get_raw_cookies_from_browser, raw_cookies_to_httpx_cookiejar
# по умолчанию извлекает все cookies
raw_cookies = get_raw_cookies_from_browser("firefox")
cookies = raw_cookies_to_httpx_cookiejar(raw_cookies)
# опциональный фильтр по host:
# raw_cookies = get_raw_cookies_from_browser("firefox", ["example.com"])
httpx.get("https://example.com", cookies=cookies)
# update cookies in extractors:
from anicli_api.source.yummy_anime_org import Extractor
ex = Extractor()
ex.http.cookies.update(cookies)
ex.http_async.cookies.update(cookies)Простой cli-клиент для ручных тестов и имитации клиента
from anicli_api.tools.dummy_cli import cli
from anicli_api.source.yummy_anime_org import Extractor
cli(Extractor())Наборы вспомогательных функций для взаимодействия с экстракторами
from anicli_api.source.yummy_anime_org import Extractor
from anicli_api.tools.helpers import get_video_by_quality, video_picker_iterator, async_video_picker_iterator
ex = Extractor()
result = ex.search('lain')[0]
anime = result.get_anime()
episodes = anime.get_episodes()
sources = episodes[0].get_sources()
videos = sources[0].get_videos()
# извлечь ближайшее доступное по качеству видео
# 1080 or linear quality: 720 -> 480 -> 360 -> 240...
video = get_video_by_quality(videos, 1080)
# итератор видео и заголовка
# извлекает выбранное видео по разрешению и озвучке
for video_and_title in video_picker_iterator(
start_source=sources[0],
start_video=videos[0],
anime=anime,
episodes=episodes[3:]): # limit episodes to 3
v, title = video_and_title
print(title, v)
# async реализация итератора
async def main():
async for vid_and_title in async_video_picker_iterator(
start_source=sources[0],
start_video=videos[0],
anime=anime,
episodes=episodes[3:] # limit episodes to 3
):
v, title = vid_and_title
print(title, v)
import asyncio
asyncio.run(main())Реализация создания m3u плейлиста из объектов экстрактора
from anicli_api.source.yummy_anime_org import Extractor
from anicli_api.tools.m3u import generate_playlist, generate_asyncio_playlist
from anicli_api.tools.helpers import video_picker_iterator, async_video_picker_iterator
ex = Extractor()
result = ex.search('lain')[0]
anime = result.get_anime()
episodes = anime.get_episodes()
# get first source for first 3 episodes
sources = [e.get_sources()[0] for e in episodes[:3]]
videos = sources[0].get_videos()
# generate playlist from sources
playlist_sources = generate_playlist(sources)
# generate playlist from video_picker
video_objs, titles_objs = [],[]
for video_and_title in video_picker_iterator(
start_source=sources[0],
start_video=videos[0],
anime=anime,
episodes=episodes[3:]): # limit episodes to 3
v, title = video_and_title
video_objs.append(v)
titles_objs.append(title)
playlist = generate_playlist(video_objs, titles_objs)
# asyncio
# if target not Source object - you can use sync variant
import asyncio
playlist_async = asyncio.run(generate_asyncio_playlist(sources))Каждый Extractor() без аргументов создаёт собственную пару настроенных
BaseHTTPSync / BaseHTTPAsync. Эта пара передаётся всем дочерним объектам и
player-экстрактору. Разные root extractors не разделяют cookies, headers и
connection pool неявно.
Можно передать обычные httpx.Client / httpx.AsyncClient. Они сохраняются без
замены и мутации:
from anicli_api.source.animego import Extractor
import httpx
my_client = httpx.Client(headers={"user-agent": "007"}, proxy="http://127.0.0.1:8080")
my_async_client = httpx.AsyncClient(headers={"user-agent": "007"}, proxy="http://127.0.0.1:8080")
ex = Extractor(http_client=my_client, http_async_client=my_async_client)
# Те же instances дойдут до Search -> Anime -> Episode -> Source -> player.Переданные clients считаются borrowed: библиотека их не закрывает. Lifecycle
остаётся ответственностью вызывающего кода. Для clients, созданных самим root
Extractor, close() / with закрывают sync client, а aclose() /
async with закрывают оба owned clients.
Для общей сессии нескольких extractors создайте clients один раз и передайте одни и те же instances:
from anicli_api._http import BaseHTTPAsync, BaseHTTPSync
from anicli_api.source.animego import Extractor as AnimeGo
from anicli_api.source.sameband import Extractor as SameBand
sync_client = BaseHTTPSync(headers={"Authorization": "Bearer ..."}, proxy="http://127.0.0.1:8080")
async_client = BaseHTTPAsync(headers={"Authorization": "Bearer ..."}, proxy="http://127.0.0.1:8080")
animego = AnimeGo(http_client=sync_client, http_async_client=async_client)
sameband = SameBand(http_client=sync_client, http_async_client=async_client)BaseHTTP* добавляют retry/reconnect и Anubis transport. Обычные custom
httpx.Client используют только собственный transport. Для opt-in middleware
передайте HTTPRetryConnectSyncTransport / HTTPRetryConnectAsyncTransport
при создании custom client.
Per-call headers / cookies / timeout применяются к каждому HTTP-запросу внутри
экстрактора для этого вызова и НЕ мутируют сам httpx.Client. Это безопасно для
конкурентных asyncio вызовов на одном клиенте/экстракторе.
Для per-tenant изоляции (разные прокси / IP-регионы) создавай отдельный
httpx.AsyncClient per tenant и присваивай через source.http_async.
from anicli_api.source.animego import Extractor
sources = (
Extractor()
.search("lain")[0]
.get_anime()
.get_episodes()[0]
.get_sources()
)
# новый API: per-call kwargs (без мутации клиента)
videos = sources[0].get_videos(
headers={"User-Agent": "custom-ua"},
cookies={"session": "..."},
timeout=30.0,
)
# Migration: ранее можно было передать http=/a_http= в get_videos для override
# клиента целиком. Теперь нужно присваивать на source:
# source.http = my_proxy_client
# source.get_videos()Для per-tenant изоляции между конкурентными задачами:
import httpx
from anicli_api.source.animego import Extractor
async def run_for_tenant(proxy_url: str):
# свой клиент на tenant - никаких гонок
async with httpx.AsyncClient(proxy=proxy_url) as client:
ex = Extractor(http_async_client=client)
...