From f695b59980662696fb0636754b1a9783e45b7440 Mon Sep 17 00:00:00 2001 From: YaNesyTortiK Date: Sun, 20 Oct 2024 13:51:13 +0300 Subject: [PATCH] Build 1.9.0; Added get_list function to kodik parser; Other adjustments and fixes for kodik parser --- CHANGELOG.md | 11 +- README.md | 114 ++++++++++++- pyproject.toml | 2 +- src/anime_parsers_ru.egg-info/PKG-INFO | 116 ++++++++++++- src/anime_parsers_ru/parser_kodik.py | 178 ++++++++++++-------- src/anime_parsers_ru/parser_kodik_async.py | 182 +++++++++++++-------- src/tests.py | 32 ++++ 7 files changed, 486 insertions(+), 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe60b0..45c1d69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,4 +90,13 @@ - Added docs about existing Exceptions ## To 1.8.3 -- Fixed rare bug in shikimori parser `search` function. Some animes don't have any info about type, year, type. Now this values will be `None` if this anime appeared. \ No newline at end of file +- Fixed rare bug in shikimori parser `search` function. Some animes don't have any info about type, year, type. Now this values will be `None` if this anime appeared. + +## To 1.9.0 +Overall fixes and adjustments to KodikParser and KodikParserAsync + +- Added `get_list` function to get ongoings*. +- Fixed `include_material_data` flag to actually work +- Added `only_anime` flag to filter only results where type is `anime` or `anime-serial` +- Moved json parser to separate function `_prettify_data` to avoid repeating code +- Polished and refactored some parts of README about KodikParser \ No newline at end of file diff --git a/README.md b/README.md index 94d064a..e2a6b25 100644 --- a/README.md +++ b/README.md @@ -75,20 +75,114 @@ pip install lxml 1. Поиск аниме по названию ```python - parser.search("Наруто") # список словарей + parser.search(title="Наруто", limit=None, include_material_data=True, only_anime=False) # список словарей + # title - Название аниме/фильма/сериала + # limit - количество результатов выдачи (int) (результатов будет сильно меньше чем указанное число, так как в выдаче результаты повторяются) + # include_material_data - Добавлять дополнительные данные об элементе + # only_anime - возвращать только элементы где type in ['anime', 'anime-serial'] + ``` + Возвращает: + ```json + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ] ``` - Возвращает словарь из аниме и их данных (см. комментарии к функции) 2. Поиск аниме по id ```python - parser.search_by_id("20", "shikimori") # список словарей - # Доступные id_type: "shikimori", "kinopoisk", "imdb" + parser.search_by_id(id="20", id_type="shikimori", limit=None) + # id - id аниме на одном из сайтов + # id_type - с какого сайта id (Поддерживается: shikimori, kinopoisk, imdb) + # limit - количество результатов выдачи (int) (результатов будет сильно меньше чем указанное число, так как в выдаче результаты повторяются) + ``` + Возвращает: + ```json + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ] + ``` + +3. Получить список из недавно обновленных + ```python + data = parser.get_list(limit_per_page=50, pages_to_parse=1, include_material_data=True, only_anime=False, start_from=None) + # limit_per_page - количество результатов на одной странице (итоговых результатов будет сильно меньше чем указан параметр) + # pages_to_parse - количество страниц для обработки (каждая страница - отдельный запрос) + # include_material_data - включить в результат дополнительные данные + # only_anime - возвращать только элементы где type in ['anime', 'anime-serial'] + # start_from - начать поиск со страницы под id (id возвращается вторым элементом результата функции) + ``` + Возвращает: + ```json + ( + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ], + "next_page_id": "id следующей страницы (для последовательного парсинга нескольких страниц) (может быть None, если след. страниц нет)" + ) ``` - Возвращает словарь из аниме и их данных (см. комментарии к функции) -3. Получить информацию об аниме +4. Получить информацию об аниме ```python - parser.get_info("z20", "shikimori") # Информация по "Наруто" + parser.get_info(id="z20", id_type="shikimori") + # id - id аниме на одном из сайтов + # id_type - с какого сайта id (Поддерживается: shikimori, kinopoisk, imdb) ``` Возвращает: ```json @@ -112,13 +206,17 @@ pip install lxml ```python parser.translations("z20", "shikimori") # список словарей ``` -4. Прямая ссылка на видеофайл +5. Прямая ссылка на видеофайл ```python parser.get_link( id="z20", id_type="shikimori", seria_num=1, translation_id="609") # Кортеж + # id - id медиа + # id_type - тип id (возможные: shikimori, kinopoisk, imdb) + # seria_num - номер серии (если фильм или одно видео - 0) + # translation_id - id перевода (прим: Anilibria = 610, если неизвестно - 0) ``` Возвращает кортеж: `("//cloud.kodik-storage.com/useruploads/67b6e546-e51d-43d2-bb11-4d8bfbedc2d7/d6f4716bc90bd30694cf09b0062d07a2:2024062705/", 720)` diff --git a/pyproject.toml b/pyproject.toml index 302812d..6eb4c77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "anime_parsers_ru" -version = "1.8.3" +version = "1.9.0" authors = [ { name="YaNesyTortiK", email="ya.nesy.tortik.email@gmail.com"}, ] diff --git a/src/anime_parsers_ru.egg-info/PKG-INFO b/src/anime_parsers_ru.egg-info/PKG-INFO index 309040a..fe89c5d 100644 --- a/src/anime_parsers_ru.egg-info/PKG-INFO +++ b/src/anime_parsers_ru.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: anime_parsers_ru -Version: 1.8.3 +Version: 1.9.0 Summary: Python package for parsing russian anime players Author-email: YaNesyTortiK Maintainer-email: YaNesyTortiK @@ -105,20 +105,114 @@ pip install lxml 1. Поиск аниме по названию ```python - parser.search("Наруто") # список словарей + parser.search(title="Наруто", limit=None, include_material_data=True, only_anime=False) # список словарей + # title - Название аниме/фильма/сериала + # limit - количество результатов выдачи (int) (результатов будет сильно меньше чем указанное число, так как в выдаче результаты повторяются) + # include_material_data - Добавлять дополнительные данные об элементе + # only_anime - возвращать только элементы где type in ['anime', 'anime-serial'] + ``` + Возвращает: + ```json + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ] ``` - Возвращает словарь из аниме и их данных (см. комментарии к функции) 2. Поиск аниме по id ```python - parser.search_by_id("20", "shikimori") # список словарей - # Доступные id_type: "shikimori", "kinopoisk", "imdb" + parser.search_by_id(id="20", id_type="shikimori", limit=None) + # id - id аниме на одном из сайтов + # id_type - с какого сайта id (Поддерживается: shikimori, kinopoisk, imdb) + # limit - количество результатов выдачи (int) (результатов будет сильно меньше чем указанное число, так как в выдаче результаты повторяются) + ``` + Возвращает: + ```json + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ] + ``` + +3. Получить список из недавно обновленных + ```python + data = parser.get_list(limit_per_page=50, pages_to_parse=1, include_material_data=True, only_anime=False, start_from=None) + # limit_per_page - количество результатов на одной странице (итоговых результатов будет сильно меньше чем указан параметр) + # pages_to_parse - количество страниц для обработки (каждая страница - отдельный запрос) + # include_material_data - включить в результат дополнительные данные + # only_anime - возвращать только элементы где type in ['anime', 'anime-serial'] + # start_from - начать поиск со страницы под id (id возвращается вторым элементом результата функции) + ``` + Возвращает: + ```json + ( + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ], + "next_page_id": "id следующей страницы (для последовательного парсинга нескольких страниц) (может быть None, если след. страниц нет)" + ) ``` - Возвращает словарь из аниме и их данных (см. комментарии к функции) -3. Получить информацию об аниме +4. Получить информацию об аниме ```python - parser.get_info("z20", "shikimori") # Информация по "Наруто" + parser.get_info(id="z20", id_type="shikimori") + # id - id аниме на одном из сайтов + # id_type - с какого сайта id (Поддерживается: shikimori, kinopoisk, imdb) ``` Возвращает: ```json @@ -142,13 +236,17 @@ pip install lxml ```python parser.translations("z20", "shikimori") # список словарей ``` -4. Прямая ссылка на видеофайл +5. Прямая ссылка на видеофайл ```python parser.get_link( id="z20", id_type="shikimori", seria_num=1, translation_id="609") # Кортеж + # id - id медиа + # id_type - тип id (возможные: shikimori, kinopoisk, imdb) + # seria_num - номер серии (если фильм или одно видео - 0) + # translation_id - id перевода (прим: Anilibria = 610, если неизвестно - 0) ``` Возвращает кортеж: `("//cloud.kodik-storage.com/useruploads/67b6e546-e51d-43d2-bb11-4d8bfbedc2d7/d6f4716bc90bd30694cf09b0062d07a2:2024062705/", 720)` diff --git a/src/anime_parsers_ru/parser_kodik.py b/src/anime_parsers_ru/parser_kodik.py index d717d86..8cc85db 100644 --- a/src/anime_parsers_ru/parser_kodik.py +++ b/src/anime_parsers_ru/parser_kodik.py @@ -86,7 +86,7 @@ def base_search(self, title: str, limit: int = 50, include_material_data: bool = "token": self.TOKEN, "title": title, "limit": limit, - "with_material_data": include_material_data + "with_material_data": 'true' if include_material_data else 'false' } url = "https://kodikapi.com/search" data = requests.post(url, data=payload).json() @@ -158,7 +158,7 @@ def base_search_by_id(self, id: str, id_type: str, limit: int = 50, include_mate "token": self.TOKEN, f"{id_type}_id": id, "limit": limit, - "with_material_data": include_material_data + "with_material_data": 'true' if include_material_data else 'false' } url = "https://kodikapi.com/search" data = requests.post(url, data=payload).json() @@ -171,7 +171,72 @@ def base_search_by_id(self, id: str, id_type: str, limit: int = 50, include_mate raise errors.NoResults(f'По id {id_type} "{id}" ничего не найдено') return data - def search(self, title: str, limit: int|None = None) -> list: + def get_list(self, limit_per_page: int = 50, pages_to_parse: int = 1, include_material_data: bool = True, only_anime: bool = False, start_from: str|None = None) -> tuple[list[dict],str]: + """ + Получение случайного списка аниме от кодика (скорее всего это будут онгоинги) + + :limit_per_page: Ограничение на количество результатов на запрос(страницу), не все элементы в списке будут аниме (по умолчанию 50) + :pages_to_parse: Ограничение на количество страниц для обработки (каждая страница - отдельный запрос) (по умолчанию 1) + :include_material_data: Добавление дополнительных данных (необязательно, по умолчанию True) + :only_anime: Возвращать только варианты аниме (тип anime или anime-serial) (по умолчанию False) + :start_from: Поиск следующих страниц по заданному id (id возвращается вторым элементом кортежа) (по умолчанию None) + + Возвращает кортеж из списка словарей и id страницы: + ( + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ], + "next_page_id": "id следующей страницы (для последовательного парсинга нескольких страниц) (может быть None, если след. страниц нет)" + ) + """ + if self.TOKEN is None: + raise errors.TokenError('Токен kodik не указан') + results = [] + next_page = start_from + payload = { + "token": self.TOKEN, + "limit": limit_per_page, + "with_material_data": 'true' if include_material_data else 'false' + } + for _ in range(pages_to_parse): + if next_page != None: + payload['next'] = next_page + url = "https://kodikapi.com/list" + data = requests.post(url, data=payload).json() + + if 'error' in data.keys() and data['error'] == 'Отсутствует или неверный токен': + raise errors.TokenError('Отсутствует или неверный токен') + elif 'error' in data.keys(): + raise errors.ServiceError(data['error']) + if data['total'] == 0: + raise errors.NoResults(f'Ничего не найдено. Скорее всего произошла ошибка, попробуйте позже или сообщите об ошибке на гитхабе.') + if 'next_page' in data.keys(): + next_page = data['next_page'][data['next_page'].rfind('=')+1:] + else: + next_page = None + results += data['results'] + return (self._prettify_data(results, only_anime=only_anime), next_page) + + def search(self, title: str, limit: int|None = None, include_material_data: bool = True, only_anime: bool = False) -> list: """ ### Для использования требуется токен kodik Получение только самых основных данных о сериале. @@ -179,26 +244,28 @@ def search(self, title: str, limit: int|None = None) -> list: :title: Название аниме / фильма / сериала :limit: Верхнее ограничение количества ответов для base_search (необязательно) + :include_material_data: Добавление дополнительных данных (необязательно, по умолчанию True) + :only_anime: Возвращать только варианты аниме (тип anime или anime-serial) (по умолчанию False) Возвращает список словарей в следующем виде: [ { - 'title': Название, - 'type': тип мультимедия (anime, film, ...) - 'year': Год выпуска фильма, - 'screenshots': [ - ссылки на скриншоты + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" ], - 'shikimori_id': Id шикимори, если нет - None, - 'kinopoisk_id': Id кинопоиска, если нет - None, - 'imdb_id': Id imdb, если нет - None, - 'worldart_link': ссылка на worldart, если нет - None - 'additional_data': { - Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" }, - 'material_data' { - Здесь будут все данные о сериале имеющиеся у кодика. - В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое. + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." }, "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" }, @@ -206,38 +273,10 @@ def search(self, title: str, limit: int|None = None) -> list: ] """ if limit is None: - search_data = self.base_search(title, include_material_data=True) + search_data = self.base_search(title, include_material_data=include_material_data) else: - search_data = self.base_search(title, limit, include_material_data=True) - data = [] - added_titles = [] - for res in search_data['results']: - if res['title'] not in added_titles: - additional_data = {} - for k, i in res.items(): - if k not in ['title', 'type', 'year', 'screenshots', 'translation', - 'shikimori_id', 'kinopoisk_id', 'imdb_id', 'worldart_link', - 'id', 'link', 'title_orig', 'other_title', 'created_at', - 'updated_at', 'quality', 'material_data']: - additional_data[k] = i - - data.append({ - 'title': res['title'], - 'title_orig': res['title_orig'], - 'other_title': res['other_title'] if 'other_title' in res.keys() else None, - 'type': res['type'], - 'year': res['year'], - 'screenshots': res['screenshots'], - 'shikimori_id': res['shikimori_id'] if 'shikimori_id' in res.keys() else None, - 'kinopoisk_id': res['kinopoisk_id'] if 'kinopoisk_id' in res.keys() else None, - 'imdb_id': res['imdb_id'] if 'imdb_id' in res.keys() else None, - 'worldart_link': res['worldart_link'] if 'worldart_link' in res.keys() else None, - 'additional_data': additional_data, - 'material_data': res['material_data'], - 'link': res['link'] - }) - added_titles.append(res['title']) - return data + search_data = self.base_search(title, limit, include_material_data=include_material_data) + return self._prettify_data(search_data['results'], only_anime=only_anime) def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> list: """ @@ -252,22 +291,22 @@ def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> list: Возвращает список словарей в следующем виде: [ { - 'title': Название, - 'type': тип мультимедия (anime, film, ...) - 'year': Год выпуска фильма, - 'screenshots': [ - ссылки на скриншоты + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" ], - 'shikimori_id': Id шикимори, если нет - None, - 'kinopoisk_id': Id кинопоиска, если нет - None, - 'imdb_id': Id imdb, если нет - None, - 'worldart_link': ссылка на worldart, если нет - None - 'additional_data': { - Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" }, - 'material_data' { - Здесь будут все данные о сериале имеющиеся у кодика. - В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое. + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." }, "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" }, @@ -278,9 +317,20 @@ def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> list: search_data = self.base_search_by_id(id, id_type, include_material_data=True) else: search_data = self.base_search_by_id(id, id_type, limit, include_material_data=True) + return self._prettify_data(search_data['results']) + + def _prettify_data(self, results: list[dict], only_anime: bool = False) -> list[dict]: + """ + Превращает полченные данные от запроса кодику в удобный вариант словаря + + :results: список словарей (response['results'] в json'е от кодика) + :only_anime: Возвращать только варианты аниме (тип anime или anime-serial) (по умолчанию False) + """ data = [] added_titles = [] - for res in search_data['results']: + for res in results: + if only_anime and res['type'] not in ['anime-serial', 'anime']: + continue if res['title'] not in added_titles: additional_data = {} for k, i in res.items(): @@ -302,7 +352,7 @@ def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> list: 'imdb_id': res['imdb_id'] if 'imdb_id' in res.keys() else None, 'worldart_link': res['worldart_link'] if 'worldart_link' in res.keys() else None, 'additional_data': additional_data, - 'material_data': res['material_data'], + 'material_data': res['material_data'] if 'material_data' in res.keys() else None, 'link': res['link'] }) added_titles.append(res['title']) diff --git a/src/anime_parsers_ru/parser_kodik_async.py b/src/anime_parsers_ru/parser_kodik_async.py index 4e0e813..04be70b 100644 --- a/src/anime_parsers_ru/parser_kodik_async.py +++ b/src/anime_parsers_ru/parser_kodik_async.py @@ -88,7 +88,7 @@ async def base_search(self, title: str, limit: int = 50, include_material_data: "token": self.TOKEN, "title": title, "limit": limit, - "with_material_data": include_material_data + "with_material_data": 'true' if include_material_data else 'false' } url = "https://kodikapi.com/search" data = await self.requests.post(url, data=payload) @@ -161,7 +161,7 @@ async def base_search_by_id(self, id: str, id_type: str, limit: int = 50, includ "token": self.TOKEN, f"{id_type}_id": id, "limit": limit, - "with_material_data": include_material_data + "with_material_data": 'true' if include_material_data else 'false' } url = "https://kodikapi.com/search" data = await self.requests.post(url, data=payload) @@ -174,8 +174,75 @@ async def base_search_by_id(self, id: str, id_type: str, limit: int = 50, includ if data['total'] == 0: raise errors.NoResults(f'По id {id_type} "{id}" ничего не найдено') return data + + async def get_list(self, limit_per_page: int = 50, pages_to_parse: int = 1, include_material_data: bool = True, only_anime: bool = False, start_from: str|None = None) -> tuple[list[dict],str]: + """ + Получение случайного списка аниме от кодика (скорее всего это будут онгоинги) + + :limit_per_page: Ограничение на количество результатов на запрос(страницу), не все элементы в списке будут аниме (по умолчанию 50) + :pages_to_parse: Ограничение на количество страниц для обработки (каждая страница - отдельный запрос) (по умолчанию 1) + :include_material_data: Добавление дополнительных данных (необязательно, по умолчанию True) + :only_anime: Возвращать только варианты аниме (тип anime или anime-serial) (по умолчанию False) + :start_from: Поиск следующих страниц по заданному id (id возвращается вторым элементом кортежа) (по умолчанию None) + + Возвращает кортеж из списка словарей и id страницы: + ( + [ + { + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" + ], + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" + }, + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." + }, + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" + }, + ... + ], + "next_page_id": "id следующей страницы (для последовательного парсинга нескольких страниц) (может быть None, если след. страниц нет)" + ) + """ + if self.TOKEN is None: + raise errors.TokenError('Токен kodik не указан') + results = [] + next_page = start_from + payload = { + "token": self.TOKEN, + "limit": limit_per_page, + "with_material_data": 'true' if include_material_data else 'false' + } + for _ in range(pages_to_parse): + if next_page != None: + payload['next'] = next_page + url = "https://kodikapi.com/list" + data = await self.requests.post(url, data=payload) + data = data.json() + + if 'error' in data.keys() and data['error'] == 'Отсутствует или неверный токен': + raise errors.TokenError('Отсутствует или неверный токен') + elif 'error' in data.keys(): + raise errors.ServiceError(data['error']) + if data['total'] == 0: + raise errors.NoResults(f'Ничего не найдено. Скорее всего произошла ошибка, попробуйте позже или сообщите об ошибке на гитхабе.') + if 'next_page' in data.keys(): + next_page = data['next_page'][data['next_page'].rfind('=')+1:] + else: + next_page = None + results += data['results'] + return (self._prettify_data(results, only_anime=only_anime), next_page) - async def search(self, title: str, limit: int|None = None) -> list: + async def search(self, title: str, limit: int|None = None, include_material_data: bool = True, only_anime: bool = False) -> list: """ ### Для использования требуется токен kodik Получение только самых основных данных о сериале. @@ -187,61 +254,33 @@ async def search(self, title: str, limit: int|None = None) -> list: Возвращает список словарей в следующем виде: [ { - 'title': Название, - 'type': тип мультимедия (anime, film, ...) - 'year': Год выпуска фильма, - 'screenshots': [ - ссылки на скриншоты + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" ], - 'shikimori_id': Id шикимори, если нет - None, - 'kinopoisk_id': Id кинопоиска, если нет - None, - 'imdb_id': Id imdb, если нет - None, - 'worldart_link': ссылка на worldart, если нет - None - 'additional_data': { - Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" }, - 'material_data' { - Здесь будут все данные о сериале имеющиеся у кодика. - В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое. + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." }, - 'link': ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p) + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" }, ... ] """ if limit is None: - search_data = await self.base_search(title, include_material_data=True) + search_data = await self.base_search(title, include_material_data=include_material_data) else: - search_data = await self.base_search(title, limit, include_material_data=True) - data = [] - added_titles = [] - for res in search_data['results']: - if res['title'] not in added_titles: - additional_data = {} - for k, i in res.items(): - if k not in ['title', 'type', 'year', 'screenshots', 'translation', - 'shikimori_id', 'kinopoisk_id', 'imdb_id', 'worldart_link', - 'id', 'link', 'title_orig', 'other_title', 'created_at', - 'updated_at', 'quality', 'material_data']: - additional_data[k] = i - - data.append({ - 'title': res['title'], - 'title_orig': res['title_orig'], - 'other_title': res['other_title'] if 'other_title' in res.keys() else None, - 'type': res['type'], - 'year': res['year'], - 'screenshots': res['screenshots'], - 'shikimori_id': res['shikimori_id'] if 'shikimori_id' in res.keys() else None, - 'kinopoisk_id': res['kinopoisk_id'] if 'kinopoisk_id' in res.keys() else None, - 'imdb_id': res['imdb_id'] if 'imdb_id' in res.keys() else None, - 'worldart_link': res['worldart_link'] if 'worldart_link' in res.keys() else None, - 'additional_data': additional_data, - 'material_data': res['material_data'], - 'link': res['link'] - }) - added_titles.append(res['title']) - return data + search_data = await self.base_search(title, limit, include_material_data=include_material_data) + return self._prettify_data(search_data['results'], only_anime=only_anime) async def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> list: """ @@ -256,24 +295,24 @@ async def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> l Возвращает список словарей в следующем виде: [ { - 'title': Название, - 'type': тип мультимедия (anime, film, ...) - 'year': Год выпуска фильма, - 'screenshots': [ - ссылки на скриншоты + "title": "Название", + "type": "тип мультимедия (anime, film, ...)", + "year": "Год выпуска фильма", + "screenshots": [ + "ссылки на скриншоты" ], - 'shikimori_id': Id шикимори, если нет - None, - 'kinopoisk_id': Id кинопоиска, если нет - None, - 'imdb_id': Id imdb, если нет - None, - 'worldart_link': ссылка на worldart, если нет - None - 'additional_data': { - Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом + "shikimori_id": "Id шикимори, если нет - None", + "kinopoisk_id": "Id кинопоиска, если нет - None", + "imdb_id": "Id imdb, если нет - None", + "worldart_link": "ссылка на worldart, если нет - None", + "additional_data": { + "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом" }, - 'material_data' { - Здесь будут все данные о сериале имеющиеся у кодика. - В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое. + "material_data": { + "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False) + В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое." }, - 'link': ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p) + "link": "ссылка на kodik.info (Пример: //kodik.info/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)" }, ... ] @@ -282,9 +321,20 @@ async def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> l search_data = await self.base_search_by_id(id, id_type, include_material_data=True) else: search_data = await self.base_search_by_id(id, id_type, limit, include_material_data=True) + return self._prettify_data(search_data['results']) + + def _prettify_data(self, results: list[dict], only_anime: bool = False) -> list[dict]: + """ + Превращает полченные данные от запроса кодику в удобный вариант словаря + + :results: список словарей (response['results'] в json'е от кодика) + :only_anime: Возвращать только варианты аниме (тип anime или anime-serial) (по умолчанию False) + """ data = [] added_titles = [] - for res in search_data['results']: + for res in results: + if only_anime and res['type'] not in ['anime-serial', 'anime']: + continue if res['title'] not in added_titles: additional_data = {} for k, i in res.items(): @@ -306,7 +356,7 @@ async def search_by_id(self, id: str, id_type: str, limit: int|None = None) -> l 'imdb_id': res['imdb_id'] if 'imdb_id' in res.keys() else None, 'worldart_link': res['worldart_link'] if 'worldart_link' in res.keys() else None, 'additional_data': additional_data, - 'material_data': res['material_data'], + 'material_data': res['material_data'] if 'material_data' in res.keys() else None, 'link': res['link'] }) added_titles.append(res['title']) diff --git a/src/tests.py b/src/tests.py index 6de8921..9d60e2e 100644 --- a/src/tests.py +++ b/src/tests.py @@ -45,6 +45,22 @@ def test_base_search_by_id(self): except Exception as ex: raise AssertionError(f'Base search with guaranteed bad search query returned error other then NoResults. Exception: {ex}') + def test_list(self): + from anime_parsers_ru import KodikParser + import anime_parsers_ru.errors as errors + parser = KodikParser(use_lxml=self.USE_LXML) + data = parser.get_list(include_material_data=False) + self.assertIsInstance(data, tuple) + self.assertIsInstance(data[0], list) + self.assertTrue(len(data[0]) > 0) + self.assertIsInstance(data[0][0], dict) + + data = parser.get_list(limit_per_page=10, pages_to_parse=3, only_anime=True) + self.assertIsInstance(data, tuple) + self.assertIsInstance(data[0], list) + self.assertTrue(len(data[0]) > 0) + self.assertIsInstance(data[0][0], dict) + def test_search(self): from anime_parsers_ru import KodikParser import anime_parsers_ru.errors as errors @@ -162,6 +178,22 @@ async def test_base_search_by_id(self): except Exception as ex: raise AssertionError(f'Base search with guaranteed bad search query returned error other then NoResults. Exception: {ex}') + async def test_list(self): + from anime_parsers_ru import KodikParserAsync + import anime_parsers_ru.errors as errors + parser = KodikParserAsync(use_lxml=self.USE_LXML) + data = await parser.get_list(include_material_data=False) + self.assertIsInstance(data, tuple) + self.assertIsInstance(data[0], list) + self.assertTrue(len(data[0]) > 0) + self.assertIsInstance(data[0][0], dict) + + data = await parser.get_list(limit_per_page=10, pages_to_parse=3, only_anime=True) + self.assertIsInstance(data, tuple) + self.assertIsInstance(data[0], list) + self.assertTrue(len(data[0]) > 0) + self.assertIsInstance(data[0][0], dict) + async def test_search(self): from anime_parsers_ru import KodikParserAsync import anime_parsers_ru.errors as errors